简介
- java.util.concurrent.CountDownLatch
- CountDownLatch是一种一次性同步辅助工具,它实现了一个或一组线程等待被其他线程唤醒的机制
- 使用场景举例:服务启动时,会同时加载多个组件,主线程需等待组件加载,当所有组件加载完毕之后,主线程才能去完成某个任务(去吃饭,等上菜、等人齐,之后才可以吃饭)
- 核心方法:
await: 阻塞调用线程,直到CountDownLatch中计数器到0;才会被唤醒
countDown: 递减CountDownLatch中的计数器
代码样例
public class MainService {
public static void main(String[] args) throws InterruptedException {
CountDownLatch partLatch = new CountDownLatch(3);
for (int i=0; i<3; i++) {
new Thread(new Part(i, partLatch)).start();
}
partLatch.await();
doSomeThingElse();
}
public static void doSomeThingElse() {
System.out.println("组件初始化完毕,执行其他动作");
}
}
class Part implements Runnable {
public int no;
public CountDownLatch partLatch;
public Part(int no, CountDownLatch partLatch) {
this.no = no;
this.partLatch = partLatch;
}
@Override
public void run() {
System.out.println("组件"+no+"初始化完毕");
partLatch.countDown();
}
}
原理解析
CountDownLatch类存在一个内部类Sync,其继承自AbstractQueuedSynchronizer(AQS),通过AQS来实现CountDownLatch的两个核心功能await、countDown
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;
}
}
}
new
每一个CountDownLatch实例都对于一个Sync实例
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
await
- 1: CountDownLatch.await
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
- 2: AQS.acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//如果线程中断则直接抛异常
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取资源,获取成功直接返回,获取失败则block线程
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
- 3: CountDownLatch#Sync.tryAcquireShared
protected int tryAcquireShared(int acquires) {
//判断Sync的state == 0 true则返回1,否则返回-1
return (getState() == 0) ? 1 : -1;
}
- 4: AQS.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) {
//获取state状态
int r = tryAcquireShared(arg);
if (r >= 0) {
//获取成功,将当前节点设置为头节点,并唤醒后置节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//获取失败后,判断node是否需要进入阻塞状态&&将当前调用线程阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 4.1:AQS.addWaiter
private Node addWaiter(Node mode) {
//基于当前Thread创建指定模式的Node;
Node node = new Node(Thread.currentThread(), mode);
// 获取队尾的节点;
Node pred = tail;
// pred不为空,即队尾节点不为空
if (pred != null) {
//新创建的Node添加到队尾;
node.prev = pred;
//采用CAS机制设置新创建节点为新的队尾
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//只有当队尾不存在,或者CAS设置队尾失败是才会走到enq方法
//enq方法很简单其核心就是将节点插入到队列,如果有必要也会做队列的初始化
enq(node);
return node;
}
//采用CAS机制设置新创建节点为新的队尾
private final boolean compareAndSetTail(Node expect, Node update) {
//tailOffset表示队尾node在lock对象中的偏移量,使用cas将新的update放入tailOffset
//this :包含要修改的字段对象;
//offset :字段在对象内的偏移量,tailOffset指的是tail指针属性相对于aqs对象的内存偏移量,之前在这段地址上存储的是原尾指针expect对象的内存地址,现在想换成update对象的地址;
//expect : 字段的期望值;
//update :如果该字段的值等于字段的期望值,用于更新字段的新值
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
- 4.2:AQS.setHeadAndPropagate 设置头节点并向后传播(唤醒后置节点)
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//将当前节点作为新的头节点
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
//这里暂时不聊这个函数,待说到countDown是会有介绍
//主要功能唤醒后续结点
doReleaseShared();
}
}
- 4.3: shouldParkAfterFailedAcquire state!= 0时判断是否需要将当前线程阻塞
/**
* CANCELLED = 1 节点已关闭
* SIGNAL = -1 后置节点线程需要被唤醒
* CONDITION = -2 当前线程在等待条件满足
* PROPAGATE = -3 表示下一个acquireShared应无条件传播
* */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 当前置节点的状态为SIGNAL时当前线程可以被阻塞,保证本线程可以被唤醒
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
- 4.4: parkAndCheckInterrupt()阻塞当前线程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
countDown
- 1:CountDownLatch.countDown
public void countDown() {
sync.releaseShared(1);
}
- 2:AQS.releaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- 3:CountDownLatch#Sync.releaseShared
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
// 当前AQS的状态已经为0时表明释放失败
if (c == 0)
return false;
int nextc = c-1;
//CAS机制将AQS的state递减
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
- 4:AQS.doReleaseShared
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
//自旋
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;
}
}
QA
Q CountDownLatch的底层实现原理是什么?
A CountDownLatch是基于AQS机制实现一种同步辅助工具;其实现了一个或一组线程等待被其他线程唤醒的机制;
Q CountDownLatch一次可以唤醒几个任务?
A 多个