摘要:如果有其它线程调用了相同对象的方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,从新争夺锁的拥有权。
存在即合理wait,notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视,而本文则是对这些关键字的使用进行描述。
在java中,每个对象都有两个池,锁池(monitor)和等待池(waitset),每个对象又都有wait、notify、notifyAll方法,使用它们可以实现线程之间的通信,只是平时用的较少。
wait(): 使当前线程处于等待状态,直到另外的线程调用notify或notifyAll将它唤醒
notify(): 唤醒该对象监听的其中一个线程(规则取决于JVM厂商,FILO,FIFO,随机...)
notifyAll(): 唤醒该对象监听的所有线程
锁池: 假设T1线程已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用该对象的synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前都需要先获得该对象的锁的拥有权,但是该对象的锁目前正被T1线程拥有,所以这些线程就进入了该对象的锁池中。
等待池: 假设T1线程调用了某个对象的wait()方法,T1线程就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前T1线程就已经拥有了该对象的锁),同时T1线程进入到了该对象的等待池中。如果有其它线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,从新争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.
注意事项在调用wait(), notify()或notifyAll()的时候,都必须获得某个对象(注意:不是类)的锁。
永远在循环(loop)里调用 wait 和 notify,而不是在 If 语句中
永远在synchronized的函数或对象里使用wait、notify和notifyAll,不然Java虚拟机会生成 IllegalMonitorStateException。
使用案例 - 生产消费private static int i = 0; static void product() {//生产者 System.out.println("P->" + (++i)); } static void consumer() {//消费者 System.out.println("C->" + i); } public static void main(String[] args) { new Thread(() -> { while (true) { product(); } }).start(); new Thread(() -> { while (true) { consumer(); } }).start(); } ////////////////////////日志//////////////////////// //P->1 //P->2 //P->3 //P->4 //C->1 ////////////////////////日志////////////////////////
分析: 从日志中可以看到数据会出现多次生产或多次消费的问题,因为在线程执行过程中,两个线程缺少协作关系,都是各干各的,T1线程只管生产数据,不管T2线程是否处理了。
改进方案 - 变量消息传递private static int i = 0; private static boolean isProduction = true; static void product() {//生产者 if (isProduction) { System.out.println("P->" + (++i)); isProduction = false; } } static void consumer() {//消费者 if (!isProduction) { System.out.println("C->" + i); isProduction = true; } }
分析: 这种情况下我们通过维护一个变量的方式,通知对方,但是效率及其差,线程频繁请求与判断大大的浪费了系统资源,虽然满足了当前要求,但并非是可选方案...
改进方案 - wait/notify上文已经介绍了使用wait和notify的前提了,接下来看案例
private final static byte[] LOCK = new byte[0];//定义一个锁对象 private static boolean isProduction = true;//消息投递 private static int i = 0;//生产的消息 static void product() { synchronized (LOCK) {// 必须是在 synchronized中 使用 wait/notify/notifyAll try { if (isProduction) {//如果标示位为生产状态,则继续生产 System.out.println("P->" + (++i)); isProduction = false; LOCK.notify();//消费者可以消费了 } else { LOCK.wait();//说明生产出来的数据还未被消费掉 } } catch (InterruptedException e) { e.printStackTrace(); } } } static void consumer() { try { synchronized (LOCK) { if (isProduction) {//如果当前还在生产,那么就暂停消费者线程 LOCK.wait(); } else { System.out.println("C->" + i); isProduction = true; LOCK.notify();//通知我已经消费完毕了 } } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { new Thread(() -> { while (true) { product(); } }).start(); new Thread(() -> { while (true) { consumer(); } }).start(); } ////////////////////////日志//////////////////////// //P->1 //C->1 //P->2 //C->2 //..... //P->354217 //C->354217 ////////////////////////日志////////////////////////
分析: 一切都是那么完美,在T1线程中,调用LOCK.wait()将当前线程移入等待池中,并交出执行权,锁池中的其他线程去竞争并取得锁的使用权(T2线程获取),当T2线程消费完毕后,调用LOCK.notify()方法通知当前对象锁等待池中的其中一个线程(因为这里notify是基于JVM算法而定,因为我们只有两个线程,所以T1线程会接收到T2线程发出的通知,从而继续生产数据。
问题: 虽然一对一没有问题,但假设多个生产者多个消费者的情况下怎么办呢?
BUG - 埋点public static void main(String[] args) { Stream.of("P1", "P2", "P3", "P4").forEach(name -> new Thread(() -> { while (true) { product(); } }, name).start()); Stream.of("C1", "C2").forEach(name -> new Thread(() -> { while (true) { consumer(); } }, name).start()); } ////////////////////////日志//////////////////////// //P2 -> 1 //C2 -> 1 //P2 -> 2 //C1 -> 2 //P3 -> 3 ////////////////////////日志////////////////////////
分析: 居然不执行了,借助前面说过的 死锁分析知识,我们看看是不是发生死锁了
结果表明,虽然没有Found one deadlock...字眼,但是可以看到有个线程都被wait住了,没有被释放,所以导致我们当前无法继续生产消费
解决方案 - notifyAllLOCK.notifyAll();//通知所有线程,我已经消费完毕了,你们继续生产
分析: 这里只修改了一句代码,就是将consumer方法中的notify -> notifyAll,由通知单个线程变成通知所有在等待池中的线程
P1 -> 1 C1 -> 1 P2 -> 2 C2 -> 2 ... P3 -> 42894 C1 -> 42894 ... P1 -> 42898 C1 -> 42898- 说点什么
全文代码:https://gitee.com/battcn/battcn-concurent/tree/master/Chapter1-1/battcn-thread/src/main/java/com/battcn/chapter7
个人QQ:1837307557
battcn开源群(适合新手):391619659
微信公众号:battcn(欢迎调戏)
喜大普奔,迎来了十一国庆节....
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/67636.html
摘要:文本将介绍两种可以优雅的终止线程的方式第一种在多线程模式中有一种叫两步终止的模式可以优雅的终止线程,这种模式采用了两个步骤来终止线程,所以叫两步终止模式。 Java中原来在Thread中提供了stop()方法来终止线程,但这个方法是不安全的,所以一般不建议使用。文本将介绍两种可以优雅的终止线程的方式... 第一种 在JAVA《Java多线程模式》中有一种叫Two-Phase Term...
摘要:造成当前线程在接到信号被中断或到达指定最后期限之前一直处于等待状态。该线程从等待方法返回前必须获得与相关的锁。如果线程已经获取了锁,则将唤醒条件队列的首节点。 一、写在前面 在前几篇我们聊了 AQS、CLH、ReentrantLock、ReentrantReadWriteLock等的原理以及其源码解读,具体参见专栏 《非学无以广才》 这章我们一起聊聊显示的Condition 对象。 ...
摘要:在前面的文章中介绍过观察者模式及并发编程的基础知识,为了让大家更好的了解观察者模式故而特意写了这篇番外概述在多线程下我们需要知道当前执行线程的状态是什么比如运行,关闭,异常等状态的通知,而且不仅仅是更新当前页面。 在前面的文章中介绍过 观察者模式 及 并发编程的基础知识,为了让大家更好的了解观察者模式故而特意写了这篇番外.. 概述 在Java多线程下,我们需要知道当前执行线程的状态是...
摘要:那并发里面的理论和模型是什么呢那便要从操作系统中解决并发问题的一种模型管程讲起了。当一个进程使用完管程后,它必须释放管程并唤醒等待管程的某一个进程。总结在并发编程领域,有两大核心问题互斥和同步,而这两个问题,管程模型都可以解决。 为什么需要了解管程 Java并发编程是Java中高级程序员必备的一项技能,但是真正学明白并发编程也并非易事。正如Java并发编程实践中的一句话编写正确的程序并...
摘要:一般差异简单来说,是一个用于线程同步的实例方法。暂停当前线程,不释放任何锁。用来线程间通信,使拥有该对象锁的线程等待直到指定时间或。执行对该对象加的同步代码块。 在JAVA的学习中,不少人会把sleep和wait搞混,认为都是做线程的等待,下面主要介绍下这俩者是什么,及了解它们之间的差异和相似之处。 一般差异 简单来说,wait()是一个用于线程同步的实例方法。因为定义在java.l...
阅读 3762·2021-10-08 10:05
阅读 2925·2021-09-27 13:57
阅读 2636·2019-08-29 11:32
阅读 988·2019-08-28 18:18
阅读 1257·2019-08-28 18:05
阅读 1946·2019-08-26 13:39
阅读 802·2019-08-26 11:37
阅读 2025·2019-08-26 10:37