学习主要的三个函数
一共用到两个源码文件,下面的代码跟踪全在这两个文件中进行:
- CountDownLatch.java
- AbstractQueuedSynchronized.java
初始化
直接上源码
- CountDownLatch.java 中
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
setState(count); //count表示线程个数,需要count个countdown后,调用await()的线程才可以继续执行
}
await()
阻塞线程,直到满足条件才可唤醒
- CountDownLatch.java
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
- AbstractQueuedSynchronized.java
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
// 首先判断线程是否被中断。若被中断,则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 其次判断持有锁的线程的个数,若满足条件(即还有线程持有锁),则该线程应被挂起直到state=0
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
- CountDownLatch.java
protected int tryAcquireShared(int acquires) {
//当还有线程持有锁,则返回-1;若无线程持有锁,则返回1
return (getState() == 0) ? 1 : -1;
}
- AbstractQueuedSynchronized.java
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
//将当前线程包装成一个Node,加入等待队列中
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) {
//若当前节点的前置节点为头节点(head),且无其他线程持有锁,表明可以唤醒该线程及后续线程节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private Node addWaiter(Node mode) {
// 将当前线程包装为一个Node
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)) {
pred.next = node;
return node;
}
}
// 若等待队列为空或设置尾节点失败(有多个线程同时进行尾节点的设置),进入enq()函数
enq(node);
return node;
}
private Node enq(final Node node) {
// 在死循环中
for (;;) {
Node t = tail;
// 若尾节点为空(对应上段代码的“等待队列为空”),则新建**头节点**并设置tail=head
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 若尾节点不为空(对应上段代码的“多个线程同时设置尾节点”),则不断尝试设置尾节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
回到函数*doAcquireSharedInterruptibly()*接着看,后续需要关注的函数还有三个:
- setHeadAndPropagate(node, r) //条件满足,唤醒头节点并向后传播
- shouldParkAfterFailedAcquire(p, node)
- parkAndCheckInterrupt(
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
// 条件满足,是唤醒锁的重要函数,稍后介绍该函数
doReleaseShared();
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
简单来说,函数shouldParkAfterFailedAcquire()就是根据节点的waitStatus值进行操作。
1)若为SINGAL,表明向后传播,返回true
2)若ws>0,表明此时节点状态为CANCELLED,向前找节点直到某节点的状态值不为CANCELLED,返回false
3)否则将该节点值设置为SIGNAL,返回false
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
该函数主要实现阻塞线程,等待满足条件
countdown()
- CountDownLatch.java
public void countDown() {
sync.releaseShared(1);
}
- AbstractQueuedSynchronized.java
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- CountDownLatch.java
// 若CAS交换成功,则返回nextc是否为0。
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;
}
}
若满足条件,进入释放锁阶段
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 释放锁
unparkSuccessor(h);
}
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
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);
}
当最后一个获取锁的线程进行countdown()后,进行锁的释放(unparkSuccessor()中的LockSupport.unpark())并唤醒等锁队列中的头节点。
调用await()的线程在doAcquireSharedInterruptibly()函数中满足r>=0,进入setHeadAndPropagate()函数进行该线程的unpark(),注意到该线程作为等待队列的头节点在该函数中会触发判断,后break出for的死循环,并唤醒队列中的下一个节点线程进行unpark()等后续操作。
if (h == head)
break;
至此,CountDownLatch的主要源码,基本流程就算过一遍了。
第一次尝试写源码分析,肯定有许多不足之处,待有空继续完善内容。