Juc并发编程14——线程计数器CountdownLatch源码剖析(上)

简介: 线程计数器文章目录线程计数器CountdownLatch源码剖析1 使用计数器锁实现任务计数2 await的源码剖析3 countdown源码剖析CountdownLatch源码剖析

1 使用计数器锁实现任务计数

多任务同步神器,它允许一个或多个线程,等待其它线程完成工作,比如我们现在有一个需求:

  • 有20个任务,需要将每个任务的执行结果算出来,但是每个任务执行的时间未知。
  • 当所有的任务执行结束后,立即整合统计所有的执行结果。

我们并不知道任务可以在什么时间完成,因此执行统计的时间不好设置,设置短了则还有任务没有完成,设置长了则统计延迟。

CountdownLatch可以做到,它是一个实现子任务同步的工具。Demo如下

  public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(20);
        for (int i = 0; i < 20; i++) {
            final int  finalI = i;
            new Thread(() ->{
                try {
                    Thread.sleep((long)(2000 + new Random().nextDouble()));
                    System.out.println("thread " + finalI + " finished");
                } catch (InterruptedException exception) {
                    exception.printStackTrace();
                }
                latch.countDown(); // 相当于计数器,每次减少1
            }).start();
        }
        latch.await(); //可以多个线程同时等待,这里仅演示了一个线程进行等待
        System.out.println("all sub task finished!");
    }

执行结果如下

其实它就是一个线程计数器,注意CountDownLatch是一次性的,不能重复使用。比如下面再多调用一次latch.await,程序还是正常结束的(毕竟计数不可逆,已经是0了,而且无法将计数器重置).


 public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(20);
        for (int i = 0; i < 20; i++) {
            final int  finalI = i;
            new Thread(() ->{
                try {
                    Thread.sleep((long)(2000 + new Random().nextDouble()));
                    System.out.println("thread " + finalI + " finished");
                } catch (InterruptedException exception) {
                    exception.printStackTrace();
                }
                latch.countDown();
            }).start();
        }
        latch.await();
        latch.await();
        System.out.println("all sub task finished!");
    }

2 await的源码剖析

上面已经演示了使用,下面来看看它的原理吧。

public class CountDownLatch {
  // 使用了AQS,不过是基于共享锁实现的 
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
      //这里实际上使用count作为了共享锁的state值
      // count数与共享锁的数量相同
      // 每调用一次countdown就是解一层锁
        Sync(int count) {
            setState(count);
        }
        int getCount() {
            return getState();
        }
      // 重写了共享锁的实现
      // 获取共享锁其实就是等待其它线程把state减到0
        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;
            }
        }
    }
    private final Sync sync;
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count); //构造sync方法
    }
  //通过acquireSharedInterruptibly获取共享锁,
  // 但是如果state不为0,将会被持续阻塞,后文详解
  public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
  // 同上,但是会被阻塞
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
  //countDown其实就是解锁一次
  public void countDown() {
        sync.releaseShared(1);
    }
// 获取当前的计数,也就是AQS的state值
  public long getCount() {
        return sync.getCount();
    }
  public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }

我们终于知道CountDownLatch它的原理了,原来它就是利用共享锁来实现计数的,锁的数量就是计数数量,countdown的过程就是解锁的过程。


我们在该系列博客已经介绍过独占锁,但是没有深入剖析过共享锁,这里我们来深入共享锁的源码进行剖析,以便大家对CountDownLatch具有更为深入的理解。

点进AbstractQueuedSynchronizer类中。先看看acquireShared获取共享锁,这个就是CountDownLatchawait方法调用的底层方法(实际上是acquireSharedInterruptibly,不过原理是一样的)。

public final void acquireShared(int arg) {
      // 尝试获取共享锁,小于0则失败
        if (tryAcquireShared(arg) < 0)
          // 获取共享锁失败,进入阻塞
            doAcquireShared(arg);
}


相关文章
|
26天前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
96 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
2月前
|
安全 Java 程序员
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
114 11
|
4月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
103 12
|
4月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
519 6
|
4月前
|
设计模式 安全 Java
Java 多线程并发编程
Java多线程并发编程是指在Java程序中使用多个线程同时执行,以提高程序的运行效率和响应速度。通过合理管理和调度线程,可以充分利用多核处理器资源,实现高效的任务处理。本内容将介绍Java多线程的基础概念、实现方式及常见问题解决方法。
236 1
|
4月前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
1月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
56 17
|
1月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
62 26
|
3月前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
318 2
|
4月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####