并发编程—等待-通知

有上一篇文章我们知道,在破坏占用且等待条件的时候,如果两个资源有一个被占用后,用的是死循环的方式来循环等待,代码如下所示:

//死循环的方式 
while (!allocator.apply(this, tar)) ;

如果说apply()操作耗时非常短,而且并发冲突量不大时,可以使用这个方案。如果apply()操作非常耗时,或者并发冲突量非常大的时候,这种循环等待的方案就不适用了,因为这种场景下,可能要循环上万次才能获取到锁,相当耗CPU。

其实在这种场景下,做好的方案是:如果线程要求的条件不满足,则线程阻塞自己,进入等待状态;当线程需要的条件满足后,通知等待的线程重新执行。其中,使用线程阻塞的方式就能避免循环等待消耗CPU的问题。

Java中是通过 synchronized 关键字配合 wait()、notify()和notifyAll()这三个方法就能轻松实现。

如何使用synchronized实现互斥锁,想必你已经很熟悉了。如下图所示:

在并发程序中,当一个线程进入临界区后,由于某些条件不满足,需要进入等待状态,Java对象wait()方法就能满足这种需求。当调用了wait()方法后,当前线程就会被阻塞,并且进入右边的等待队列中,这个等待队列也是互斥锁的等待队列。线程在进入等待队列的同时,会释放持有的互斥锁,线程释放锁后,其他线程就有机会获得锁,并进入临界区了。

当线程需要的条件满足是,Java中的notify()和notifyAll()方法,就会通知阻塞队列中的线程高速他条件曾经满足过。因为notify()和notifyAll()只能保证在通知时间点,条件是满足的。而被通知线程的执行时间点和通知的时间点基本上不会重合,所有当线程执行的时候,可能有其他线程已经插队执行了,又是条件不满足了。

在使用 wait()、notify()和notifyAll()方法时要在synchronized代码块中,否则会报java.lang.IllegalMonitorStateException异常。

在这个等待-通知机制中,我们需要考虑一下四个要素:

  1. 互斥锁:等待-通知首先需要满足互斥
  2. 线程要求的条件
  3. 何时等待
  4. 何时通知

尽量使用notifyAll

因为notify()方法是随机地通知等待队列中的一个线程,而notifyAll()会通知等待队列中的所有线程。比如在多生产-多消费者模式中,如果使用notify()方法可能每次唤醒的都是 生产者或者消费者线程,这样可能会导致某一方始终得不到执行机会。

所以除非经过深思熟虑,否则尽量使用notifyAll()方法。

总结

等待-通知机制是一种非常普遍的线程间协作方式。Java 语言内置的 synchronized 配合 wait()、notify()、notifyAll() 这三个方法可以快速实现这种机制,但是它们的使用看上去还是有点复杂,所以你需要认真理解等待队列和 wait()、notify()、notifyAll() 的关系。最好用现实世界做个类比,这样有助于你的理解

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值