1. 源码预览
java.util.concurrent.locks.AQS以及静态内部类Node
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
...
static final class Node {
// 共享节点
static final Node SHARED = new Node();
// 独占节点
static final Node EXCLUSIVE = null;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
...
}
}
1.1 关于Node
- AQS内部维护了一个FIFO的双向队列,AQS依赖此队列来完成同步状态的管理;
- Node节点是FIFO双向队列的组成元素,其封装了当前线程以及等待状态信息。
The wait queue is a variant of a “CLH” (Craig, Landin, and Hagersten) lock queue.
To enqueue into a CLH lock, you atomically splice it in as new tail. To dequeue, you just set the head field.
1.2 关于waitStatus
static final int CANCELLED = 1; // 线程已被取消
static final int SIGNAL = -1; // 线程需要唤醒unparking
static final int CONDITION = -2; // 线程在等待条件
static final int PROPAGATE = -3; // 无条件传播
2. 双向队列
2.1 入队
AQS#acquire()
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire()方法代码很少,但是内容确很多。
- 尝试获取锁失败,会进行addWaiter(node)操作,插入node节点到队尾
- acquireQueue()方法内部会再次尝试获取锁,获取成功则当前线程不需要挂起,否则当前线程挂起(内部还有维护队列的操作)
AQS#acquireQueued()
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
AQS#addWaiter(mode)
private Node addWaiter(Node mode) {
Node node = new Node(mode);
// 1.
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
// 2.
U.putObject(node, Node.PREV, oldTail);
// 3.
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
- 无条件循环,直到将节点插入到尾部;
- 设置新节点node的前置节点
- CAS操作,设置oldTail的后置节点为node,完成node节点插入操作
下图描述将新节点插入到队尾。
2.2 出队
出队操作就是释放锁的操作,用户调用reentrantLock.unlock()来触发唤醒下一个Node中的线程。
ReentrantLock#unlock()
public void unlock() {
sync.release(1);
}
流程由ReentrantLock进入AQS。
AQS#release(int)
public final boolean release(int arg) {
// 1.
if (tryRelease(arg)) {
Node h = head;
// 2.
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
- unlock操作,会改变锁对象(ReentrantLock#Sync)的state字段值,state=0则返回true,否则返回false;
- 唤醒下一个节点表示的线程;
- 步骤1的返回值就代表方法的返回值
ReentrantLock#Sync#tryRelease(int)
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
上述步骤1说明:
改变AQS#state字段值,如果state=0,则返回true,否则返回false。
AQS#unparkSuccessor(node)
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)
node.compareAndSetWaitStatus(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 p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
if (s != null)
LockSupport.unpark(s.thread);
}
唤醒“下一个节点”的策略:
- 默认唤醒当前头节点的下一个节点:Node s = node.next;
- 如果s为空,或者waitStatus>0,则转向从队尾开始寻找waitStatus<=0的节点(离头节点最近的节点),并赋值给s;
- 唤醒节点s代表的线程。
3. 双向队列的优势
双向队列是空间换时间的机制来提高访问效率,入队和出队都只需要O(1)的时间复杂度,在高并发的场景下可以有更有的性能。