CountdownLatch 源码

构造方法

java 复制代码
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

Sync

java 复制代码
private static final class Sync extends AbstractQueuedSynchronizer {
   private static final long serialVersionUID = 4982264938641297070L;

   Sync(int count) {
       setState(count);
   }

   int getCount() {
       return getState();
   }

   // await调用:共享模式尝试获取锁
   protected int tryAcquireShared(int acquires) {
       // state == 0 返回1(获取成功),否则-1(阻塞)
       return (getState() == 0) ? 1 : -1;
   }

   // countDown调用:共享模式释放锁,state自减1
   protected boolean tryReleaseShared(int releases) {
       // CAS循环自旋减state
       for (;;) {
           int c = getState();
           if (c == 0)
               return false;
           int nextc = c - 1;
           if (compareAndSetState(c, nextc))
               return nextc == 0;
       }
   }
}

countdown

java 复制代码
public void countDown() {
    sync.releaseShared(1);
}

releaseShared

java 复制代码
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

tryReleaseShared

java 复制代码
protected boolean tryReleaseShared(int releases) {
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c - 1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

doReleaseShared

  • 遍历 AQS 阻塞队列,唤醒头部等待节点;
  • 等待线程被 unpark 唤醒,再次执行tryAcquireShared;
  • 如果 state=0,返回 1,获取共享锁成功,退出阻塞逻辑,主线程继续执行后续代码
java 复制代码
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 头节点标记SIGNAL,说明后继需要唤醒
            if (ws == Node.SIGNAL) {
                // CAS重置头节点状态为0,防止重复唤醒
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;
                // 唤醒头节点的后继等待线程(主线程)
                unparkSuccessor(h);
            }
            // 无等待线程,设置PROPAGATE传播标记
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;
        }
        // 循环终止条件:中途head发生变化,说明有新节点被唤醒,继续循环传播
        if (h == head)
            break;
    }
}

await

java 复制代码
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

acquireSharedInterruptibly

java 复制代码
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

tryAcquireShared

java 复制代码
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

doAcquireSharedInterruptibly

  • 把当前主线程封装成 共享 SHARED 类型 Node
  • CAS 把节点加入 AQS 阻塞双向链表尾部
  • 循环自旋:
    1. 如果当前节点是队列头节点,再次调用 tryAcquireShared
    2. 执行 parkAndCheckInterrupt(),调用 LockSupport.park()
    3. 当前线程挂起,卡住,不再往下执行,等待其他线程 countDown 把 state 降到 0 唤醒
java 复制代码
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 1. 创建共享SHARED类型节点,加入队尾
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 获取当前节点前驱
            final Node p = node.prev;
            // 如果前驱是头结点,有资格尝试获取共享锁
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    // 获取共享锁成功,设置新head,传播唤醒
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 调整节点waitStatus,无资格则park阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

setHeadAndPropagate

java 复制代码
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // 保存旧头
    setHead(node); // 当前等待节点晋升为新head,清空thread、prev
    // propagate>0 说明还有剩余共享许可,需要继续唤醒后继
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        // 如果下一个节点也是共享节点,继续唤醒传播
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

getCount

java 复制代码
public long getCount() {
    return sync.getCount();
}

总结

await

plaintext 复制代码
latch.await()
→ sync.acquireSharedInterruptibly(1)
    → tryAcquireShared 返回 -1
    → doAcquireSharedInterruptibly(1)
        addWaiter(SHARED) 入队
        for 自旋
            tryAcquireShared 失败
            shouldParkAfterFailedAcquire 设置前驱 SIGNAL
            parkAndCheckInterrupt() 阻塞主线程

countdown

plaintext 复制代码
latch.countDown()
→ sync.releaseShared(1)
    → tryReleaseShared CAS state到0,返回true
    → doReleaseShared()
        CAS清空head SIGNAL标记
        unparkSuccessor 唤醒主线程
等待线程被唤醒,回到 doAcquireSharedInterruptibly 自旋
    tryAcquireShared 返回 1
    setHeadAndPropagate 晋升新 head、传播唤醒
    退出阻塞,await执行完毕
相关推荐
干到60岁退休的码农10 分钟前
13.构建登录接口响应数据
java·spring boot·mybatis
水巷石子18 分钟前
学习langChain4j的第二天,体验springBoot中的starter
java·spring boot·学习·spring·langchain4j
ly768923 分钟前
Spring Bean生命周期全流程:从BeanDefinition到销毁
java·后端·spring·bean生命周期·beandefinition·初始化回调
君顾137 分钟前
智慧场馆解决方案小程序系统实战:从架构设计到上线指南
java·开发语言·智慧场馆
zhougl99638 分钟前
Dockerfile实战教程
java·开发语言·spring boot
新时代牛马44 分钟前
Linux 驱动调试完整篇:从printk/dev_dbg、动态调试到 debugfs/ftrace 排障
java·linux·服务器
计算机毕设定制辅导-无忧学长1 小时前
《基于Spring Boot传承之光非遗陶瓷烧造产品交易平台的设计与实现》
java·vue.js·spring boot·后端·毕业设计
Zane19941 小时前
内存都要回收,为什么JVM偏要把堆分成新生代和老年代
java·后端
Terra.K1 小时前
后端开发阶段性总结
java·开发语言·后端