以前虽然看过一次AQS的源码实现,但在过一段时间后与同学交流时,发觉自己理解并不够深,印像太浅。需要做一个记录整理,帮助自己消化。
AQS中Node的设计:
几个点:
1. Node实现作者: "CLH" (Craig, Landin, and * Hagersten) ,有名的CLH queue
2. 是一个FIFO的链表的实现,对于队列的控制经常要做double-check。
3. Node节点通过一个int waiteStatus代表一些不同意义的状态。
- SIGNAL=-1,代表是需要当前Node节点需要唤起后一个Node节点。在Node节点enqueue时,会设置前一个节点的状态。这样链式的唤醒,完成这样的一个交接棒。
- CONDITION = -2 ,
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) { // 位置1
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize // 位置3
Node h = new Node(); // Dummy header
h.next = node;
node.prev = h;
if (compareAndSetHead(h)) { // 位置4
tail = node;
return h;
}
}
else {
node.prev = t;
if (compareAndSetTail(t, node)) { // 位置2
t.next = node;
return t;
}
}
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
final Node p = node.predecessor(); // 位置1
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
private void unparkSuccessor(Node node) {
/*
* Try to clear status in anticipation of signalling. It is
* OK if this fails or if status is changed by waiting thread.
*/
compareAndSetWaitStatus(node, Node.SIGNAL, 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) { //位置1
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);
}
acquire , release , cancel三个动作设计
按照原先作者的设计:
Acquire:
while (!tryAcquire(arg)) {
enqueue thread if it is not already queued;
possibly block current thread;
}
Release:
if (tryRelease(arg))
unblock the first queued thread;
预留了5个protected方法,用于client自己实现相关的处理,进行业务行为控制,因为cocurrent很多Lock,Future的实现都是基于此扩展,定义了自己的处理。
具体的一些方法使用,后续再补。
acquire动作:
独占锁:
- public final void acquire(int arg)
- public final void acquireInterruptibly(int arg) throws InterruptedException
- public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); // 位置1
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // 位置2
parkAndCheckInterrupt()) // 位置3
interrupted = true;
}
} catch (RuntimeException ex) {
cancelAcquire(node); // 位置4
throw ex;
}
}
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
try {
for (;;) {
final Node p = node.predecessor(); // 位置1
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return;
}
if (shouldParkAfterFailedAcquire(p, node) && // 位置2
parkAndCheckInterrupt())
break; // 位置3
}
} catch (RuntimeException ex) {
cancelAcquire(node);
throw ex;
}
// Arrive here only if interrupted
cancelAcquire(node);
throw new InterruptedException(); // 位置4
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
long lastTime = System.nanoTime();
final Node node = addWaiter(Node.EXCLUSIVE);
try {
for (;;) {
final Node p = node.predecessor(); // 位置1
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return true;
}
if (nanosTimeout <= 0) { // 位置2
cancelAcquire(node);
return false;
}
if (nanosTimeout > spinForTimeoutThreshold && // 位置3
shouldParkAfterFailedAcquire(p, node)) // 位置4
LockSupport.parkNanos(this, nanosTimeout); // 位置5
long now = System.nanoTime(); // 位置6
nanosTimeout -= now - lastTime;
lastTime = now;
if (Thread.interrupted()) // 位置7
break;
}
} catch (RuntimeException ex) {
cancelAcquire(node);
throw ex;
}
// Arrive here only if interrupted
cancelAcquire(node);
throw new InterruptedException(); // 位置8
}
public final void acquireShared(int arg)
和独占锁处理方式基本类似,来看一下核心代码:
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r); // 位置1
p.next = null; // help GC
if (interrupted)
selfInterrupt();
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (RuntimeException ex) {
cancelAcquire(node);
throw ex;
}
}
这里设计上有点小技巧,原先思考一个共享锁的典型场景:读写锁。 一旦写锁释放,应该是唤起所有的读锁。而原先在看setHeadAndPropagate,并没有一个循环释放锁的过程。后来思考了下,采用的是一个链式释放的过程,前一个shared的锁对象释放下一个,在释放的时候继续进行tryAccquireShared控制。
一点感悟:在写并发程序时,一些传统编程的思路要有所改变。
public final void acquireSharedInterruptibly(int arg) throws InterruptedException
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException
这两个实现上和独占锁类似,也就是setHeadAndPropagate处理上的不同点而已。
release动作:
public final boolean release(int arg)
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
没啥特别好将的,一看基本也就明白了,出队列的时候,同时唤醒下一个Node。
cancel动作:
private void cancelAcquire(Node node)
代码就不贴了,几个处理:
1. 从链表上删除cancel节点
2. 如果cancel节点是head,则尝试唤醒cancel节点的下一个节点。
ConditionObject的理解
几个主要方法:
- public final void await() throws InterruptedException
- public final void awaitUninterruptibly()
- public final long awaitNanos(long nanosTimeout) throws InterruptedException
- public final boolean awaitUntil(Date deadline) throws InterruptedException
- public final boolean await(long time, TimeUnit unit) throws InterruptedException
- public final void signal()
- public final void signalAll()
Array queue;
Object empty = new Object();
Object full = new Object();
// 生产者
if(queue 是否满了)
full.wait() //阻塞等待
else
put(queue , data) //放入数据
empty.single(); // 已经放了一个,通知一下
// 消费者
if(queue 是否空了)
empty.wait() // 阻塞等待
else
data = get(queue);
full.single() // 已经消费了,通知一下
整体概念
在整个AQS存在两种链表。 一个链表就是整个Sync Node链表,横向链表。另一种链表就是Condition的wait Node链表,相对于Sync node,它属于node节点的一个纵向链表。当纵向列表被single通知后,会进入对应的Sync Node进行排队处理。
通过这样的纵横队列,实现了ConditionObject共享lock锁数据。