深入理解Java并发框架AQS系列(五):条件队列(Condition)

深入理解Java并发框架AQS系列(一):线程
深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念
深入理解Java并发框架AQS系列(三):独占锁(Exclusive Lock)
深入理解Java并发框架AQS系列(四):共享锁(Shared Lock)
深入理解Java并发框架AQS系列(五):条件队列(Condition)

一、前言

AQS中的条件队列相比较前文中的“独占锁”、“共享锁”等比较独立,即便没有条件队列也丝毫不影响诸如ReentrantLock、Semaphore类的实现,那如此说来条件队列是否就是一个可有可无的产物?答案是否定的,我们来看下直接或间接用到条件队列的JDK并发类:

ReentrantLock 独占锁经典类

ReentrantReadWriteLock 读写锁

ArrayBlockingQueue 基于数组的阻塞队列

CyclicBarrier 循环栅栏,解决线程同步问题

DelayQueue 延时队列

LinkedBlockingDeque 双向阻塞队列

PriorityBlockingQueue 支持优先级的无界阻塞队列

ThreadPoolExecutor 线程池构造器

ScheduledThreadPoolExecutor 可基于时间调度的线程池构造器

StampedLock 邮戳锁,1.8后引入,更高效的读写锁

如此豪华的阵容,可见Condition的地位不可小觑

我们简单描述下条件队列实现的功能:有3个线程A、B、C,分别调用wait/await方法后,线程进入阻塞,在没有其他线程去唤醒的情况下,3个线程将永远处于阻塞状态。此时如果有另外线程调用notify/signal,那么A、B、C线程中的某一个将被激活(根据其进入条件队列的顺序而定),从而执行后续的逻辑;如果调用notifyAll/signalAll的话,那么3个线程都将被激活,这可能是我们对条件队列的简单认识。这样的描述是否准确呢?可能不太严谨,我们引入JDK的条件队列来做说明

统一话术:其实语法层面支持的wait/notify与AQS都属于JDK的范畴,但为了区分两者,我们定义如下:

JDK条件队列:语法层面提供支持的wait/notify,即Object类中的wait()/notify()方法

AQS条件队列:AQS提供的条件队列,即AQS内部的ConditionObject类

二、JDK中的条件队列(wait/notify)

众所周知,在JDK中,wait/notify/notifyAll是根对象Object中内置的方法,且方法均被定义为native本地方法

// 等待 public final native void wait(long timeout) throws InterruptedException; // 唤醒 public final native void notify(); // 唤醒所有等待线程 public final native void notifyAll(); 2.1、wait // 步骤1 synchronized (obj) { // 步骤2 before(); // 步骤3 obj.wait(); // 步骤4 after(); }

相信大家对上述代码并不陌生,我们将JDK的条件队列抽象为4步,逐一阐述

步骤1: synchronized (obj)

在jdk中如果想调用Object.wait()方法,必须首先获取该对象的synchronized锁,当前步骤,如果成功获取到锁,那么将进入“步骤2”,如果存在并发,当前线程将会进入阻塞(线程状态为BLOCKED),知道获取到锁为止

步骤2: before()

我们知道synchronized是独占锁,所以在执行步骤2代码时,程序是不存在并发的,即同一时刻,只有一个线程正在执行,此处也相对好理解

步骤3: obj.wait()

此步骤是将当前线程放入条件队列,同时释放obj的同步锁。此处跟我们对synchronized的认知有悖,我们一般认为synchronized (obj) {......}在大括号中的代码会一直持有锁,而事实情况却是,当程序执行wait()方法时,会释放obj的同步锁

步骤4: after()

此步骤是并发执行还是串行执行?假设我们现在有3个线程A、B、C都已经执行完毕wait()方法,并进入了条件队列,等待其他线程唤醒;此时另外一个线程执行了notifyAll()时,后续的激活流程是怎么样的?

错误观点:有很多同学直观感受是,线程A、B、C同时被激活,所以步骤4是并发执行的;就像是百米赛跑,所有同学都准备就绪(wait),一声枪响后(notifyAll),所有人开始赛跑,并跑到终点(步骤4)

正确观点:其实“步骤4”是串行执行的,大家再检查下代码后便可发现,“步骤4”处于synchronized的大括号之间;还是拿上述赛跑举例,如果认为从听到枪响至跑到终点是“步骤4”的话,那真实的场景应该是这样的:一声枪响后,A起跑,B、C原地不动;A跑到终点后,B开始起跑,C原地不动;最后是C跑到终点

由此我们断定,obj.wait()虽然是native方法,但其内部经历了释放锁、重新抢锁的两个大环节

2.2、notify synchronized (obj) { obj.notify(); // obj.notifyAll(); }

所有因obj.wait()阻塞的线程,都要通过notify来唤醒

notify() 唤醒条件队列中,队首节点

notifyAll() 唤醒条件队列中所有节点

三、AQS中的条件队列(await/signal)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwjwdw.html