编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 1.1String深入理解原理
  • 1.2浮点型数据深入研究
  • 1.3数据装箱和拆箱原理
  • 1.4泛型由来和设计思想
  • 1.5加密和解密设计和原理
  • 2.1面向对象设计思想
  • 2.2抽象类和接口设计
  • 2.3封装和继承设计思想
  • 2.4复用和组合设计思想
  • 2.5对象和引用设计思想
  • 3.1IO流设计思想和原理
  • 3.2为何设计序列化数据
  • 3.3各种拷贝数据比较
  • 3.4高效文件读写的原理
  • 4.1反射性能探索和优化
  • 4.2为何要设计注解思想
  • 4.3动态代理的设计思想
  • 4.4SPI机制设计的思想
  • 4.5异常设计和捕获原理
  • 4.6虚拟机如何处理异常
  • 4.7四种引用设计思想
  • 5.1线程的前世今生探索
  • 5.2线程通信的设计思想
  • 5.3线程监控和Debug设计
  • 5.4线程和JVM之间联系
  • 5.5线程池使用技巧介绍
  • 5.6线程池设计核心原理
  • 5.7线程如何最大优化
  • 6.1多线程并发经典案例
  • 6.2并发安全前世今生
  • 6.3线程安全如何保证
  • 6.4变量的线程安全探索
  • 6.5并发上下文切换原理
  • 6.6理解CAS设计和由来
  • 6.7协程设计思想和原理
  • 6.8事物并发模型解读
  • 6.9并发设计模型研究
  • 6.10并发编程数据一致性
  • 6.11锁问题的定位和修复
  • 6.12多线程如何性能调优
  • 7.1类的加载过程和原理
  • 7.2对象布局设计的原理
  • 7.3双亲委派机制设计思想
  • 7.5代码攻击和安全防护
  • 7.6设计动态生成Java类

5.2线程通信的设计思想

目录介绍

  • 01.线程通信的背景
    • 1.1 先思考一下问题
    • 1.2 线程通信概念
    • 1.3 设计通信的思想
  • 02.线程通信的方式
    • 2.1 通过需求实现案例
    • 2.2 wait和notify方式
    • 2.3 Condition休眠和唤醒
    • 2.4 CountDownLatch方式
  • 03.wait和notify
    • 3.1 wait和notifyAll实践
    • 3.2 wait虚假唤醒问题
    • 3.3 解决wait虚假唤醒
    • 3.4 替换notify出现死锁
    • 3.5 搞清楚锁池和等待池
    • 3.6 notify和notifyAll
    • 3.7 notify死锁解决方法
  • 04.Condition设计思想
    • 4.1
  • 05.CountDownLatch实践
    • 5.1
  • 07.高级并发工具类

参考博客

  • https://www.51cto.com/article/750927.html

01.线程通信的背景

1.1 先思考一下问题

  • 什么是线程通信,有什么作用
  • 线程通信的三种实现方式
  • notifyAll的虚假唤醒问题,notify死锁问题
  • 通过 ReentrantLock 实现精确唤醒
  • 多线程按顺序执行的四种方案

1.2 线程通信概念

  • 线程通信是指线程之间通过某种机制传递信息,以协调它们之间的行为。
    • Java 中提供了内置的线程通信机制,包括 wait()、notify() 和 notifyAll() 方法,这些方法都是 Object 类的一部分,每个对象都可以作为线程通信的锁。

1.3 设计通信的思想

  • 在Java中,线程通信的思想主要基于两个关键概念:共享对象和同步机制。以下是一些常见的线程通信思想和机制:

02.线程通信的方式

2.1 通过需求实现案例

  • 比如:小明放假在家,肚子饿了,如果发现没有吃的就会喊:妈,我饿了,弄点吃的,如果妈妈发现没有吃的了就会做菜,通知小明吃饭,总之:有菜通知小明吃饭,没菜小明通知妈妈做饭,简直吃货一个。
  • 此时就是两个线程对饭菜这同一资源有不同的任务,妈妈线程就是做饭,小明线程是吃饭,如果想要实现上边的场景,就需要妈妈线程和小明线程之间通信。

