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

posted @   欢乐豆123  阅读(117)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示