• Wait-Notify机制


    1. 简介

    回顾Minitor锁的结构:
    在这里插入图片描述

    • Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态
    • BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间
    • BLOCKED线程会在Owner线程释放锁时唤醒
    • WAITING线程会在Owner调用notify或notifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList重新竞争

    2. 相关API

    1. Obj.wait():让进入object的Monitor的线程到WaitSet等待
    2. Obj.wait(long n):有时限的等待,到n毫秒后结束等待,或者在n毫秒内被唤醒
    3. Obj.notify():在object上正在waitSet等待的线程中挑哟个唤醒
    4. Obj.notifyAll():让object上正在waitSet等待的线程全部被唤醒

    3. wait notify的正确姿势

    • sleep(long n)和wait(long n)的区别
    1. sleep是Thread的静态方法,而wait是Object的方法
    2. sleep不需要强制和synchronized配合使用,但wait需要
    3. sleep在睡眠的同时,不会释放对象锁,但wait在等待时会释放对象锁
    • step1

    思考下面代码:

    @Slf4j
    public class jvm {
        static final Object room=new Object();
        static boolean hasCigarette=false;
        static boolean hasTakeout=false;
    
        public static void main(String[] args) {
            new Thread(()->{
                synchronized (room){
                    log.debug("有烟没?[{}]",hasCigarette);
                    if(!hasCigarette){
                        log.debug(("没烟,先歇一会!"));
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    log.debug("有烟没?[{}]",hasCigarette);
                    if(hasCigarette){
                        log.debug("开始干活了!");
                    }
                }
            },"小南").start();
            for (int i = 0; i < 5; i++) {
                new Thread(()->{
                    synchronized (room){
                        log.debug("可以开始干活了!");
                    }
                },"其它人").start();
            }
            new Thread(()->{
                //synchronized (room){
                    hasCigarette=true;
                    log.debug("烟到了哦!");
                //}
            },"送烟的").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    在这里插入图片描述
    问题是,小南线程在睡眠的时候,其它线程全部在EntryList中阻塞等待,在高并发场景下,这种效率是很低的,所以我们需要改善这种情况。

    • Step 2

    使用wait-notify解决上面问题

    @Slf4j
    public class jvm {
        static final Object room=new Object();
        static boolean hasCigarette=false;
        static boolean hasTakeout=false;
    
        public static void main(String[] args) {
            new Thread(()->{
                synchronized (room){
                    log.debug("有烟没?[{}]",hasCigarette);
                    if(!hasCigarette){
                        try {
                            log.debug(("没烟,先歇一会!"));
                             room.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    log.debug("有烟没?[{}]",hasCigarette);
                    if(hasCigarette){
                        log.debug("开始干活了!");
                    }
                }
            },"小南").start();
            for (int i = 0; i < 5; i++) {
                new Thread(()->{
                    synchronized (room){
                        log.debug("可以开始干活了!");
                    }
                },"其它人").start();
            }
            new Thread(()->{
                synchronized (room){
                    hasCigarette=true;
                    log.debug("烟到了哦!");
                    room.notify();
                }
            },"送烟的").start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    在这里插入图片描述
    上面代码初步解决了前面小南线程在等待烟的时候,所有等待room的线程都会阻塞的问题。但同时也引入了新的问题,假如不止小南线程在等待,还有其它线程在wait,那么送烟线程会错误的唤醒其它线程,而不是指定的小南线程

    • Step 3
    @Slf4j
    public class jvm {
        static final Object room=new Object();
        static boolean hasCigarette=false;
        static boolean hasTakeout=false;
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                synchronized (room){
                    log.debug("有烟没?[{}]",hasCigarette);
                    if(!hasCigarette){
                        try {
                            log.debug(("没烟,先歇一会!"));
                             room.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    log.debug("有烟没?[{}]",hasCigarette);
                    if(hasCigarette){
                        log.debug("开始干活了!");
                    }else {
                        log.debug("这活干不了!");
                    }
                }
            },"小南").start();
            new Thread(()->{
                synchronized (room){
                    log.debug("外买到了没?[{}]",hasTakeout);
                    if(!hasTakeout){
                        try {
                            log.debug(("没外卖,先歇一会!"));
                            room.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    log.debug("外买到了没?[{}]",hasTakeout);
                    if(hasTakeout){
                        log.debug("开始干活了!");
                    }else{
                        log.debug("这活干不了!");
                    }
                }
            },"小北").start();
            Thread.sleep(1000);
            new Thread(()->{
                synchronized (room){
                    hasTakeout=true;
                    log.debug("外卖到了哦!");
                    room.notify();
                }
            },"送外卖的").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    在这里插入图片描述
    从上面结果我们可以看出,外卖的线程唤醒了小南线程,这就出现了虚假唤醒的情况,那么怎么解决这个问题呢,我们使用notifyAll可以解决上面问题。

    @Slf4j
    public class jvm {
        static final Object room=new Object();
        static boolean hasCigarette=false;
        static boolean hasTakeout=false;
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                synchronized (room){
                    log.debug("有烟没?[{}]",hasCigarette);
                    if(!hasCigarette){
                        try {
                            log.debug(("没烟,先歇一会!"));
                             room.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    log.debug("有烟没?[{}]",hasCigarette);
                    if(hasCigarette){
                        log.debug("开始干活了!");
                    }else {
                        log.debug("这活干不了!");
                    }
                }
            },"小南").start();
            new Thread(()->{
                synchronized (room){
                    log.debug("外买到了没?[{}]",hasTakeout);
                    if(!hasTakeout){
                        try {
                            log.debug(("没外卖,先歇一会!"));
                            room.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    log.debug("外买到了没?[{}]",hasTakeout);
                    if(hasTakeout){
                        log.debug("开始干活了!");
                    }else{
                        log.debug("这活干不了!");
                    }
                }
            },"小北").start();
            Thread.sleep(1000);
            new Thread(()->{
                synchronized (room){
                    hasTakeout=true;
                    log.debug("外卖到了哦!");
                    room.notifyAll();
                }
            },"送外卖的").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    在这里插入图片描述
    虽然我们解决了小北的问题,小南的问题我们还是没有解决,由于小南被外卖线程给唤醒了,但是却没有拿到烟,这是小南线程还是没干活,这就是新出现的问题。

    • Step 4

    我们之前使用的if判断,如果我们改成while循环就解决了上面问题

    @Slf4j
    public class jvm {
        static final Object room=new Object();
        static boolean hasCigarette=false;
        static boolean hasTakeout=false;
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                synchronized (room){
                    log.debug("有烟没?[{}]",hasCigarette);
                    while(!hasCigarette){
                        try {
                            log.debug(("没烟,先歇一会!"));
                             room.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    log.debug("有烟没?[{}]",hasCigarette);
                    if(hasCigarette){
                        log.debug("开始干活了!");
                    }else {
                        log.debug("这活干不了!");
                    }
                }
            },"小南").start();
            new Thread(()->{
                synchronized (room){
                    log.debug("外买到了没?[{}]",hasTakeout);
                    while(!hasTakeout){
                        try {
                            log.debug(("没外卖,先歇一会!"));
                            room.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    log.debug("外买到了没?[{}]",hasTakeout);
                    if(hasTakeout){
                        log.debug("开始干活了!");
                    }else{
                        log.debug("这活干不了!");
                    }
                }
            },"小北").start();
            Thread.sleep(1000);
            new Thread(()->{
                synchronized (room){
                    hasTakeout=true;
                    log.debug("外卖到了哦!");
                    room.notifyAll();
                }
            },"送外卖的").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    在这里插入图片描述

    到此为止我们解决了虚假唤醒的问题

    4. 总结

    我们使用wait-notify的正确姿势应该如下:

    synchronized(lock){
    	while(条件B不成立)
    	{
           lock.wait();
    	}
    	//代码逻辑
    }
    synchronized(lock){
    	lock.notifyAll();
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    rust macro创建及使用
    猿创征文|一个.Net过气开发工程师的成长之路
    Reids实战——分布式锁优化(Lua脚本)
    小白入门pytorch(二)----神经网络
    nRF5340(入门篇)之1.0 window下开发环境搭建
    用户管理系统(1)
    SpringBoot项目--电脑商城【增加/减少购物车商品数量】
    多输入多输出 | MATLAB实现PSO-BP粒子群优化BP神经网络多输入多输出
    js文件模块化引用问题(JavaScript modules)
    链式法则(Chain Rule)
  • 原文地址:https://blog.csdn.net/qq_43456605/article/details/134262543