1. 作用
允许一条或多条线程等待其他线程中一组操作完成后,再继续执行
收集龙珠,召唤神龙:委派7个人同时收集,主人一直等待,直到7个人完成后,主人再继续召唤神龙
2. 使用方式demo
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
CountDownLatch countDownLatch = new CountDownLatch(list.size());
// 并行寻找龙珠
for (Integer num : list) {
new Thread(()->{
search(countDownLatch,num);
}).start();
}
// 主人一直等待,直到收集完成
countDownLatch.await();
System.out.println("召唤神龙");
}
/**
* 收集龙珠
* @param countDownLatch
* @param num
*/
public static void search(CountDownLatch countDownLatch, int num) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("找到了第:'" + num + "'颗龙珠");
countDownLatch.countDown();
}
3. 原理
- 初始化latch
- 将等 await() 的线程放入等待队列
- 子任务完成后释放锁
- 当state=0时,唤醒等待队列中的任务
问题:如何推动等待队列?
等待队列是FIFO的,最前面是一个虚节点head,当Node1获取到锁,此时Node1会赋值为head,之前的head节点会被GC
3.1 初始化latch
CountDownLatch countDownLatch = new CountDownLatch(7);
同时会指定同步状态state被占用7次
3.2 将等 await() 的线程放入等待队列
主人进入等待队列,称为Node1
countDownLatch.await() // main线程进入等待
ps: 如果其他人也需要等待(比克大魔王 也在悄悄的等待7人收集完成,也可调用该方法,称为Node2)
new Thread(()->{ try { countDownLatch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("比克大魔王收到收集完龙珠消息"); }).start();
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 返回负数,获取锁失败
if (tryAcquireShared(arg) < 0)
// 进入等待队列
doAcquireSharedInterruptibly(arg);
}
// 返回负数,获取锁失败
// 返回0,获取锁成功,不唤醒后续节点
// 返回正数,获取锁成功,唤醒后续节点
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 加入等待队列:cas自旋获取锁成功的标志:r >= 0 (countDownLatch的state=0,)
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
// 尝试获取锁,>=0 需要唤醒后面的节点
int r = tryAcquireShared(arg);
if (r >= 0) {
//将此节点置为head(之前的head会被GC),再唤醒后续的共享模式节点的任务
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 void setHeadAndPropagate(Node node, int propagate) {
// 将此节点设置head节点
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 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;
}
}
问题:tryAcquireShared 为什么不返回0
因为等待队列可能有多个任务,返回0无法使用唤醒逻辑
3.3 子任务完成后释放锁
countDownLatch.countDown();
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
// 是否释放锁成功
if (tryReleaseShared(arg)) {
// 唤醒等待队列中的任务
doReleaseShared();
return true;
}
return false;
}
// 尝试释放锁(-1后 state=0)
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
// c已经=0,表示等待队列是空的,不需要再唤醒(state总数=7,但是有10个任务调了
countDownLatch.countDown() 方法,第7次执行完就唤醒了等待队列中的任务,后面
2次不需要再唤醒等待队列中的任务)
if (c == 0)
return false;
int nextc = c-1;
// state=0 时返回true,表示后续需要唤醒等待队列中的任务
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;
}
}