【Java并发】等待/通知机制详解

转载请注明出处https://blog.csdn.net/fury97/article/details/81336047

目录

等待/通知机制

同步队列

示例

举例说明

应用场景

等待/通知的经典范式

Thread.join() 



等待/通知机制

当一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,最终执行的又是另一个线程。 
前者是生产者,后者是消费者。

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()方法或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,继续执行后续的操作。上述两个线程通过对象O来进行交互,对象O的wait()和notify()就像开关信号一样。


在继续介绍之前,我们先了解一下使用synchronized关键字时,对象,监视器,同步队列和执行线程之间的关系。

 

同步队列

【Java并发】等待/通知机制详解

synchronized关键字对对象的获取锁与释放锁,实质上是使用了monitorenter和monitorexit指令,本质是对于一个对象的监视器(monitor)的获取与释放,这个获取是排他的,也就意味着同一时刻只能有一个线程获取到对象的监视器。

由图我们可以看到这样的过程:

  1. 线程访问对象,首先使用monitor.enter来获取对象监视器。
  2. 如果获取成功,则线程继续执行。
  3. 如果获取失败,说明已有其他线程获得了该对象的锁,则该线程进入同步队列,线程状态变为BLOCKED(阻塞)。
  4. 当之前已获得该对象的锁的线程释放锁时,该释放操作会唤醒阻塞在同步队列中的线程,使其继续尝试获取该对象的监视器。

了解了同步队列之后,我们进入正题,看一个等待/通知的示例。

 

示例

【Java并发】等待/通知机制详解

由图我们可以看到这样的过程:

  1. WaitThread首先Monitor.Enter获取对象的监视器(获取锁),获取成功后,调用对象的wait()方法,进入了等待队列WaitQueue,进入等待状态,同时释放了锁(这个很关键,调用wait的同时会释放锁)。
  2. NotifyThread之前因为获取锁失败,进入了同步队列阻塞中,由于WaitThread释放了锁,NotifyThread自然获得了对象的锁,并继续执行,调用了对象的notify()方法。
  3. WaitQueue等待队列中的WaitThread收到了notify信号,从WaitQueue等待队列迁移至同步队列,WaitThread从等待状态进入阻塞状态。
  4. NotifyThread执行完毕,释放了锁,由于WaitThread在同步队列中,自然就获得了锁,然后继续执行。

 

举例说明

我们举一个浅显易懂的例子: 
1. 有一家餐厅专门做外卖,这家餐厅有一个取餐窗口(同步队列)和一个取餐等待区(等待队列),取餐窗口只有一个服务员(对象的锁),同时只能接待一个外卖小哥(线程)。 
2. 外卖小哥一到餐厅,如果没有人排队,就直接去窗口办事,如果有人,就排队等待服务员接待。 
3. 服务员接待到外卖小哥时,外卖小哥首先询问他的餐是否已做好,如果做好,则直接拿走,如果没有做好,则被安排到等待区休息(wait)。 
4. 等待区有很多的外卖小哥在等餐,餐好了会被叫号(notify),直接去窗口继续排队领餐。 
5. 每个外卖小哥领完餐,就会离开窗口(释放锁),下一个外卖小哥进入窗口领餐(获得锁)。


不知道大家看了这个例子以后,能搞清楚了吗~

 

应用场景

当生产速度跟不上消费速度的时候,可以很大程度的提升效率。 
例如上述例子中,如果外卖小哥A的饭没做好,那么他一直在窗口等的话,后面的全部都要排队等待,即使后面的小哥B餐好了,也要等待小哥A拿到餐以后才可以进入窗口领餐。如果使用等待/通知机制来处理的话,小哥A的没好,那么就先让他去旁边等,先处理后面小哥B、C的事情,当小哥A的餐好的时候再叫他来拿,极大程度的提升了效率,减少了平均等待时间

由此,我们可以总结出等待/通知的经典范式。

 

等待/通知的经典范式

等待方(消费者):

  1. 获取对象的锁。
  2. 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件是否满足。
  3. 条件满足则执行对应的逻辑。

通知方(生产者): 
1. 获取对象的锁。 
2. 改变条件。 
3. 通知等待在对象上的线程notify() /通知所有等待在对象上的线程。

 


Thread.join() 

Java中的Thread.join()方法的源码中,也是采用的等待/通知机制,当线程A中调用线程B.join(),线程A会等待线程B执行完的通知后再继续执行。

 

个人学习总结,有不对的地方欢迎指正。

参考资料:《Java并发编程的艺术》。