用“等待-通知”机制优化循环等待

本文介绍了如何使用等待-通知机制优化高并发场景下的循环等待问题,通过Java中的synchronized关键字结合wait()、notify()和notifyAll()方法实现线程间的协作。文中详细解释了等待队列和互斥锁的概念,并强调了notifyAll()优于notify()的原因,最后给出了一种资源分配器的优化示例。
摘要由CSDN通过智能技术生成

前言

上篇已经了解到,破坏占用且等待条件时如果两个锁不满足同时获取的条件下,就用死循环的方式来循环等待。

while(!actr.apply(this, target))
	;

如果apply操作耗时非常短,并发量冲突不大的情况下,是可以这样做的。但是在现在并发量大,耗时时间长的情况下就不适用了,因为可能需要循环上万次才能获取到锁,太消耗CPU了。

为了解决上面循环等待消耗CPU的问题,最好的方案是:如果线程要求的条件不满足,则线程阻塞自己,进入等待状态;当线程要求的条件满足后,通知等待的线程重新执行。这就需要支持等待-通知机制

完美的就医流程

现实中就医流程大致如下:
挂号——就诊门诊分诊——等待叫号——大夫就诊——去做检查——叫下一位患者——检查完拿检测报告重新分诊——等待叫号——大夫就诊

上述就是一个“等待-通知机制”的典型案例。

把上面示例类比到软件中:申请CPU执行权 – 线程获得CPU执行权 – 等待其他线程释放锁 – 拿到锁 – 检查发现不满足某种条件 – 释放锁 – 等待任务条件完成 – 等待CPU的执行权 – 重新获得锁 – 任务完成。

一个完整的等待-通知机制:线程首先获取互斥锁,当线程要求的条件不满足时,释放互斥锁,进入等待状态;当要求的条件满足时,通知等待的线程,重新获取互斥锁。

用synchronized实现等待-通知机制

Java语言中,可以通过synchronized配合wait(),notify(),notifyAll()这三个方法实现。

用synchronized实现互斥锁:同一时刻,只允许有一个线程进入到synchronized保护的临界区

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

等待队列和互斥锁是一对一的关系,每个互斥锁都有自己的等待队列。

在这里插入图片描述

线程要求的条件满足时,Java提供了notify()notifyAll()方法,当条件满足时调用notify()和notifyAll()方法,会通知等待队列(互斥锁的等待队列)中的线程,告诉它条件曾经满足过
在这里插入图片描述
需要注意的是:

  1. 条件曾经满足过:因为notify()只能保证在通知时间点条件是满足的。而被通知线程的执行时间点和通知时间点基本上不会重合,所以当线程执行时,很可能条件已经不满足了(保不准有其他线程插队)。
  2. 重新申请互斥锁:被通知的线程要想重新执行,仍然需要获取到互斥锁。因为曾经获取的锁在调用wait()时已经释放了。

** 我们直到,如果synchronized锁定的是this,那么对应的一定是this.wait(),this.notify(),this.notifyAll()。
如果锁定的是target,那么锁定的就是target.wait(),target.notify(),this.notifyAll()。**

** 而且如果调用wait(),notify(),notifyAll()方法被调用,前提是已经获取到了相应的互斥锁。所以wait(),notify(),notifyAll()必须在synchronized修饰的临界区被调用的,如果在临界区外部调用,或者锁定的是this,而调用target.wait()的话,JVM会抛出一个运行时异常:Java.lang.IllegalMonitorStateException**。

小试牛刀:一个更好地资源分配器

上篇的转账问题中,由于while(!apply())循环很消耗CPU,那就用等待-通知机制优化一下。

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

  1. 互斥锁:单例Allocator,可以用this作为互斥锁
  2. 线程要求的条件:必须转出/转入账户都没有被分配过
  3. 何时等待:线程不满足条件就等待
  4. 何时通知:线程满足条件就通知
class Allocator{
   
	
	private Allocator(){
   }

	private List<Object> als = new ArrayList<>();
	
	/** 申请资源 */
	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值