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);
}


相关文章
|
4天前
|
Java
并发编程之线程池的底层原理的详细解析
并发编程之线程池的底层原理的详细解析
15 0
|
4天前
|
Java
并发编程之线程池的应用以及一些小细节的详细解析
并发编程之线程池的应用以及一些小细节的详细解析
17 0
|
2天前
|
监控 测试技术 Linux
线程死循环是并发编程中常见的问题之一
【4月更文挑战第24天】线程死循环是并发编程中常见的问题之一
10 1
|
3天前
|
Java
Java中的并发编程:理解和应用线程池
【4月更文挑战第23天】在现代的Java应用程序中,性能和资源的有效利用已经成为了一个重要的考量因素。并发编程是提高应用程序性能的关键手段之一,而线程池则是实现高效并发的重要工具。本文将深入探讨Java中的线程池,包括其基本原理、优势、以及如何在实际开发中有效地使用线程池。我们将通过实例和代码片段,帮助读者理解线程池的概念,并学习如何在Java应用中合理地使用线程池。
|
4天前
|
监控 Java
并发编程之线程池的详细解析
并发编程之线程池的详细解析
7 0
|
8天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。
|
8天前
|
安全 Java 程序员
Java中的多线程并发编程实践
【4月更文挑战第18天】在现代软件开发中,为了提高程序性能和响应速度,经常需要利用多线程技术来实现并发执行。本文将深入探讨Java语言中的多线程机制,包括线程的创建、启动、同步以及线程池的使用等关键技术点。我们将通过具体代码实例,分析多线程编程的优势与挑战,并提出一系列优化策略来确保多线程环境下的程序稳定性和性能。
|
17天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
28天前
|
存储 缓存 NoSQL
Redis单线程已经很快了6.0引入多线程
Redis单线程已经很快了6.0引入多线程
31 3
|
1月前
|
消息中间件 安全 Linux
线程同步与IPC:单进程多线程环境下的选择与权衡
线程同步与IPC:单进程多线程环境下的选择与权衡
58 0