CountDownLatch的介绍
CountDownLatch的介绍
CountDownLatch是JUC中的一个同步工具类,它可以帮助我们实现线程之间的同步和协作。它的核心思想是通过计数器来控制线程的执行顺序。当计数器的值降为0时,所有等待的线程都会被唤醒,然后开始执行下一步操作。
一、实现原理
1. Sync类
CountDownLatch的核心实现类是Sync,它是一个继承自AbstractQueuedSynchronizer的内部类。
1 // Sync 类继承自 AbstractQueuedSynchronizer,提供了共享锁的功能 2 private static final class Sync extends AbstractQueuedSynchronizer { 3 4 // 构造方法,设置初始计数值 5 Sync(int count) { 6 // 使用 AQS 的 setState 方法来设置同步器的状态,这里用 count 来初始化状态 7 // 计数值即锁的可用次数 8 setState(count); 9 } 10 11 // 获取当前的计数值(即剩余的共享资源数量) 12 int getCount() { 13 // 调用 AQS 的 getState 获取当前状态,即共享资源的剩余数量 14 return getState(); 15 } 16 17 // 试图以共享模式获取锁(acquireShared),即检查是否可以获取共享资源 18 protected int tryAcquireShared(int acquires) { 19 // 状态为0时,返回为1,表示资源获取成功。返回为-1时,表示资源不可用,线程需要挂起等待。 20 return (getState() == 0) ? 1 : -1; 21 } 22 23 // 试图以共享模式释放锁(releaseShared),即释放一个共享资源 24 protected boolean tryReleaseShared(int releases) { 25 for (;;) { // 循环尝试,直到成功 26 // 获取当前的状态值(即共享资源数量) 27 int c = getState(); 28 29 //如果当前状态(即共享资源的数量)为 0,表示已经没有共享资源可以释放,因此返回 false,表示释放操作失败。 30 if (c == 0) 31 return false; // 没有资源,返回 false 32 33 // 计数器的值减1 34 int nextc = c - 1; 35 36 // 尝试更新状态(使用 CAS 操作) 37 if (compareAndSetState(c, nextc)) { 38 // 如果计数器的值减为0,说明所有线程已经执行完毕,返回 true,否则返回false。 39 return nextc == 0; 40 } 41 } 42 } 43 }
2. 核心方法
CountDownLatch的使用方式非常简单,主要包括两个方法:await()和countDown()。
1)await() 方法
该方法会阻塞当前线程,直到计数器的值减为0,CountDownLatch 会释放所有在 await() 方法上等待的线程。
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
await() 方法中调用了AQS提供的acquireSharedInterruptibly() 方法
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { //检查当前线程是否被中断。如果是中断状态,它会抛出 InterruptedException。 if (Thread.interrupted()) throw new InterruptedException(); //如果返回值大于等于 0,表示获取锁成功,返回值小于 0,表示无法获取锁,需要进行等待或重试 if (tryAcquireShared(arg) < 0) //等待获取锁的逻辑(包括中断处理) doAcquireSharedInterruptibly(arg); }
这里调用的tryAcquireShared()是由CountDownLatch中静态内部类Sync继承AQS后进行重写的。当获取的状态值state为0时,获取资源成功,此时acquireSharedInterruptibly()方法就会放行,执行后面的逻辑。
在获取锁失败的时候,调用AQS的doAcquireSharedInterruptibly()方法
1 private void doAcquireSharedInterruptibly(int arg) 2 throws InterruptedException { 3 // 将当前线程添加到等待队列中,并设置当前节点为共享模式 4 final Node node = addWaiter(Node.SHARED); 5 6 // 标记当前操作是否失败 7 boolean failed = true; 8 9 try { 10 // 死循环,尝试获取共享锁 11 for (;;) { 12 // 获取当前节点的前驱节点 13 final Node p = node.predecessor(); 14 15 // 如果前驱节点是头节点,则尝试获取共享锁 16 if (p == head) { 17 // 尝试获取共享锁 18 int r = tryAcquireShared(arg); 19 20 // 如果获取成功(r >= 0),更新头节点并传播信号 21 if (r >= 0) { 22 setHeadAndPropagate(node, r); 23 p.next = null; // 断开前驱节点的 next 指针,帮助垃圾回收 24 failed = false; // 操作成功,标记为没有失败 25 return; // 返回,表示共享锁获取成功 26 } 27 } 28 29 // 如果获取锁失败且需要等待,则调用 parkAndCheckInterrupt() 来挂起当前线程 30 if (shouldParkAfterFailedAcquire(p, node) && 31 parkAndCheckInterrupt()) 32 // 如果被中断,抛出 InterruptedException 33 throw new InterruptedException(); 34 } 35 } finally { 36 // 如果操作失败,取消当前线程的获取锁请求 37 if (failed) 38 cancelAcquire(node); 39 } 40 }
当节点设置为共享模式时,多个线程可以同时等待,并且共享同一个等待状态。这意味着,多个线程可以被唤醒并尝试获取共享资源。与排它模式不同,在排它模式下,只有一个线程能够获取独占锁,其他线程会在锁释放前被阻塞。
2)countDown()方法
该方法会将计数器的值减1。
public void countDown() { sync.releaseShared(1); }
countDown()方法中调用了AQS提供的releaseShared()方法
public final boolean releaseShared(int arg) { //尝试释放共享锁 if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
这里调用的tryReleaseShared()是由CountDownLatch中静态内部类Sync继承AQS后进行重写的,当计数器减为0时,就会释放资源。
注意:如果获取getState()的值为0时,则表示已经没有资源可以释放了,释放操作会失败。
二、应用示例
1 public class CountDownLatchDemo { 2 public static void main(String[] args) throws InterruptedException { 3 final int count = 3; 4 final CountDownLatch latch = new CountDownLatch(count); 5 6 for (int i = 0; i < count; i++) { 7 new Thread(() -> { 8 // 线程执行任务 9 System.out.println(Thread.currentThread().getName() + " 执行任务..."); 10 // 任务执行完毕,计数器减1 11 latch.countDown(); 12 }).start(); 13 } 14 15 // 等待所有任务执行完毕 16 latch.await(); 17 System.out.println("所有任务执行完毕..."); 18 } 19 } 20 21 // 执行结果 22 //Thread-1 执行任务... 23 //Thread-0 执行任务... 24 //Thread-2 执行任务... 25 所有任务执行完毕...
在该示例代码中,我们创建了一个CountDownLatch对象,并将计数器初始化为3。然后创建了3个线程,每个线程执行一个任务,任务执行完毕后,将计数器减1。最后,在主线程中调用latch.await()方法等待所有任务执行完毕。
参考链接:
https://juejin.cn/post/7226175610212417594
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)