2.2 wait和notify方式

  • Object类中相关的方法有notify方法和wait方法。因为wait和notify方法定义在Object类中,因此会被所有的类所继承。这些方法都是final的,即它们都是不能被重写的,不能通过子类覆写去改变它们的行为。
    • ①wait()方法: 让当前线程进入等待,并释放锁。
    • ②wait(long)方法: 让当前线程进入等待,并释放锁,不过等待时间为long,超过这个时间没有对当前线程进行唤醒,将自动唤醒。
    • ③notify()方法: 让当前线程通知那些处于等待状态的线程,当前线程执行完毕后释放锁,并从其他线程中唤醒其中一个继续执行。
    • ④notifyAll()方法: 让当前线程通知那些处于等待状态的线程,当前线程执行完毕后释放锁,将唤醒所有等待状态的线程。

2.3 Condition休眠和唤醒

  • 关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,类似ReentrantLock也可以实现同样的功能,但需要借助于Condition对象。
  • 关于Condition实现等待/通知就不详细介绍了,可以完全类比wait()/notify(),基本使用和注意事项完全一致。
  • 就只简单介绍下类比情况:
    • condition.await()————>lock.wait()
    • condition.await(long time, TimeUnit unit)————>lock.wait(long timeout)
    • condition.signal()————>lock.notify()
    • condition.signaAll()————>lock.notifyAll()

2.4 CountDownLatch方式

  • 使用JUC中的CountDownLatch【计数器】

03.wait和notify

