zoukankan      html  css  js  c++  java
  • 多线程

    多线程概述

    日常生活中很多事都是可以同时进行的,例如:人可以同时进行呼吸、血液循环、思考问题等活动。

    多线程是指一个应用程序中有多条并发执行的线索,每条线索都被称作为一个线程,它们会交替执行,彼此之间可以进行通信。

    多线程之主线程概念:JVM启动后,会有一个执行路径(线程)从main方法开始的,一直执行到main方法结束,这个线程在Java中称之为主线程。

    线程对象调用 run方法和调用start方法区别?

     调用run方法不开启线程。仅是对象调用方法。

     调用start开启线程,并让jvm调用run方法在开启的线程中执行。

    Thread

    创建线程

        方式一继承Thread

    创建线程的步骤

    1. 定义一个类继承Thread类。
    2. 重写run方法。
    3. 创建子类对象

    调用start方法,开启线程并让线程执行,同时还会告诉JVM去调用run方法。

     

    public class Demo {
        public static void main(String[] args) {
            //创建自定义线程对象
            MyThread myThread = new MyThread();
            //开启新线程    
            myThread.start();
            while (true) {// 死循环
                System.out.println("Main 方法在运行");
            }
        }
    }

    方式二 实现Runnable接口

    1. 定义类实现Runnable接口
    2. 覆盖接口中的run方法
    3. 创建Thread类的对象
    4. Runnable接口的子类对象作为参数传递给Thread类的构造函数
    5. 调用Thread类的start方法开启线程

    方式三实现callable接口:此方法不常用就不做探究了,还有别的开启线程的方法,我最常用的是上面俩种。

    线程安全问题

    个人理解:线程安全就是说多线程访问同一代码,不会产生不确定的结果,而不安全就是多个线程对同一个对象进行操作,对象本身会产生不确定的结果。

    举个例子:你去食堂打饭(你是一个线程),你来晚了,就剩最后一份糖醋排骨了(不纠结喜不喜欢吃的问题)你正在向阿姨要最后一份排骨,这时候另外一个人(另外一个线程)过来插你队也要糖醋排骨。这时候阿姨按理来说应该把排骨给你,可是这个插你队的是阿姨侄子,阿姨可以给你也可以给他侄子(就看公私分不分明)。这样阿姨就处在不确定的情况下。而线程安全就是直接强行规定死了,只能你这个线程打完饭,后面的人才能去打饭,只有等你释放了食堂阿姨这个资源之后下面的人才能去进行(打饭)操作。你不打完饭释放资源,下面的不可能去操作食堂阿姨这个角色。(个人理解有问题欢迎━(*`∀´*)ノ亻!指正)

    不喜欢吃糖醋排骨我再来一个:买票

    多线程模拟火车站的售票窗口,每一个线程表示一个售票窗口,共出售100张票

    ublic class Demo2 {
        public static void main(String[] args) {
            TicketWindow window = new TicketWindow();
            new Thread(window,"窗口1").start();
            new Thread(window,"窗口2").start();
            new Thread(window,"窗口3").start();
            new Thread(window,"窗口4").start();
        }
    }
    class TicketWindow implements Runnable{
        private int tickets = 100;
        @Override
        public void run() {
            while(true){
                if(tickets > 0){
                    try {
                        //睡眠让问题更容易“暴露”
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    String threadName = Thread.currentThread().getName();
                    System.out.println(threadName + "正在发售第"+tickets--+"张票");
                }else{
                    break;
                }
            }
        }
    }
    View Code

    上面的例子,由于多个线程共用了同一个资源(票),会造成线程安全问题,票售出了-1张

    错误截图

    线程安全问题都是由全局变量静态变量以及共享的变量引起的。若每个线程中对全局变量、静态变量以及共享的变量只有读操作,那么这个全局变量是线程安全的;

    若有多个线程同时执行写操作(修改),都需要考虑线程同步,否则就可能发生线程安全问题。

    Java中提供了线程同步机制,它能够解决上述的线程安全问题。

    方式一:同步代码块 

    方式二:同步方法

    同步代码块: 在代码块声明上,加上synchronized

    synchronized (锁对象(lock、被锁对象)) {
        可能会产生线程安全问题的代码
    }

    同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

     lock 是一个锁对象,是同步代码块的关键,锁对象可以是任意类型的对象,是多个线程共享的锁对必须是唯一的,“任意”说的是共享锁对象的类型。所以,锁对象的创建代码不能放到run()方法中,否则每个线程都会创建一个新的对象,每个锁都有自己的标志,那就没有意义了

    被锁对象:表示如果当前线程访问"被锁对象"synchronized的代码块时,其它线程不能访问此代码块,另外,也不能访问"被锁对象"中的其它synchronized的代码块;

    同步的好处:解决了多线程并发访问的问题,使多线程并发访问的共享数据能够保持一致性

    同步的弊端:使当前的对象的工作效率降低;因为要处理线程同步的问题;

    同步方法:在方法声明上加上synchronized

    public synchronized void method(){
           可能会产生线程安全问题的代码
    }


    同步方法的也是一样锁住同步的代码,但是锁对象的是Runable实现类对象,也就是this,谁调用方法,就是谁,在这里就是创建的run对象

    第三种方法:Lock接口

    java.util.concurrent.locks.Lock
    Lock接口中的方法:
    void lock():获取锁
    void unlock():释放锁

    使用方法:
    1、在Runable实现类的成员变量创建一个ReentrantLock对象
    2、在可能产生线程安全问题的代码前该对象调用lock方法获取锁
    3、在可能产生线程安全问题的代码后该对象调用unlock方法获取锁

    public class xiancheng implements Runnable {
        int ticket = 100;//火车票100张
        Object o = new Object();//创建一个锁对象
        ReentrantLock r = new ReentrantLock();
        // java除了使用关键字synchronized外,还可以使用ReentrantLock实现独占锁的功能。
        // 而且ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景
        // 在Runable实现类的成员变量创建一个ReentrantLock对象
    
        @Override
        public void run() {
    
            while (true) {
                if (ticket > 0) {
                    try {
                        r.lock();//在可能出现线程安全的地方上锁
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // unlock方法放在finally里面,无论程序是否有出现异常,该方法都会执行,也就是都会释放锁
                        r.unlock();// 3、在可能产生线程安全问题的代码后该对象调用unlock方法获取锁
    
                    }
                }
    
            }
        }
    }
  • 相关阅读:
    原来四五年没有写过什么东西了
    Spark难道比oracle性能还差?百万级数据测试性能
    程序人常去的网站(转)
    Android中关于dip和px以及转换的总结
    padding与margin的区别
    Python装饰器探究——装饰器参数
    Python 装饰器执行顺序迷思
    Python CSV模块简介
    理解线程3 c语言示例线程基本操作
    Python 外部函数调用库ctypes简介
  • 原文地址:https://www.cnblogs.com/cmm123/p/13207727.html
Copyright © 2011-2022 走看看