• 并发之wait/notify说明


    1 wait/notify的原理

    在这里插入图片描述

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

    2 wait/notify的API

    • obj.wait() 让进入 object 监视器的线程到 waitSet 等待

    • obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒**(随机)**

    • obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

    它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这些方法

    final static Object obj = new Object();
    public static void main(String[] args) {
     new Thread(() -> {
     synchronized (obj) {
     log.debug("执行....");
     try {
         // 让线程在obj上一直等待下去
     obj.wait(); 
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     log.debug("其它代码....");
     }
     }).start();
     new Thread(() -> {
     synchronized (obj) {
     log.debug("执行....");
     try {
         // 让线程在obj上一直等待下去
     obj.wait(); 
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     log.debug("其它代码....");
     }
     }).start();
     // 主线程两秒后执行
     sleep(2);
     log.debug("唤醒 obj 上其它线程");
     synchronized (obj) {
         // 唤醒obj上一个线程  第一次测试
     obj.notify(); 
         // 唤醒obj上所有等待线程 第二次测试
     // obj.notifyAll(); 
     }
    }
    
    • 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

    第一次测试notify结果

    20:00:53.096 [Thread-0] c.TestWaitNotify - 执行.... 
    20:00:53.099 [Thread-1] c.TestWaitNotify - 执行.... 
    20:00:55.096 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
    20:00:55.096 [Thread-0] c.TestWaitNotify - 其它代码.... 
    
    • 1
    • 2
    • 3
    • 4

    第二次测试notifyAll结果

    19:58:15.457 [Thread-0] c.TestWaitNotify - 执行.... 
    19:58:15.460 [Thread-1] c.TestWaitNotify - 执行.... 
    19:58:17.456 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
    19:58:17.456 [Thread-1] c.TestWaitNotify - 其它代码.... 
    19:58:17.456 [Thread-0] c.TestWaitNotify - 其它代码.... 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到 notify 为止.

    wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify

    3 wait/notify的使用

    sleep(long n)和wait(long n)的区别

    不同:

    • sleep 是 Thread 方法,而 wait 是 Object 的方法
    • sleep 不需要强制和 synchronized 配合使用,但 wait 需要 和 synchronized 一起用
    • sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁

    相同:

    • 它们 状态 TIMED_WAITING

    案例1

    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    
    new Thread(() -> {
     synchronized (room) {
     log.debug("有烟没?[{}]", hasCigarette);
     if (!hasCigarette) {
     log.debug("没烟,先歇会!");
     sleep(2);
     }
     log.debug("有烟没?[{}]", hasCigarette);
     if (hasCigarette) {
     log.debug("可以开始干活了");
     }
     }
    }, "小南").start();
    for (int i = 0; i < 5; i++) {
     new Thread(() -> {
     synchronized (room) {
     log.debug("可以开始干活了");
     }
     }, "其它人").start();
    }
    sleep(1);
    new Thread(() -> {
     // 这里能不能加 synchronized (room)?
     hasCigarette = true;
     log.debug("烟到了噢!");
    }, "送烟的").start();
    
    /*
    运行结果:
    20:49:49.883 [小南] c.TestCorrectPosture - 有烟没?[false] 
    20:49:49.887 [小南] c.TestCorrectPosture - 没烟,先歇会!
    20:49:50.882 [送烟的] c.TestCorrectPosture - 烟到了噢!
    20:49:51.887 [小南] c.TestCorrectPosture - 有烟没?[true] 
    20:49:51.887 [小南] c.TestCorrectPosture - 可以开始干活了
    20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
    20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
    20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
    20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
    20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
    
    */
    
    • 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

    说明:

    • 其它干活的线程,都要一直阻塞,效率太低
    • 小南线程必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来
    • 加了 synchronized (room) 后,就好比小南在里面反锁了门睡觉,烟根本没法送进门,main 没加 synchronized 就好像 main 线程是翻窗户进来的
    • 解决方法,使用 wait - notify 机制

    案例2

    new Thread(() -> {
     synchronized (room) {
     log.debug("有烟没?[{}]", hasCigarette);
     if (!hasCigarette) {
     log.debug("没烟,先歇会!");
     try {
     room.wait(2000);
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     }
     log.debug("有烟没?[{}]", hasCigarette);
     if (hasCigarette) {
     log.debug("可以开始干活了");
     }
     }
    }, "小南").start();
    for (int i = 0; i < 5; i++) {
     new Thread(() -> {
     synchronized (room) {
     log.debug("可以开始干活了");
     }
     }, "其它人").start();
    }
    sleep(1);
    new Thread(() -> {
     synchronized (room) {
     hasCigarette = true;
     log.debug("烟到了噢!");
     room.notify();
     }
    }, "送烟的").start();
    /*
    运行结果:
    20:51:42.489 [小南] c.TestCorrectPosture - 有烟没?[false] 
    20:51:42.493 [小南] c.TestCorrectPosture - 没烟,先歇会!
    20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
    20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
    20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
    20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
    20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
    20:51:43.490 [送烟的] c.TestCorrectPosture - 烟到了噢!
    20:51:43.490 [小南] c.TestCorrectPosture - 有烟没?[true] 
    20:51:43.490 [小南] c.TestCorrectPosture - 可以开始干活了
    */
    
    • 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

    说明:

    • 解决了其它干活的线程阻塞的问题

    存在问题, 即存在其他线程等待的场景,不一定保证唤醒指定的线程.

    案例3

    new Thread(() -> {
     synchronized (room) {
     log.debug("有烟没?[{}]", hasCigarette);
     if (!hasCigarette) {
     log.debug("没烟,先歇会!");
     try {
     room.wait();
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     }
     log.debug("有烟没?[{}]", hasCigarette);
     if (hasCigarette) {
     log.debug("可以开始干活了");
     } else {
     log.debug("没干成活...");
     }
     }
    }, "小南").start();
    new Thread(() -> {
     synchronized (room) {
     Thread thread = Thread.currentThread();
     log.debug("外卖送到没?[{}]", hasTakeout);
     if (!hasTakeout) {
     log.debug("没外卖,先歇会!");
     try {
     room.wait();
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     }
     log.debug("外卖送到没?[{}]", hasTakeout);
     if (hasTakeout) {
     log.debug("可以开始干活了");
     } else {
     log.debug("没干成活...");
     }
     }
    }, "小女").start();
    sleep(1);
    new Thread(() -> {
     synchronized (room) {
     hasTakeout = true;
     log.debug("外卖到了噢!");
     room.notify();
     }
    }, "送外卖的").start();
    /*
    运行结果:
    20:53:12.173 [小南] c.TestCorrectPosture - 有烟没?[false] 
    20:53:12.176 [小南] c.TestCorrectPosture - 没烟,先歇会!
    20:53:12.176 [小女] c.TestCorrectPosture - 外卖送到没?[false] 
    20:53:12.176 [小女] c.TestCorrectPosture - 没外卖,先歇会!
    20:53:13.174 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
    20:53:13.174 [小南] c.TestCorrectPosture - 有烟没?[false] 
    20:53:13.174 [小南] c.TestCorrectPosture - 没干成活... 
    
    */
    
    • 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
    • 56
    • 57
    • 58

    说明:

    • notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线 程,称为 虚假唤醒

    解决方法: notifyAll唤醒全部线程

    案例4

    new Thread(() -> {
     synchronized (room) {
     hasTakeout = true;
     log.debug("外卖到了噢!");
     room.notifyAll();
     }
    }, "送外卖的").start();
    /*
    20:55:23.978 [小南] c.TestCorrectPosture - 有烟没?[false] 
    20:55:23.982 [小南] c.TestCorrectPosture - 没烟,先歇会!
    20:55:23.982 [小女] c.TestCorrectPosture - 外卖送到没?[false] 
    20:55:23.982 [小女] c.TestCorrectPosture - 没外卖,先歇会!
    20:55:24.979 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
    20:55:24.979 [小女] c.TestCorrectPosture - 外卖送到没?[true] 
    20:55:24.980 [小女] c.TestCorrectPosture - 可以开始干活了
    20:55:24.980 [小南] c.TestCorrectPosture - 有烟没?[false] 
    20:55:24.980 [小南] c.TestCorrectPosture - 没干成活... 
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    说明:

    • 用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新 判断的机会了

    解决: 用 while + wait,当条件不成立,再次 wait

    案例5

    if (!hasCigarette) {
     log.debug("没烟,先歇会!");
     try {
     room.wait();
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
    }
    
    // 改为
    
    while (!hasCigarette) {
     log.debug("没烟,先歇会!");
     try {
     room.wait();
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
    }
    /*
    运行结果:
    20:58:34.322 [小南] c.TestCorrectPosture - 有烟没?[false] 
    20:58:34.326 [小南] c.TestCorrectPosture - 没烟,先歇会!
    20:58:34.326 [小女] c.TestCorrectPosture - 外卖送到没?[false] 
    20:58:34.326 [小女] c.TestCorrectPosture - 没外卖,先歇会!
    20:58:35.323 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
    20:58:35.324 [小女] c.TestCorrectPosture - 外卖送到没?[true] 
    20:58:35.324 [小女] c.TestCorrectPosture - 可以开始干活了
    20:58:35.324 [小南] c.TestCorrectPosture - 没烟,先歇会!
    
    */
    
    • 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

    结构模式:

    synchronized(lock) {
     while(条件不成立) {
     lock.wait();
     }
     // 干活
    }
    //另一个线程
    synchronized(lock) {
     lock.notifyAll();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    前端进击笔记第十五节 代码构建与 Webpack 必备技能
    前端HTML5 +CSS3 1. 基础认知
    LeetCode - 二维数组及滚动数组
    如何成为优秀的产品经理?新人必看!
    阿里、华为、达梦等十大国产数据库案例及布局
    Java泛型漫谈
    好物推荐:文字转语音朗读软件哪个好?
    nodejs--开发自己的项目—3.2--优化-表单的数据验证——合法性
    【自适应滤波】基于FxLMS的样条自适应滤波算法分析(Matlab代码实现)
    Hadoop学习笔记(2)——HDFS(1)
  • 原文地址:https://blog.csdn.net/ABestRookie/article/details/125492850