3.1 wait和notifyAll实践

  • 在此案例中,同一资源就是饭菜,小明对吃的操作是造,而妈妈对吃的操作是做。
    public class WaitNotifyAllTest {
    
        public static void main(String[] args) {
            // 创建饭菜对象
            KitChenRoom chenRoom = new KitChenRoom();
            // 创建小明妈妈线程,做饭
            new Thread(() -> {
                for (int i = 0; i < 3; i++) {
                    chenRoom.cook();
                }
            },"小明妈妈线程:").start();
            // 创建小明自己线程,吃饭
            new Thread(() -> {
                for (int i = 0; i < 3; i++) {
                    chenRoom.eat();
                }
            },"小明自己线程:").start();
        }
    
        public static class KitChenRoom {
            // 是否有吃的
            private boolean hasFood = false;
            // 设置同步锁,做饭和吃饭只能同时有一个在执行,不能边做边吃
            private final Object lock = new Object();
            // 做饭
            public void cook() {
                // 加锁
                synchronized (lock) {
                    // 如果有吃的,就不做饭
                    if(hasFood) {
                        // 还有吃的,先不做饭
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    // 否则就做饭,
                    System.out.println(Thread.currentThread().getName() + "没吃的了,给娃做饭!");
                    // 做好之后,修改为true
                    hasFood = true;
                    // 通知其他线程吃饭
                    lock.notifyAll();
                }
            }
    
            // 吃饭
            public void eat() {
                synchronized (lock) {
                    // 如果没吃的,就喊妈妈做饭,暂时吃不了
                    if (!hasFood) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    // 否则就吃饭
                    System.out.println(Thread.currentThread().getName() + "感谢老妈,恰饭,恰饭");
                    // 吃完之后,修改为false
                    hasFood = false;
                    // 通知其他线程吃饭
                    lock.notifyAll();
                }
            }
        }
    }
  • 运行结果:发现两个线程交替执行,没饭的时候妈妈做饭,有饭的时候小明就恰饭
    小明妈妈线程:没吃的了,给娃做饭!
    小明自己线程:感谢老妈,恰饭,恰饭
    小明妈妈线程:没吃的了,给娃做饭!
    小明自己线程:感谢老妈,恰饭,恰饭
    小明妈妈线程:没吃的了,给娃做饭!
    小明自己线程:感谢老妈,恰饭,恰饭

3.2 wait虚假唤醒问题

  • 在wait方法的源码注释中有这么一段话:
    • As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop
    • 翻译:在单参数版本中,中断和虚假唤醒是可能的,并且该方法应始终在循环中使用
  • 现在业务场景添加:如果此时我们再开启一个大明线程吃饭,开启一个爸爸线程做饭,此时会发生什么问题呢。
  • 改造测试类:再开启一个大明线程和一个爸爸线程。
    public class WaitNotifyAllTest2 {
    
        public static void main(String[] args) {
            // 创建饭菜对象
            KitChenRoom chenRoom = new KitChenRoom();
            // 创建小明妈妈线程,做饭
            new Thread(() -> {
                for (int i = 0; i < 3; i++) {
                    chenRoom.cook();
                }
            },"小明妈妈线程:").start();
            // 爸爸线程:做饭
            new Thread(() -> {
                for (int i = 0; i < 3; i++) {
                    chenRoom.cook();
                }
            },"小明爸爸线程:").start();
            // 创建小明自己线程,吃饭
            new Thread(() -> {
                for (int i = 0; i < 3; i++) {
                    chenRoom.eat();
                }
            },"小明自己线程:").start();
            // 大明线程:吃饭
            new Thread(() -> {
                for (int i = 0; i < 3; i++) {
                    chenRoom.eat();
                }
            },"大明哥哥线程:").start();
        }
    
        public static class KitChenRoom {
            // 是否有吃的
            private boolean hasFood = false;
            // 设置同步锁,做饭和吃饭只能同时有一个在执行,不能边做边吃
            private final Object lock = new Object();
            // 做饭
            public void cook() {
                // 加锁
                synchronized (lock) {
                    // 如果有吃的,就不做饭
                    if(hasFood) {
                        // 还有吃的,先不做饭
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    // 否则就做饭,
                    System.out.println(Thread.currentThread().getName() + "没吃的了,给娃做饭!");
                    // 做好之后,修改为true
                    hasFood = true;
                    // 通知其他线程吃饭
                    lock.notifyAll();
                }
            }
    
            // 吃饭
            public void eat() {
                synchronized (lock) {
                    // 如果没吃的,就喊妈妈做饭,暂时吃不了
                    if (!hasFood) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    // 否则就吃饭
                    System.out.println(Thread.currentThread().getName() + "感谢老妈,恰饭,恰饭");
                    // 吃完之后,修改为false
                    hasFood = false;
                    // 通知其他线程吃饭
                    lock.notifyAll();
                }
            }
        }
    }
  • 运行结果:发现爸爸线程和妈妈线程连着做了三次饭
    小明妈妈线程:没吃的了,给娃做饭!
    小明自己线程:感谢老妈,恰饭,恰饭
    小明爸爸线程:没吃的了,给娃做饭!
    小明妈妈线程:没吃的了,给娃做饭!
    小明爸爸线程:没吃的了,给娃做饭!
    小明自己线程:感谢老妈,恰饭,恰饭
    小明爸爸线程:没吃的了,给娃做饭!
    小明妈妈线程:没吃的了,给娃做饭!
    小明自己线程:感谢老妈,恰饭,恰饭
  • 为什么会出现这种情况呢?分析一下:
    • 这是由于wait方法的机制导致的,wait方法会使线程阻塞,直到被唤醒之后才会运行,在哪里阻塞,再次被唤醒之后得到CPU执行权,就会在哪里继续运行。
    • 现在是4条线程,假设爸爸线程运行之后将 hasFood 改为true,此时爸爸线程就会唤醒其他线程,也就是妈妈线程和小明,大明线程都会被唤醒,如果此时妈妈线程获取到CPU时间片开始运行,判断 hasFood 为 true,那么就触发wait等待,等待之后就会释放CPU执行权,唤醒其他线程。
    • 如果此时爸爸线程又获取到CPU执行权,同样判断hasFood之后为true,就会进入等待,唤醒其他线程,如果此时CPU执行权又分配给了妈妈线程,因为之前已经经过了判断,就会在wait的地方,继续执行,就会触发给娃做饭,之后再唤醒其他线程。
    • 此时爸爸线程得到CPU时间片,则会在上次wait的地方继续执行,同样的给娃做饭,就会出现上图的效果,爸妈线程交替做饭。
    • image1
      image1

3.3 解决wait虚假唤醒

  • 解决3.2问题方案:
    • 将if替换为while,while语句块每次执行完之后都会重新判断,知道条件不成立才会结束循环,即可解决。
    public static class KitChenRoom {
        // 是否有吃的
        private boolean hasFood = false;
        // 设置同步锁,做饭和吃饭只能同时有一个在执行,不能边做边吃
        private final Object lock = new Object();
        // 做饭
        public void cook() {
            // 加锁
            synchronized (lock) {
                // 如果有吃的,就不做饭
                while(hasFood) {
                    // 还有吃的,先不做饭
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                // 否则就做饭,
                System.out.println(Thread.currentThread().getName() + "没吃的了,给娃做饭!");
                // 做好之后,修改为true
                hasFood = true;
                // 通知其他线程吃饭
                lock.notifyAll();
            }
        }
    
        // 吃饭
        public void eat() {
            synchronized (lock) {
                // 如果没吃的,就喊妈妈做饭,暂时吃不了
                while (!hasFood) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                // 否则就吃饭
                System.out.println(Thread.currentThread().getName() + "感谢老妈,恰饭,恰饭");
                // 吃完之后,修改为false
                hasFood = false;
                // 通知其他线程吃饭
                lock.notifyAll();
            }
        }
    }
  • 运行结果:发现做饭和吃饭交替执行
    小明妈妈线程:没吃的了,给娃做饭!
    小明自己线程:感谢老妈,恰饭,恰饭
    小明爸爸线程:没吃的了,给娃做饭!
    小明自己线程:感谢老妈,恰饭,恰饭
    小明妈妈线程:没吃的了,给娃做饭!
    小明自己线程:感谢老妈,恰饭,恰饭
    小明爸爸线程:没吃的了,给娃做饭!
    大明哥哥线程:感谢老妈,恰饭,恰饭
    小明妈妈线程:没吃的了,给娃做饭!
    大明哥哥线程:感谢老妈,恰饭,恰饭
    小明爸爸线程:没吃的了,给娃做饭!
    大明哥哥线程:感谢老妈,恰饭,恰饭
  • 为什么使用while就能解决呢?其实就是 if和while的区别
    • 由于在多线程内容中,有很多小伙伴犯迷,为什么用while就解决了,其实是思路没有打开,把以前学的东西都忘记了,满脑子都是多线程的东西,你说是不是!学习要融会贯通,将前后所有的知识点串起来。
  • 解决虚假唤醒非常简单,其实就是利用了while的特性,while体每次执行都会循环再次判断条件,直到条件不成立跳出循环,在这也是一样:
    • 妈妈线程执行发现hasFood = true,就进入等待,再次得到cpu时间片执行时,在哪里等待就在哪里醒来继续执行,也就是再lock.wait()的地方继续执行
    • 由于该代码在while循环中,会循环判断,如果hasFood = true继续wait,如果hasFood = false就跳出循环,执行循环体之外的代码
    • 但是如果是if,就只会判断一次,醒来之后不会再次判断,因为lock.wait()代码已经执行过了,会直接向下执行,开始给娃做饭

3.4 替换notify出现死锁

  • 上边我们使用notifyAll唤醒了所有线程,如果将notifyAll替换为notify会发生什么?
    public static class KitChenRoom {
        // 是否有吃的
        private boolean hasFood = false;
        // 设置同步锁,做饭和吃饭只能同时有一个在执行,不能边做边吃
        private final Object lock = new Object();
        // 做饭
        public void cook() {
            // 加锁
            synchronized (lock) {
                // 如果有吃的,就不做饭
                while(hasFood) {
                    // 还有吃的,先不做饭
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                // 否则就做饭,
                System.out.println(Thread.currentThread().getName() + "没吃的了,给娃做饭!");
                // 做好之后,修改为true
                hasFood = true;
                // 通知其他线程吃饭
                lock.notify();
            }
        }
    
        // 吃饭
        public void eat() {
            synchronized (lock) {
                // 如果没吃的,就喊妈妈做饭,暂时吃不了
                while (!hasFood) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                // 否则就吃饭
                System.out.println(Thread.currentThread().getName() + "感谢老妈,恰饭,恰饭");
                // 吃完之后,修改为false
                hasFood = false;
                // 通知其他线程吃饭
                lock.notify();
            }
        }
    }
  • 运行结果:多运行几次,发现程序卡住不动,产生死锁。
    小明妈妈线程:没吃的了,给娃做饭!
    小明自己线程:感谢老妈,恰饭,恰饭
    小明妈妈线程:没吃的了,给娃做饭!
    大明哥哥线程:感谢老妈,恰饭,恰饭

3.5 搞清楚锁池和等待池

  • 在解释3.4案例使用notify导致死锁这个原因之前,先搞清楚 锁池 和 等待池 两个概念:
    • 锁池:假设线程A已经拥有了某个对象的锁【注意:不是类】,而其它的线程想要调用这个对象的某个synchronized方法【或者synchronized块】,由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
    • 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,之后进入到了该对象的等待池中。
  • 对象锁:任何一个对象都可以被当做锁,所以称为对象锁,比如下方代码lock1和lock2就是两把对象锁,都有自己独立的锁池和等待池。
    • 调用 lock1.wait() 就是该线程进入到lock1对象锁的等待池中
    • lock1.notify()就是唤醒lock1对象锁的等待池中的随机一个等待线程,lock1.notifyAll(); 就是唤醒该等待池中所有等待线程
    • lock1的锁池和等待池与lock2是独立的,互不影响,并不会唤醒彼此等待池中的线程

3.6 notify和notifyAll

  • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
  • 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
  • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

3.7 notify死锁解决方法

  • 使用notify:notify方法只能唤醒一个线程,其它等待的线程仍然处于wait状态。
    • 假设调用cook方法的线程执行完后,所有的线程都处于等待状态,此时又执行了notify方法,这时如果唤醒的仍然是一个调用cook方法的线程【比如爸爸线程 将 妈妈线程唤醒】,那么while循环等于true,则此唤醒的线程【妈妈线程】就会调用wait方法,也会处于等待状态,而且没有唤醒其他线程,此时所有的线程都处于等待状态,就发生了死锁。
  • 使用notifyAll:可以唤醒所有正在等待该锁的线程。
    • 那么所有的线程都会处于运行前的准备状态(就是cook方法执行完后,唤醒了所有等待该锁的线程),那么此时,即使再次唤醒一个调用cook方法的线程,while循环等于true,唤醒的线程再次处于等待状态,那么还会有其它的线程可以获得锁,进入运行状态。
  • 解决wait死锁的两种方案:
    • 通过调用notifyAll唤醒所有等待线程。这种方式可以看3.1 wait和notifyAll实践
    • 调用 wait(long timeout) 重载方法,设置等待超时时长,在指定时间内还没被唤醒则自动醒来。经过下面案例测试,运行结果:运行三次发现,第一次程序陷入了两次等待2秒之后程序继续执行,这就是超时自动唤醒,避免了死锁。
    public static class KitChenRoom {
        // 是否有吃的
        private boolean hasFood = false;
        // 设置同步锁,做饭和吃饭只能同时有一个在执行,不能边做边吃
        private final Object lock = new Object();
        // 做饭
        public void cook() {
            // 加锁
            synchronized (lock) {
                // 如果有吃的,就不做饭
                while(hasFood) {
                    // 还有吃的,先不做饭
                    try {
                        lock.wait(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                // 否则就做饭,
                System.out.println(Thread.currentThread().getName() + "没吃的了,给娃做饭!");
                // 做好之后,修改为true
                hasFood = true;
                // 通知其他线程吃饭
                lock.notify();
            }
        }
    
        // 吃饭
        public void eat() {
            synchronized (lock) {
                // 如果没吃的,就喊妈妈做饭,暂时吃不了
                while (!hasFood) {
                    try {
                        lock.wait(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                // 否则就吃饭
                System.out.println(Thread.currentThread().getName() + "感谢老妈,恰饭,恰饭");
                // 吃完之后,修改为false
                hasFood = false;
                // 通知其他线程吃饭
                lock.notify();
            }
        }
    }
  • notify死锁解决方法总结一下
    • notify方法很容易引起死锁,除非你根据自己的程序设计,确定不会发生死锁,notifyAll方法则是线程的安全唤醒方法。

02.Condition实现等待/通知

2.1 Condition简单介绍

2.2 Condition实现方式

  • 特殊之处:synchronized相当于整个ReentrantLock对象只有一个单一的Condition对象情况。而一个ReentrantLock却可以拥有多个Condition对象,来实现通知部分线程。
  • 具体实现方式:
    • 假设有两个Condition对象:ConditionA和ConditionB。那么由ConditionA.await()方法进入等待状态的线程,由ConditionA.signalAll()通知唤醒;由ConditionB.await()方法进入等待状态的线程,由ConditionB.signalAll()通知唤醒。篇幅有限,代码示例就不写了。
贡献者: yangchong211
上一篇
5.1线程的前世今生探索
下一篇
5.3线程监控和Debug设计