Java并发包原理剖析(一)AQS原理分析

AQS 简单介绍

AQS 的全称为(AbstractQueuedSynchronizer),这个类在 java.util.concurrent.locks 包下面。

AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask(jdk1.7) 等等皆是基于 AQS 的。当然,我们自己也能利用 AQS 非常轻松容易地构造出符合我们自己需求的同步器。

AQS原理

AQS原理概览

AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。

AQS(AbstractQueuedSynchronizer)原理图:

AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。

1
2
3
4
/**
* The synchronization state.
*/
private volatile int state; //共享变量,使用volatile修饰保证线程可见性

状态信息通过 protected 类型的getState,setState,compareAndSetState进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
* @return current state value
*/
protected final int getState() {
return state;
}

/**
* Sets the value of synchronization state.
* This operation has memory semantics of a {@code volatile} write.
* @param newState the new state value
*/
protected final void setState(int newState) {
state = newState;
}

/**
* Atomically sets synchronization state to the given updated
* value if the current state value equals the expected value.
* This operation has memory semantics of a {@code volatile} read
* and write.
* 原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that the actual
* value was not equal to the expected value.
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

AQS对资源的共享方式

独占方式 Exclusive

在独占方式下,获取与释放资源的流程如下:

  1. 当一个线程调用acquire(int arg)方法获取独占资源时,首先会使用tryAcruire方法尝试获取资源,具体是设置状态变量state得值,成功则直接放回,失败则将当前线程封装成Node.EXCLUSIVE的节点后插入到AQS阻塞队列的尾部,并调用LockSupport.park(this)方法挂起自己。
1
2
3
4
5
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
  1. 当一个线程调用relase(int arg) 方法时会尝试使用tryRelease操作释放资源,这里是设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryAcquire尝试,看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起。
1
2
3
4
5
6
7
8
9
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

需要注意的是,AQS类并没有提供可用的tryAcquire和tryRelease方法,需要由具体子类来实现。子类在实现tryAcquire和tryRelease时要根据具体场景使用CAS算法尝试修改state的值,成功返回true,否则返回false。子类还需要定义,在调用acquire和release方法时state状态值的增减代表什么含义。

比如继承自AQS实现的独占锁ReentrantLock,定义当state为0时表示锁空闲,为1时表示锁已经被占用。在重写tryAcquire时,在内部需要使用CAS算法查看当前state是否为0,如果为0则设置为1,并设置当前锁的持有者为当前线程,而后返回true,如果CAS失败则返回false。

比如继承自AQS实现的独占锁在实现tryRelease时,在内部需要使用CAS算法把当前state的值从1修改为0,并设置当前锁的持有者为null,然后返回true,如果CAS失败则返回false。

共享方式

在共享方式下,获取与释放资源的流程如下:

  1. 当线程调用acquire(int arg)获取共享资源时,会首先使用tryAcquireShared尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为类型为Node.SHARED的Node节点后插入AQS阻塞队列的尾部,并使用LockSupport.park(this)方法挂起自己。
1
2
3
4
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
  1. 当一个线程调用releaseShared(int arg)时会首先尝试使用tryReleaseShared操作释放资源,这里是设置状态变量state的值,然后使用LockSupport.unpark(thread)激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryReleaseShared查看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下执行,否则还是会被放入AQS队列被挂起。
1
2
3
4
5
6
7
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

同样需要注意的是,AQS类并没有提供可用的tryAcquireShared和tryReleaseShared方法,正如AQS是锁阻塞和同步器的基础框架一样,tryAcquireShared和tryReleaseShared需要由具体的子类来实现。子类在实现tryAcquireShared和tryReleaseShared时要根据具体的场景使用CAS算法舱室修改state状态值,成功返回true,否则返回false。

比如继承自AQS实现的读写锁ReentrantReadWriteLock里面的读锁重写tryAcquireShared时,首先查看写锁是否被其他线程持有,如果是则返回false,否则使用CAS递增state的高16位(在ReentrantReadWriteLock中,state的高16位为后去读锁的次数)。在重写tryReleaseShared时,在内部需要使用CAS算法把当前state的高16位减1,然后返回true,如果CAS失败返回false。

下一章分析线程同步器CountDownLatch。


Java并发包原理剖析(一)AQS原理分析
https://www.weypage.com/2020/03/02/java/多线程/juc/AQS原理/
作者
weylan
发布于
2020年3月2日
许可协议