1. 队列同步器AQS的组成及使用
-
AQS在
CountDownLatch
、Semaphore
、CountDownLatch
等工具内都有使用,全称是:AbstractQueuedSynchronizer
是一个抽象类,在《Java并发编程艺术》一书中称之为队列同步器,是用来构建锁或者其他同步组件的基础框架,通过内置FIFO队列来完成获取资源线程的派对工作;作者是大佬Doug lea -
我们可以大致看一下我们锁用到的这些并发控制的工具类和锁的内部实现
- `Semaphore``
public class Semaphore implements java.io.Serializable { private static final long serialVersionUID = -3222578661600680210L; private final Sync sync; abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 1192457210091910933L; Sync(int permits) { setState(permits); } ......
ReentrantLock
public class ReentrantLock implements Lock, java.io.Serializable { private static final long serialVersionUID = 7373984872572414699L; private final Sync sync; abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; ......
CountDownLatch
public class CountDownLatch { private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L; Sync(int count) { setState(count); } ......
-
由上源码我们可以看到,里面都有一个内部类,
Sync
继承自AbstractQueuedSynchronizer
-
它的主要作用就是同个state的加减和队列来管理线程的执行。
1.2 AQS的组成及内部原理
- 看源码可知:ReentrantLock、Semaphore、ReentrantReadWriteLock、CountDownLatch中都有继承自AQS的Sync及其子类。
- AQS 同步器主要通过继承的方式,来实现它的抽象方法来实现管理同步器状态。《并发编程的艺术》
- 主要实现中组成就包括状态管理部分(包括
state
以及需要我们实现的tryAcquire
、tryRelease
等相关),阻塞管理部分(包括acquire()
方法、release()
方法、LockSupport
工具、FIFO队列等)
1.1 状态管理
- state:状态及其设置方法
/**
* The synchronization state.
*/
private volatile int state;
......
// 获取同步状态
protected final int getState() {
return state;
}
// 设置当前同步状态
protected final void setState(int newState) {
state = newState;
}
// CAS更新同步状态
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
- 我们可以看到里面使用了原子类也使用到了的Unsafe类,使用CAS来更新state。
- 状态在不同的工具中的意思一般不同,如:CountDownLatch中做计数器,Semaphore中做许可数量,ReentrantLock中做重入次数。都与计数相关。并且在AQS中管理时也以state作为阻塞和唤醒相关操作的判断依据。主要通过调用tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared来实现独占式同步状态和共享是同步状态获取和释放。需要工具自行实现。
- AQS源码中是直接抛出错误,所以需要继承然后实现。如下的AQS中的
tryReleaseShared
方法。
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
- 获取同步状态在各类中state的变化是不同的。
- 获取同步状态:
- 在
ReentrantLock
中,就是获取锁。state+1
- 在
Semaphore
中就是acquire
获取许可,state-1
,当state==0
就会阻塞 - 在
CountDownLatch
中就是await
方法,就是等待state==0
- 在
- 释放同步状态:
- 释放操作不会阻塞
- 在
ReentrantLock
中就是unlock
方法调用release(1)
对应state-1
- 在
Semaphore
中就是realease
,也是state-1
CountDownLatch
中就是countDown
方法,也是state-1
- 一般情况下,实现类都会实现
tryAcquire
、tryRelease
、tryAcquireShared
、tryReleaseShared
相关方法,以对应各个类的需求
1.2 阻塞管理
- 阻塞管理部分主要是管理线程的阻塞和唤醒,主要通过acquire和release等方法来实现,而这类方法在Java中都是固定的实现。核心方法就是如下两个,其他的拓展时间处理和共享处理都差不多。根本逻辑还是差不都的
- acquire()方法:独占式获取同步状态。tryAcquire就是工具或者我们自定义的工具类实现的逻辑。这个地方需要注意一点,独占式的是返回true或false,而共享式的则是返回数字。具体是为什么后面再说。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 两个判断主要是第一个是尝试获取同步状态,如果失败,那就调用第二个方法addWaiter加入同步队列尾部中等待,Node.EXCLUSIVE是独占式的表明。
- release()方法:独占式释放同步队列,这个方法在尝试释放成果后,利用LockSupport工具去唤醒FIFO队列中的头结点的线程(不为空的情况下)。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
- LockSupport工具:它定义了一组公共静态方法,这些方法提供了线程的阻塞和唤醒功能。是构建同步组件的基础工具,主要方法是park()负责阻塞当前线程和unpark(Thread)唤醒传入的线程,此外还有时间相关的阻塞方法。我们可以看下unparkSuccessor()方法中的代码
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
-
可以看到前面是状态操作,后面就是调用LockSupport的方法。
-
FIFO队列:
/** * Head of the wait queue, lazily initialized. Except for * initialization, it is modified only via method setHead. Note: * If head exists, its waitStatus is guaranteed not to be * CANCELLED. */ private transient volatile Node head; /** * Tail of the wait queue, lazily initialized. Modified only via * method enq to add new wait node. */ private transient volatile Node tail;
-
等待队列就是用来存放没有竞争到锁的等待线程的,AQS会对这个队列进行管理,AQS中定义了头结点和尾节点。
-
FIFO队列是一个双向链表;队列头节点是当前拿到锁的线程;在AQS中保存了这个队列的头尾节点。
- 当我们切换执行线程时,head指针会指向头结点的next指针。
1.3 AQS的使用点
- 在工具内部写一个Sync类继承同步队列器AQS(可以模仿CountDownLatch)
- 根据需求实现获取和释放方法,也就是acquire和release方法的调用规则
- 根据是独占还是共享来决定重写的方法:独占使用
tryAcquire
/tryRelease
、共享使用tryAcquireShared(int acquires)
/tryReleaseShared(int releases)
;
2. AQS在CountDownLatch中的源码剖析
- 下面我们以
CountDownLatch
为例分析源码:
2.1 注意点
- CountDownLatch使用的是共享式同步状态,所以acquire和release是调用的tryAcquireShared和tryReleaseShared,这两个方式返回的是数字。关于数字是什么意思,下面阅读过源码后就明白了。
2.2 构造函数
- 我们看到内部实现就是初始化一个
Sync
然后把计数值传入
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
-
我们可以看下面的
CountDownlatch
中Sync
的实现,在构造方法创建的Sync
传入的count
调用了setState
方法传入了AQS
的state
中 -
在
CountDownLatch
内部有一个继承AQS的Sync
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
-
CountDownLatch
的getCount()
方法public long getCount() { return sync.getCount(); }
- 我们可以看到
getCount
实际也是调用Sync
的getCount()
来获取state
并返回
- 我们可以看到
2.3 CountDownLatch
的countDown()
方法
public void countDown() {
sync.releaseShared(1);
}
- 我们看一看到它直接调用了
AQS
的releaseShared(1)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- 而
releaseShared
则是回去调用CountDownLatch
中实现的tryReleaseShared
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
-
而在
tryReleaseShared
中则是主要对state的值做-1操作,如果state大于零可以获取到就减一并且用CAS并发更新值,如果最新值为0就返回true -
返回true过后就
doReleaseShared
释放锁,唤醒队列里面的等待线程。也就是调用了await()
方法的线程
2.4 CountDownLatch
的await()
方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
- 而
await
则会调用AQS中的默认实现sync.acquireSharedInterruptibly(1);
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
-
而里面则是调用
tryAcquireShared(arg) < 0
看是否小于0,如果小于0就代表没有获取到锁,就调用doAcquireSharedInterruptibly(arg);
入队 -
而
tryAcquireShared
则是在CountDownLatch
中的Sync
实现的
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
- 如果当前state为0了(也就是说计数已经到0了)就返回一个1就不会满住上面的
acquireSharedInterruptibly
方法中的条件,就会放行,如果不等于0就会返回-1,此时就会入队。调用doAcquireSharedInterruptibly
方法
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 这个方法首先会把当前线程在
addWaiter
中包装成一个Node
节点并添加到队列尾部;而这个Node
节点就是FIFO队列的节点。 - 然后就会进入循环,如果当前节点不是
head
,那么就会进入到后面的判断,其中重要的是parkAndCheckInterrupt
,方法如下:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 它会调用
LockSupport
的park
并且此park
方法就是封装了Unsafe
的native方法park()
来把线程挂起进入阻塞状态
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
-
我们只需要知道
doAcquireSharedInterruptibly
方法就是把当前线程放到阻塞队列中,并且把线程阻塞就OK了。 -
AQS在
CountDownLatch
中使用的一些点:- 调用
CountDownLatch
的await()
时,便会尝试获取共享锁,开始时是获取不到锁的,于是就被阻塞 - 可以获取到的条件就是计数器为0,也就是
state==0
的时候。 - 只有每次调用
countDown
方法才会使得计数器减一,减到0时就回去唤醒阻塞中的线程。
- 调用
3. AQS在Semaphore
中的源码剖析
-
由于上面讲得很细了,接下来就简略一些
-
在
Semaphore
中state
就是许可证的数量 -
主要的操作就是acquire和release,也是借用Sync对state的操作来控制线程的阻塞与唤醒
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void release() {
sync.releaseShared(1);
}
- 先看下
acquire
调用的acquireSharedInterruptibly
此方法在上面已经说过。
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
- 而在Semaphore中Sync有两个实现:
NonfairSync
、FairSync
- 在FairSync中
tryAcquireShared
就会有hasQueuedPredecessors
判断,如果不是头节点,那就返回-1,在acquireSharedInterruptibly
方法中去调用doAcquireSharedInterruptibly
入队并且阻塞线程
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
-
而在
NonfairSync
中而是直接调用Sync
的nonfairTryAcquireShared
protected int tryAcquireShared(int acquires) { return nonfairTryAcquireShared(acquires); }
final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
- 可以看到其中并没有对是否阻塞队列的头节点判断,直接去获取值,判断是会否许可足够。
-
而
release
中则是调用AQS的releaseShared
其也是调用Semaphore
中Sync
的tryReleaseShared
来判断是否需要释放锁,去唤醒阻塞线程public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
-
tryReleaseShared
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
- 我们可以看到此处就是关于
Semaphore
的已获取许可的释放 把state
加回去然后用CAS更新state
4. AQS在ReentrantLock
中的应用
-
源码就不分析了
-
在
ReentrantLock
中,state
主要是重入的次数,加锁的时候state+1 ,而在释放锁的时候,state-1然后判断当前的state==0
-
在
ReentrantLock
中与AQS相关的有三个类:UnfairSync
,FairSync
,Sync
-
关于加锁和解锁的逻辑也是AQS中的acquire方法的逻辑(获取锁失败就会放入队列中)和release方法(调用子类的tryRelease来去掉头部,并且唤醒线程)
-
而加锁解锁中的逻辑,主要是公平锁和非公平锁的区别,公平锁会去判断是否在队列头部,如果在才会去执行,而非公平锁则会抢锁。不会管你是不是在队列头部。
-
相信在上面的源码分析过后,分析
ReentrantLock
是十分简单的。大家可以自行分析。