多线程使用notify和wait进行线程间通信丢失信号的问题
2017-11-16 00:00
453 查看
1.notify,wait和notifyAll
一个线程一旦调用了任意对象的wait()方法,就会变为非运行状态,直到另一个线程调用了同一个对象的notify()方法。为了调用wait()或者notify(),线程必须先获得那个对象的锁。也就是说,线程必须在同步块里调用wait()或者notify()public class MonitorObject{ } public class MyWaitNotify{ MonitorObject myMonitorObject = new MonitorObject(); public void doWait(){ synchronized(myMonitorObject){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } } public void doNotify(){ synchronized(myMonitorObject){ myMonitorObject.notify(); } } }
等待线程将调用doWait(),而唤醒线程将调用doNotify()。当一个线程调用一个对象的notify()方法,正在等待该对象的所有线程中将有一个线程被唤醒并允许执行(校注:这个将被唤醒的线程是随机的,不可以指定唤醒哪个线程)。同时也提供了一个notifyAll()方法来唤醒正在等待一个给定对象的所有线程。
如你所见,不管是等待线程还是唤醒线程都在同步块里调用wait()和notify()。这是强制性的!一个线程如果没有持有对象锁,将不能调用wait(),notify()或者notifyAll()。否则,会抛出IllegalMonitorStateException异常。
(校注:JVM是这么实现的,当你调用wait时候它首先要检查下当前线程是否是锁的拥有者,不是则抛出IllegalMonitorStateExcept,参考JVM源码的 1422行。)
一旦线程调用了wait()方法,它就释放了所持有的监视器对象上的锁。这将允许其他线程也可以调用wait()或者notify()。
一旦一个线程被唤醒,不能立刻就退出wait()的方法调用,直到调用notify()的线程退出了它自己的同步块。换句话说:被唤醒的线程必须重新获得监视器对象的锁,才可以退出wait()的方法调用,因为wait方法调用运行在同步块里面。如果多个线程被notifyAll()唤醒,那么在同一时刻将只有一个线程可以退出wait()方法,因为每个线程在退出wait()前必须获得监视器对象的锁。
2.丢失的信号
如果一个线程先于被通知线程调用wait()前调用了notify(),等待的线程将错过这个信号。这可能是也可能不是个问题。不过,在某些情况下,这可能使等待线程永远在等待,不再醒来,因为线程错过了唤醒信号。为了避免丢失信号量的问题,可以加一个boolean型的变量作为是否调用唤醒方法的标记。
public class MyWaitNotify2{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ if(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }
留意doNotify()方法在调用notify()前把wasSignalled变量设为true。同时,留意doWait()方法在调用wait()前会检查wasSignalled变量。事实上,如果没有信号在前一次doWait()调用和这次doWait()调用之间的时间段里被接收到,它将只调用wait()。
3.假唤醒
假唤醒就是在没有线程调用notify或notifyAll方法的情况下被唤醒。为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里。这样的一个while循环叫做自旋锁(校注:这种做法要慎重,目前的JVM实现自旋会消耗CPU,如果长时间不调用doNotify方法,doWait方法会一直自旋,CPU会消耗太大)。被唤醒的线程会自旋直到自旋锁(while循环)里的条件变为false。
public class MyWaitNotify3{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ while(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }
留意wait()方法是在while循环里,而不在if表达式里。如果等待线程没有收到信号就唤醒,wasSignalled变量将变为false,while循环会再执行一次,促使醒来的线程回到等待状态。
相关文章推荐
- Qt多线程程序设计中,可使用信号和槽进行线程通信
- java 通过使用wait和notify进行线程之间通信(代码)
- JavaSE第一百零四讲:哲学家就餐问题、死锁与使用wait及notify方法实现线程之间的相互通信
- 多线程(银行存款)使用notify()和wait()线程通信实现交替存款
- 多线程一共就俩问题:1.线程安全(访问共享数据) 2.线程通信(wait(),notify())
- Java使用notify()、wait()、和notifyAll()来进行线程间的通信
- java线程基础巩固---线程间通信快速入门,使用wait和notify进行线程间的数据通信
- Qt多线程编程(4)——使用线程中自定义的信号和槽进行通信
- 多线程并发之线程间的通信,notify,wait
- Java 多线程 线程间的通信——wait及notify方法
- java多线程-线程间通信-示例代码-解决安全问题-等待唤醒机制wait()notify()notifyAll()
- 【Java并发编程】之十:使用wait/notify/notifyAll实现线程间通信的几点重要说明
- 【郭林专刊】多线程知识总结线程通信,wait()与notify()的运用
- 多线程总结(五)线程间的通信——wait及notify方法
- [原]Java多线程编程学习笔记之九:使用wait/notify/notifyAll实现线程间通信的几点说明
- Java多线程通信-利用传统的线程通信wait(),notify()方法实现“生产者消费者模式”
- Java【多线程知识总结(10)】线程通信之wait()与notify()的运用--模拟指挥官指挥2个连队交替轰炸战区<另外的写法>
- Java核心知识点学习----多线程并发之线程间的通信,notify,wait
- Java【多线程知识总结(8)】线程通信,wait()与notify()的运用
- Java 多线程(七) 线程间的通信——wait及notify方法