美文网首页Android 线程多线程Android知识
多线程编程基础之 wait()、notify()、sleep()

多线程编程基础之 wait()、notify()、sleep()

作者: 明朗__ | 来源:发表于2017-04-15 18:41 被阅读326次

    前言:
    在面试过程中 关于多线程编程这一块是经常问到的 为了更好的理解关于多线程编程基础特地的记录此文章把思路理清楚
    线程的生命周期

    1. 首先线程一般是这样创建的:

    new Thread(){run(){....}}.start();
    new Thread(new Runnable(){run().....}).start();

    2.来看一个经典案例 生产者和消费者的问题

        static class Product{
            public static String value;
        }
        //生产者线程
        static  class Producer extends Thread{
            @Override
            public void run() {
                while (true){
                    if (Product.value==null){
                        Product.value="No"+System.currentTimeMillis();
                        System.out.println("产品:"+Product.value);
                    }
                }
            }
        }
        //消费者线程
        static  class Consumer extends Thread{
            @Override
            public void run() {
                while (true){
                    if (Product.value!=null){
                        Product.value=null;
                        System.out.println("产品已消费");
                    }
                }
            }
        }
    
    //调用:
    public static void main(String[] args){
            new Producer().start();//开启生产
            new Consumer().start();//开启消费
        }
    
    产品:null
    产品已消费
    产品:null
    产品:No1492225732628
    产品已消费
    产品已消费
    产品:No1492225732628
    产品:No1492225732629
    
    thread
    读操作会优先读取工作内存的数据,如果工作内存中不存在,则从主内存中拷贝一份数据到工作内存中;写操作只会修改工作内存的副本数据,这种情况下,其它线程就无法读取变量的最新值
    解决:
    在解决这个问题的时候先来了解下Java中关于多线程中使用的一些关键字和一些方法的作用
    关键字 作用
    volatile 线程操作变量可见
    Lock Java6.0增加的线程同步锁
    synchronized 线程同步锁
    wait() 让该线程处于等待状态
    notify() 唤醒处于wait的线程
    notifyAll() 唤醒所有处于wait状态的线程
    sleep() 线程休眠
    join() 使当线程处于阻塞状态
    yield() 让出该线程的时间片给其他线程

    注意:
    1. wait()、notify()、notifyAll()都必须在synchronized中执行,否则会抛出异常
    2. wait()、notify()、notifyAll()都是属于超类Object的方法
    2. 一个对象只有一个锁(对象锁和类锁还是有区别的)

    一. 使用volatile关键字:

     static class Product{
            //添加volatile关键字
            public volatile static String value;
        }
    //调用
    public static void main(String[] args){
            new Producer().start();//开启生产
            new Consumer().start();//开启消费
        }
    
    产品:No1492229533263
    产品已消费
    产品:No1492229533263
    产品已消费
    产品:No1492229533263
    产品已消费
    产品:No1492229533263
    产品已消费
    产品:No1492229533263
    产品已消费
    产品:No1492229533263
    产品已消费
    

    Volatile: 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。volatile关键字会强制将修改的值立即写入主存,使线程的工作内存中缓存变量行无效。
    缺点:但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。
    二. 使用synchronized线程同步锁

    static class Product{
            public static String value;
        }
    //生产者线程
        static  class Producer extends Thread{
            Object object;
            public Producer(Object object) {
                this.object = object;
            }
            @Override
            public void run() {
                while (true) {
                    synchronized (object) {
                        if (Product.value == null) {
                            Product.value = "No" + System.currentTimeMillis();
                            System.out.println("产品:" + Product.value);
                        }
                    }
                }
            }
        }
    
    //消费者线程
        static  class Consumer extends Thread{
            Object object;
            public Consumer(Object object) {
                this.object=object;
            }
            @Override
            public void run() {
                while (true) {
                    synchronized (object) {
                        if (Product.value != null) {
                            Product.value = null;
                            System.out.println("产品已消费");
                        }
                    }
                }
            }
        }
    
    //调用
    public static void main(String[] args){
            Object object=new Object();
            new Producer(object).start();//开启生产
            new Consumer(object).start();//开启消费
        }
    
    产品:No1492244495050
    产品已消费
    产品:No1492244495050
    产品已消费
    产品:No1492244495050
    产品已消费
    产品:No1492244495050
    产品已消费
    产品:No1492244495050
    

    上面通过synchronizedObject object=new Object();加锁 也叫对象锁 实现锁的互斥,当生产线程生产产品的时候会对object加锁 消费者线程会进入阻塞状态 直到生产线程完成产品的生产释放锁 反之也是同样的道理。但是这样只能被动的唤醒线程的执行 可以使用wait和notify来进行主动唤醒线程继续执行

    //生产者线程
        static class Producer extends Thread {
            Object object;
            public Producer(Object object) {
                this.object = object;
            }
            @Override
            public void run() {
                while (true) {
                    //对象锁
                    synchronized (object) {
                        if (Product.value != null) {
                            try {
                                object.wait();//产品还未消费 进入等待状态
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        Product.value = "No" + System.currentTimeMillis();
                        System.out.println("产品:" + Product.value);
                        object.notify();//产品已生产 唤醒消费者线程
                    }
                }
            }
        }
    
    //消费者线程
        static class Consumer extends Thread {
            Object object;
            public Consumer(Object object) {
                this.object = object;
            }
            @Override
            public void run() {
                while (true) {
                    synchronized (object) {
                        if (Product.value == null) {
                            try {
                                object.wait();//产品为空 进入等待状态
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        Product.value = null;
                        System.out.println("产品已消费");
                        object.notify();//产品已经消费 唤醒生产者线程生产
                    }
                }
            }
        }
    
    public static void main(String[] args){
            Object object=new Object();
            new Producer(object).start();//开启生产
            new Consumer(object).start();//开启消费
        }
    
    产品:No1492246274190
    产品已消费
    产品:No1492246274190
    产品已消费
    产品:No1492246274190
    产品已消费
    

    通过添加wait notify关键字去主动唤醒生产者或消费者线程的执行
    三.线程同步之ReentrantLock锁
    与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁,缺点就是缺少像synchronized那样隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

    Lock lock=new ReentrantLock();
    public void setSpData(String name){
            lock.lock();
            try {
                //线程同步操作 比如IO读写 等
            }finally {
                lock.unlock();
            }
        }
    

    注意:
    注意的是,千万不要忘记调用unlock来释放锁,否则可能会引发死锁等问题,
    而用synchronized,JVM将确保锁会获得自动释放,这也是为什么Lock没有完全替代掉synchronized的原因
    四.sleep(),join(),yield()的使用

    sleep()让线程休息指定的时间,时间一到就继续运行

     new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(3000);//休眠3秒 毫秒为单位
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
    

    join()让指定的线程先执行完再执行其他线程,而且会阻塞主线程

     static class B extends Thread {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("线程一:" + 1);
                }
            }
        }
    static class A extends Thread {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("线程二:" + 1);
                }
            }
        }
    
    public static void main(String[] args) {
            A a = new A();
            B b = new B();
            try {
                a.start();//启动线程
                a.join();//join 该线程优先执行 其他线程进入等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                b.start();//启动线程
                b.join();//join 该线程优先执行 其他线程进入等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    线程二:0
    线程二:1
    线程二:2
    线程二:3
    线程二:4
    线程一:0
    线程一:1
    线程一:2
    线程一:3
    线程一:4
    

    yield()将指定线程先礼让一下别的线程的先执行

    注意:yield()会礼让给相同优先级的或者是优先级更高的线程执行,不过yield()这个方法只是把线程的执行状态打回准备就绪状态,所以执行了该方法后,有可能马上又开始运行,有可能等待很长时间

    static class B extends Thread {
            String name;
            public B(String name) {
                this.name=name;
            }
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(name + i);
                    if(i==3){
                        System.out.println("将时间片礼让给别的线程");
                        Thread.yield();
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    
    public static void main(String[] args) {
            new B("线程一:").start();
            new B("线程二:").start();
        }
    
    线程一:0
    线程一:1
    线程一:2
    线程一:3
    将时间片礼让给别的线程
    线程二:0
    线程二:1
    线程二:2
    线程二:3
    将时间片礼让给别的线程
    线程二:4
    线程一:4
    

    其他:

    1. 线程优先级:通过setPriority(int priority)设置线程优先级提高线程获取时间的几率(这只是提高线程优先获取时间片的几率 而不是肯定优先执行) getPriority()获取线程的优先级 最高为10 最低为1 默认为5
    public static void main(String[] args) {
            A a = new A("线程一:");
            B b = new B("线程二:");
            a.setPriority(3);
            b.setPriority(10);
            a.start();
            b.start();
        }
    
    1. 守护线程:前面所讲的是用户线程 其实还有一个守护线程,起作:用只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者,在编写程序时也可以自己设置守护线程。
    static class B extends Thread {
            String name;
            public B(String name) {
                this.name=name;
            }
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(name + i);
                }
            }
        }
        static class A extends Thread {
            String name;
            public A(String name) {
                this.name=name;
            }
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    System.out.println(name + i);
                }
            }
        }
    
    public static void main(String[] args) {
            A a = new A("守护线程执行:");
            B b = new B("用户线程执行:");
            a.setDaemon(true);//设置a线程为守护线程 必须在start()前设置 不然或有异常
            a.isDaemon();//判断a线程是否为守护线程
            a.start();
            b.start();
        }
    
    守护线程执行:0
    守护线程执行:1
    守护线程执行:2
    守护线程执行:3
    守护线程执行:4
    守护线程执行:5
    守护线程执行:6
    守护线程执行:7
    守护线程执行:8
    用户线程执行:0
    用户线程执行:1
    用户线程执行:2
    用户线程执行:3
    用户线程执行:4
    守护线程执行:9
    守护线程执行:10
    守护线程执行:11
    守护线程执行:12
    守护线程执行:13
    守护线程执行:14
    守护线程执行:15
    守护线程执行:16
    守护线程执行:17
    Process finished with exit code 0
    

    当用户线程执行完毕后 JVM虚拟了退出前 守护线程随着JVM一同结束工作
    设置线程为守护线程 必须在start()前设置 不然或有异常

    相关文章

      网友评论

      • 787599e1c3f8:文中说不同的线程会开辟自己的工作内存空间,那第一个例子中,生产者和消费者不是就各自有两份Product.value么,为什么打印结果生产者线程还是会受到Product.value值被消费者线程改变的影响
      • 来往穿梭:麻烦问一下,第一个生产者例子里为什么刚给Product.value赋值完,紧接着还打印空?这中间的工作内存和主内存的变化是什么样的呢?
        MiBoy:@明朗__ 另外一个线程抢先执行 去将Product.value赋值为null,那是把工作内存置空了还是主内存置空了
        明朗__:这样出现的原因是因为没有使用同步锁的缘故 导致Product.value 赋值后还没打印前 另外一个线程抢先执行 去将Product.value赋值为null 并打印了 (备注:这个是我当时运行的情况,因为未用锁进行控制 所以什么情况都有可能发生 )

      本文标题:多线程编程基础之 wait()、notify()、sleep()

      本文链接:https://www.haomeiwen.com/subject/bsqoattx.html