Java中的wait/notify/notifyAll可用来实现线程间通讯,是Object类的方法,这三个方法都是native方法,是平台相关的,经常使用来实现生产者/消费者模式。先来咱们来看下相关定义:html
wait() :调用该方法的线程进入WATTING状态,只有等待另外线程的通知或中断才会返回,调用wait()方法后,会释放对象的锁。编程
wait(long):超时等待最多long毫秒,若是没有通知就超时返回。安全
notify() : 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁。并发
notifyAll():通知全部等待在该对象上的线程。jvm
咱们来模拟个简单的例子来讲明,咱们楼下有个小小的饺子馆,生意火爆,店里有一个厨师,一个服务员,为避免厨师每作好一份,服务员端出去一份,效率过低且浪费体力。现假设厨师每作好10份,服务员就用一个大木盘子端给客户,天天卖够100份就打烊收工,厨师服务员各自回家休息。ide
思考一下,要实现该功能,若是不使用等待/通知机制,那么最直接的方式可能就是,服务员隔一段时间去厨房看看,满10份就用盘子端出去。这种方式有两个很大的弊病:工具
1.若是服务员去厨房看的太勤快,服务员太累了,这样还不如每作一碗就端一碗给客人,大木盘子的做用就体现不出来了。具体表如今实现代码层面就是:须要不断的循环,浪费处理器资源。spa
2.若是服务员隔好久才去厨房看一下,就没法确保及时性了,可能厨师早都作够10份了,服务员却没观察到。线程
针对上面这个例子,使用等待/通知机制就合理的多了,厨师每作够10份,就喊一声“饺子好了,能够端走啦”。服务员收到通知,就去厨房将饺子端给客人;厨师还没作够,即还没收到厨师的通知,就能够稍微休息下,但也得竖起耳朵等候厨师的通知。 code
1 package ConcurrentTest; 2 3 import thread.BlockQueue; 4 5 /** 6 * Created by chengxiao on 2017/6/17. 7 */ 8 public class JiaoziDemo { 9 //建立个共享对象作监视器用 10 private static Object obj = new Object(); 11 //大木盘子,一盘最多可盛10份饺子,厨师作满10份,服务员就能够端出去了。 12 private static Integer platter = 0; 13 //卖出的饺子总量,卖够100份就打烊收工 14 private static Integer count = 0; 15 16 /** 17 * 厨师 18 */ 19 static class Cook implements Runnable{ 20 @Override 21 public void run() { 22 while(count<100){ 23 synchronized (obj){ 24 while (platter<10){ 25 platter++; 26 } 27 //通知服务员饺子好了,能够端走了 28 obj.notify(); 29 System.out.println(Thread.currentThread().getName()+"--饺子好啦,厨师休息会儿"); 30 } 31 try { 32 //线程睡一会,帮助服务员线程抢到对象锁 33 Thread.sleep(100); 34 } catch (InterruptedException e) { 35 e.printStackTrace(); 36 } 37 } 38 System.out.println(Thread.currentThread().getName()+"--打烊收工,厨师回家"); 39 } 40 } 41 42 /** 43 * 服务员 44 */ 45 static class Waiter implements Runnable{ 46 @Override 47 public void run() { 48 while(count<100){ 49 synchronized (obj){ 50 //厨师作够10份了,就能够端出去了 51 while(platter < 10){ 52 try { 53 System.out.println(Thread.currentThread().getName()+"--饺子还没好,等待厨师通知..."); 54 obj.wait(); 55 BlockQueue 56 } catch (InterruptedException e) { 57 e.printStackTrace(); 58 } 59 } 60 //饺子端给客人了,盘子清空 61 platter-=10; 62 //又卖出去10份。 63 count+=10; 64 System.out.println(Thread.currentThread().getName()+"--服务员把饺子端给客人了"); 65 } 66 } 67 System.out.println(Thread.currentThread().getName()+"--打烊收工,服务员回家"); 68 69 } 70 } 71 public static void main(String []args){ 72 Thread cookThread = new Thread(new Cook(),"cookThread"); 73 Thread waiterThread = new Thread(new Waiter(),"waiterThread"); 74 cookThread.start(); 75 waiterThread.start(); 76 } 77 }
运行结果
cookThread--饺子好啦,厨师休息会儿 waiterThread--服务员把饺子端给客人了 waiterThread--饺子还没好,等待厨师通知... cookThread--饺子好啦,厨师休息会儿 waiterThread--服务员把饺子端给客人了 waiterThread--饺子还没好,等待厨师通知... cookThread--饺子好啦,厨师休息会儿 waiterThread--服务员把饺子端给客人了 waiterThread--饺子还没好,等待厨师通知... cookThread--饺子好啦,厨师休息会儿 waiterThread--服务员把饺子端给客人了 waiterThread--饺子还没好,等待厨师通知... cookThread--饺子好啦,厨师休息会儿 waiterThread--服务员把饺子端给客人了 waiterThread--饺子还没好,等待厨师通知... cookThread--饺子好啦,厨师休息会儿 waiterThread--服务员把饺子端给客人了 waiterThread--饺子还没好,等待厨师通知... cookThread--饺子好啦,厨师休息会儿 waiterThread--服务员把饺子端给客人了 waiterThread--饺子还没好,等待厨师通知... cookThread--饺子好啦,厨师休息会儿 waiterThread--服务员把饺子端给客人了 waiterThread--饺子还没好,等待厨师通知... cookThread--饺子好啦,厨师休息会儿 waiterThread--服务员把饺子端给客人了 waiterThread--饺子还没好,等待厨师通知... cookThread--饺子好啦,厨师休息会儿 waiterThread--服务员把饺子端给客人了 waiterThread--打烊收工,服务员回家 cookThread--打烊收工,厨师回家
借用《并发编程的艺术》中的一张图来了解下wait/notify的运行机制
可能有人会对所谓监视器(monitor),对象锁(lock)不甚了解,在此简单解释下:
jvm为每个对象和类都关联一个锁,锁住了一个对象,就是得到了对象相关联的监视器。
只有获取到对象锁,才能拿到监视器,若是获取锁失败了,那么线程就会进入阻塞队列中;若是成功拿到对象锁,也可使用wait()方法,在监视器上等待,此时会释放锁,并进入等地队列中。
关于锁和监视器的区别,园子里有个哥们的文章写得很详细透彻,在此引用一下,有兴趣的童鞋能够了解一下锁和监视器之间的区别 - Java并发
根据上面的图咱们来理一下具体的过程
1.首先,waitThread获取对象锁,而后调用wait()方法,此时,wait线程会放弃对象锁,同时进入对象的等待队列WaitQueue中;
2.notifyThread线程抢占到对象锁,执行一些操做后,调用notify()方法,此时会将等待线程waitThread从等待队列WaitQueue中移到同步队列SynchronizedQueue中,waitThread由waitting状态变为blocked状态。须要注意的时,notifyThread此时并不会当即释放锁,它继续运行,把本身剩余的事儿干完以后才会释放锁;
3.waitThread再次获取到对象锁,从wait()方法返回继续执行后续的操做;
4.一个基于等待/通知机制的线程间通讯的过程结束。
至于notifyAll则是在第二步中将等待队列中的全部线程移到同步队列中去。
在使用wait/notify/notifyAll时有一些特别留意的,在此再总结一下:
1.必定在synchronized中使用wait()/notify()/notifyAll(),也就是说必定要先获取锁,这个前面咱们讲过,由于只有加锁后,才能得到监视器。不然jvm也会抛出IllegalMonitorStateException异常。
2.使用wait()时,判断线程是否进入wait状态的条件必定要使用while而不要使用if,由于等待线程可能会被错误地唤醒,因此应该使用while循环在等待前等待后都检查唤醒条件是否被知足,保证安全性。
3.notify()或notifyAll()方法调用后,线程不会当即释放锁。调用只会将wait中的线程从等待队列移到同步队列,也就是线程状态从waitting变为blocked;
4.从wait()方法返回的前提是线程从新得到了调用对象的锁。
关于wait/notify的相关内容就介绍到此,在实际使用中,要特别留意上文中提到的几点,不过通常状况下,咱们直接使用wait/notify/notifyAll去完成线程间通讯,生产者/消费者模型的机会很少,由于Java并发包中已经提供了不少优秀精妙的工具,像各类BlockingQueue等等,后面有机会也会详细介绍的。
共勉