CountDownLatch的作用类似于Thread.join()方法,但比join()更加灵活。它可以等待多个线程(取决于实例化时声明的数量)都达到预期状态或者完成工作以后,通知其他正在等待的线程继续执行。简单的说,Thread.join()是等待具体的一个线程执行完毕,CountDownLatch等待多个线程。

  如果需要统计4个文件中的内容行数,可以用4个线程分别执行,然后用一个线程等待统计结果,最后执行数据汇总。这样场景就适合使用CountDownLatch。

  本篇从CountDownLatch的源码分析它的原理机制。再给出一个简单的使用案例。

  

  首先认识一下CountDownLatch中的内部类:

private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; Sync(int count) {
setState(count); // 更新AQS中的state
} int getCount() {
return getState();
} protected int tryAcquireShared(int acquires) {
return (getState() == ) ? : -;
} protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == )
return false;
int nextc = c-;
if (compareAndSetState(c, nextc))
return nextc == ;
}
}
}

  其实CountDownLatch的机制和ReentrantLock有点像,都是利用AQS(AbstractQueuedSynchronizer)来实现的。CountDownLatch的内部类Sync继承AQS,重写了tryAcquireShared()方法和tryReleaseShared()方法。这里的重点是CountDownLatch的构造函数需要传入一个int值count,就是等待的线程数。这个count被Sync用来直接更新为AQS中的state。

  

1、await()等待方法

//CountDownLatch
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly();
}
//AQS
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < ) // 1
doAcquireSharedInterruptibly(arg); // 2  
}
//Sync
protected int tryAcquireShared(int acquires) {
return (getState() == ) ? : -;
}
//AQS
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= ) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
  1. 调用AQS中的tryAcquireShared()方法时,Sync重写了tryAcquireShared()方法,获取state,判断state是否为0。
  2. 如果不为0,调用doAcquireSharedInterruptibly()方法,将线程加入队列,挂起线程。

2、countDown()

public void countDown() {
sync.releaseShared();
}
//AQS
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//Sync
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == )
return false;
int nextc = c-;
if (compareAndSetState(c, nextc))
return nextc == ;
}
}

  重点也是在于Sync重写的tryReleaseShared()方法。利用CAS算法将state减1。如果state减到0,说明所有工作线程都执行完毕,那么就唤醒等待队列中的线程。

使用示例:

