在前篇介绍死锁的文章中,咱们破坏等待占用且等待条件时,用了一个死循环来获取两个帐本对象。html
// 一次性申请转出帐户和转入帐户,直到成功 while(!actr.apply(this, target)) ;
咱们提到过,若是apply()操做耗时很是短,且并发冲突量也不大,这种方案仍是能够。不然的话,就可能要循环上万次才能够获取锁,这样的话就太消耗CPU了!java
因而咱们给出另外一个更好的解决方案,等待-通知机制:
如果线程要求的条件不知足,则线程阻塞本身,进入等待状态;当线程要求的条件知足时,通知等待的线程从新执行。编程
Java是支持这种等待-通知机制的,下面咱们就来详细介绍这个机制,并用这个机制来优化咱们的转帐流程。
咱们先经过一个就医流程来了解一个完善的“等待-通知”机制。多线程
在医院就医的流程基本是以下这样:并发
咱们将上述过程对应到线程的运行状况:app
一个完整的“等待—通知”机制以下:
线程首先获取互斥锁,当线程要求条件不知足时,释放互斥锁,进入等待状态;当条件知足时,通知等待的线程,从新获取锁。优化
必定要理解每个关键点,还须要注意,通知的时候虽然条件知足了,可是不表明该线程再次获取到锁时,条件仍是知足的。this
在Java中,等待—通知机制能够有多种实现,这里咱们讲解由synchronized
配合wait()
、notify()
或者notifyAll()
的实现。spa
当线程进入获取锁进入同步代码块后,如果条件不知足,咱们便调用wait()
方法使得当前线程被阻塞且释放锁。线程
上图中的等待队列和互斥锁是一一对应的,每一个互斥锁都有本身的独立的等待队列(等待队列是同一个)。(这句话还在暗示咱们后面唤醒线程时,是唤醒对应锁上的线程。)
当条件知足时,咱们调用notify()
或者notifyAll()
,通知等待队列(互斥锁的等待队列)中的线程,告诉它条件曾经知足过。
咱们要在相应的锁上使用wait() 、notify()和notifyAll()。
须要注意,这三个方法能够被调用的前提是咱们已经获取到了相应的互斥锁。因此,咱们会发现wait() 、notify() notifyAll()都是在synchronized{...}
内部中被调用的。若是在synchronized外部调用,JVM会抛出异常:java.lang.IllegalMonitorStateException。
咱们如今使用“等待—通知”机制来优化上篇的一直循环获取锁的方案。首先咱们要清楚以下以下四点:
使用“等待—通知”机制时,咱们通常会套用一个“范式”,能够看做是前人的经验总结用法。
while(条件不知足) { wait(); }
这个范式能够解决“条件曾将知足过”这个问题。由于当wait()返回时,条件已经发生变化,使用这种结构就能够检验条件是否还知足。
解决咱们的转帐问题:
class Allocator { private List<Object> als; // 一次性申请全部资源 synchronized void apply(Object from, Object to){ // 经典写法 while(als.contains(from) || als.contains(to)){ // from 或者 to帐户被其余线程拥有 try{ wait(); // 条件不知足时阻塞当前线程 }catch(Exception e){ } } als.add(from); als.add(to); } // 归还资源 synchronized void free( Object from, Object to){ als.remove(from); als.remove(to); notifyAll(); // 归还资源,唤醒其余全部线程 } }
sleep()
和wait()
均可以使线程阻塞,可是它们仍是有很大的区别:
wait()、notify()以及notifyAll()它们之间的联系是依靠互斥锁,也就同步锁(内置锁),咱们前面介绍过,每一个Java对象均可以用做一个实现同步的锁,因此这些方法是定义在Object中,而不是Thread中。
“等待—通知”机制是一种很是广泛的线程间协做的方式,咱们在理解时能够利用生活中的例子去相似,就如上面的就医流程。上文中没有明显说明notify()和notifyAll()的区别,只是在图中标注了一下。咱们建议尽可能使用notifyAll(),notify() 是会随机地通知等待队列中的一个线程,在极端状况下可能会使某个线程一直处于阻塞状态不能去竞争获取锁致使线程“饥饿”;而 notifyAll() 会通知等待队列中的全部线程,即全部等待的线程都有机会去获取锁的使用权。
参考: [1]极客时间专栏王宝令《Java并发编程实战》 [2]Brian Goetz.Tim Peierls. et al.Java并发编程实战[M].北京:机械工业出版社,2016 [3]skywang12345.Java多线程系列--“基础篇”05之 线程等待与唤醒.https://www.cnblogs.com/skywang12345/p/3479224.html