JUC中的AQS原理详解:从背景到源码
JUC中的AQS原理详解:从背景到源码
AQS(AbstractQueuedSynchronizer)是Java并发包(JUC)中的核心组件,用于实现锁和其他同步器组件。本文将从背景、概念、作用等多个维度深入解析AQS的工作原理,帮助读者理解其在并发编程中的重要地位和应用场景。
0、背景
在多线程编程中,我们经常需要对共享资源进行加锁和解锁操作。例如,以下是一个常见的加锁解锁代码:
Lock lock = new ReentrantLock();
lock.lock();
try{
//do Something
} finally{
lock.unlock();
}
当多个线程同时尝试获取锁时,抢不到锁的线程会被分配到哪里?它们是如何排队等待的?队列底层又是如何维护的?这些问题的答案都与AQS密切相关。
1、AQS介绍
AQS,即AbstractQueuedSynchronizer,是JUC中的基石组件,类似于JVM之于Java。AQS主要用于实现锁或其他同步器组件的公共基础部分,是一个重量级的基础框架,主要用于解决锁分配给"谁"的问题。
AQS相关的类包括:
- AbstractOwnableSynchronizer(下面两兄弟的父类)
- AbstractQueuedLongSynchronizer:自JDK 1.6起引入
- AbstractQueuedSynchronizer:简称AQS,自JDK 1.5起引入
这些类都是抽象类,完成了一部分逻辑,剩余部分定义了方法规范,供子类继承和重写。
2、AQS核心概念
AQS的核心概念可以概括为"先进先出的一个队列 + state状态值"。可以将其类比为银行办理业务的场景:
- 当有人开始办理业务时,指示灯变红(state=1,表示有人占用资源),其余人去候客区的椅子上等待。
- 这个候客区就是一个抽象的FIFO队列,通过一个int类型的变量表示持有锁的状态。
- 这个队列被称为CLH队列,是Craig、Landin和Hagersten三人提出的单向链表的变体,但在AQS中实现为虚拟的双向队列FIFO。
3、AQS是JUC的基石
AQS之所以成为JUC的基石,是因为JUC中的许多组件都是基于AQS实现的。例如,ReentrantLock、Semaphore、CountDownLatch等都依赖于AQS来实现同步控制。
4、锁和同步器的关系
在并发编程中,锁和同步器扮演着不同的角色:
- 锁:面向普通开发者,提供了lock、unlock等API,隐藏了实现细节。
- 同步器:面向锁的实现者,提供了统一的规范和简化了锁的实现,屏蔽了同步状态管理、同步队列的管理和维护、阻塞线程排队和通知唤醒机制等公共的底层细节。
5、AQS的作用
AQS的主要作用是处理线程的阻塞和唤醒机制。当共享资源被占用时,需要一定的阻塞、唤醒机制来保证锁的分配。这个机制主要通过CLH队列的变体实现,将暂时获取不到锁的线程加入到队列中。
具体来说,AQS使用以下机制实现同步效果:
- 一个volatile的int类型的成员变量state来表示同步状态
- 内置的FIFO队列来完成资源获取的排队工作
- 将每个请求共享资源的线程封装成队列的结点对象Node
- 通过CAS自旋和LockSupport.park()的方式维护state变量的状态
6、公平锁与非公平锁
基于AQS框架开发的锁,其公平性主要体现在新线程如何参与锁的竞争:
- 非公平锁:新来的线程与队列中的线程共同抢锁,通过CAS竞争state值。
- 公平锁:新来的线程排到FIFO队列的末尾,只让队列中的head线程获得锁。
7、state和CLH队列
AQS类中包含一个int类型的成员变量state,用于表示同步状态:
private volatile int state;
这个state变量类似于银行办理业务的受理窗口状态:0表示没人占用,大于等于1表示有人占用。而CLH队列就是候客区,队列中装的是一个个Node内部类的对象。
总结来说,AQS的实质就是一个CLH的双端队列加上一个state变量,队列中排队的每个个体就是一个Node。
8、AQS的内部类Node
AQS的内部类Node用于表示队列中的每个节点,其成员变量含义如下:
static final class Node {
/** 共享模式 */
static final Node SHARED = new Node();
/** 独占模式 */
static final Node EXCLUSIVE = null;
/** 线程被取消了 */
static final int CANCELLED = 1;
/** 后继线程需要被唤醒 */
static final int SIGNAL = -1;
/** 等待condition唤醒 */
static final int CONDITION = -2;
/** 共享式同步状态获取将会无条件地传播下去 */
static final int PROPAGATE = -3;
/** 等待状态 */
volatile int waitStatus;
/** 前置节点 */
volatile Node prev;
/** 后置节点 */
volatile Node next;
/** 坐在Node里的线程 */
volatile Thread thread;
// 构造方法、实例方法、静态代码块略
}
每个Node节点包含了线程的等待状态、前后节点引用以及线程本身的信息,通过这些信息实现线程的排队和唤醒机制。