public class CountDownLatchTest {
private static CountDownLatch countDownLatch = new CountDownLatch();
private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(, ,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10)); public static void main(String[] args) {
//等待线程
for (int i = ; i < ; i++) {
String threadName = "等待线程 " + i;
threadPool.execute(new Runnable() { @Override
public void run() {
try {
System.out.println(threadName + " 正在等待...");
//等待
countDownLatch.await();
System.out.println(threadName + " 结束等待...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
//工作线程
for (int i = ; i < ; i++) {
String threadName = "工作线程 " + i;
threadPool.execute(new Runnable() { @Override
public void run() {
try {
System.out.println(threadName + " 进入...");
//沉睡1秒
TimeUnit.MILLISECONDS.sleep();
System.out.println(threadName + " 完成...");
//通知
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
} threadPool.shutdown();
}
}

  执行结果为:

等待线程 1 正在等待...
等待线程 0 正在等待...
工作线程 2 进入...
工作线程 3 进入...
工作线程 4 进入...
工作线程 3 完成...
工作线程 2 完成...
工作线程 4 完成...
等待线程 0 结束等待...
等待线程 1 结束等待...

  从结果也能看到,等待线程先执行,调用countDownLatch.await()方法开始等待。每个工作线程工作完成以后,都调用countDownLatch.countDown()方法,告知自己的任务完成。countDownLatch初始参数为3,所以3个工作线程都告知自己结束以后,等待线程才开始工作。

并发工具CountDownLatch源码分析的更多相关文章

  1. 并发工具CyclicBarrier源码分析及应用

      本文首发于微信公众号[猿灯塔],转载引用请说明出处 今天呢!灯塔君跟大家讲: 并发工具CyclicBarrier源码分析及应用 一.CyclicBarrier简介 1.简介 CyclicBarri ...

  2. Java - "JUC" CountDownLatch源码分析

    Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例 CountDownLatch简介 CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前 ...

  3. CountDownLatch 源码分析

    CountDownLatch 源码分析: 1:CountDownLatch数据结构 成员变量 Sync类型对象 private final Sync sync; Sync是继承AQS的一个类,Coun ...

  4. [软件测试]网站压测工具Webbench源码分析

    一.我与webbench二三事 Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能.Webbench ...

  5. 网站(Web)压测工具Webbench源码分析

    一.我与webbench二三事 Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能.Webbench ...

  6. Java并发——结合CountDownLatch源码、Semaphore源码及ReentrantLock源码来看AQS原理

    前言: 如果说J.U.C包下的核心是什么?那我想答案只有一个就是AQS.那么AQS是什么呢?接下来让我们一起揭开AQS的神秘面纱 AQS是什么? AQS是AbstractQueuedSynchroni ...

  7. JUC之CountDownLatch源码分析

    CountDownLatch是AbstractQueuedSynchronizer中共享锁模式的一个的实现,是一个同步工具类,用来协调多个线程之间的同步.CountDownLatch能够使一个或多个线 ...

  8. bootstrap_栅格系统_响应式工具_源码分析

    -----------------------------------------------------------------------------margin 为负 ​使盒子重叠 ​等高 等高 ...

  9. concurrent(五)同步辅助器CountDownLatch & 源码分析

    参考文档: https://blog.csdn.net/zxdfc/article/details/52752803 简介 CountDownLatch是一个同步辅助类.允许一个或多个线程等待其他线程 ...

随机推荐

  1. Go实现分布式外部排序

    Go实现分布式外部排序 项目路径: https://github.com/Draymonders/go_external_sort 默认读入文件: small.in 默认输出文件:small.out ...

  2. Laya 首日红点逻辑

    Laya 首日红点逻辑 @author ixenos 2019-08-26 10:50:27 1.原理:显然,首日红点意味着包含进程销毁的情况,那么就要持久化存储信息,这里我们使用LocalStora ...

  3. RedHat下使用gcc编译HelloWorld.cpp

    gcc ./HelloWorld.cpp 错误: /tmp/ccZuz3Ca.o:(.eh_frame+0x12): undefined reference to `__gxx_personality ...

  4. [BZOJ3199][SDOI2013]escape:半平面交

    分析 好像叫V图什么的. 容易发现,对于每个点,其监视的范围就是这个点与其它所有点的垂直平分线分割平面后的半平面交.由于数据范围很小,所以我们可以直接枚举每个点,使用双端队列求出其监视的范围.若两个点 ...

  5. node.js配置环境变量

    今天配置node.js的时候,碰到了配置环境变量的问题 为什么会出这样的问题: 因为我将 node.js 安装到了D盘,(这是个坑,以后一定要安到C盘),当我发现,我的node操作指令无效的时候,知道 ...

  6. node.js安装和配置(windows系统)

    node.js安装和配置(windows系统) node javasript vscode  node是javascript的管理工具,所以开发javasript项目都要下载安装和配置node. 传送 ...

  7. Hypermesh中弹簧单元设置

    1D >> springs 单元类型 CBUSH1D 单元属性 PBUSH1D

  8. activiti 流程发起人控制

    最近做activiti流程发起人的控制,最开始的想法是新建一张表 ,通过控制流程定义id与发起人id进行控制,如果这样每次发布新的流程就必须 重新设置流程发起人,因为通过流程定义不能获取流程模型id, ...

  9. UI自动化之特殊处理一(iframe\句柄\鼠标键盘)

    iframe\句柄\鼠标键盘是一些比较特殊的事件,需要特殊处理 目录 1.iframe 2.句柄(handle) 3.鼠标键盘 1.iframe iframe 是内嵌的网页元素,也可以说是内嵌的框架, ...

  10. Learn Python the hard way, ex45 对象、类、以及从属关系

    #!/usr/bin/python #coding:utf-8 # animal is-a object(yes,sort of sonfusing)look at the extra credit ...