前言

在多线程环境下,JDK给开发者提供了许多的组件供用户使用(主要在java.util.concurrent下),使得用户不需要再去关心在具体场景下要如何写出同时兼顾线程安全性与高效率的代码。之前讲过的线程池、BlockingQueue都是在java.util.concurrent下的组件,Timer虽然不在java.util.concurrent下,但也算是。后两篇文章将以例子的形式简单讲解一些多线程下其他组件的使用,不需要多深刻的理解,知道每个组件大致什么作用就行。

本文主要讲解的是CountDownLatch

CountDownLatch

CountDownLatch主要提供的机制是当多个(具体数量等于初始化CountDownLatch时count参数的值)线程都达到了预期状态或完成预期工作时触发事件,其他线程可以等待这个事件来触发自己的后续工作。值得注意的是,CountDownLatch是可以唤醒多个等待的线程的。

通俗点讲用给定的计数 初始化 CountDownLatch设置一个值。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回,调用一次countDown() 计数count -1 , 直到为0时,会通知所有await的线程,注意,这里计数是不能被重置的,也就是只能使用通知一次


为了到达自己预期状态(初始化设置的值)的线程会调用CountDownLatch的countDown方法(该方法会在初始化的值上  count-1),等待的线程会调用CountDownLatch的await方法(当count计数器到达0时调用)。如果CountDownLatch初始化的count值为1,那么这就退化为一个单一事件了,即是由一个线程来通知其他线程,效果等同于对象的wait和notifyAll,count值大于1是常用的方式,目的是为了让多个线程到达各自的预期状态,变为一个事件进行通知,线程则继续自己的行为。

CountDownLatch个重要的方法说明

countDown()

  1. public void countDown()
  2. 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。
  3. 如果当前计数等于零,则不发生任何操作。

await()
  1. public boolean await(long timeout,
  2. TimeUnit unit)
  3. throws InterruptedException
  4. 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:
  5. 1.由于调用 countDown() 方法,计数到达零;
  6. 2.或者其他某个线程中断当前线程;
  7. 3.或者已超出指定的等待时间。如果计数到达零,则该方法返回 true 值。

  8. 另外还要注意的几点,如果当前线程:
  9. 1.在进入此方法时已经设置了该线程的中断状态;
  10. 2.或者在等待时被中断,则抛出 InterruptedException,并且清除当前线程的已中断状态。如果超出了指定的等待时间,则返回值为 false。如果该时间小于等于零,则此方法根本不会等待。
  11. 参数:
  12. timeout - 要等待的最长时间
  13. unit - timeout 参数的时间单位。
  14. 返回:
  15. 如果计数到达零,则返回 true;如果在计数到达零之前超过了等待时间,则返回 false
  16. 抛出:
  17. InterruptedException - 如果当前线程在等待时被中断


看一个例子:

private static class WorkThread extends Thread
{
private CountDownLatch cdl;
private int sleepSecond; public WorkThread(String name, CountDownLatch cdl, int sleepSecond)
{
super(name);
this.cdl = cdl;
this.sleepSecond = sleepSecond;
} public void run()
{
try
{
System.out.println(this.getName() + "启动了,时间为" + System.currentTimeMillis());
Thread.sleep(sleepSecond * 1000);
cdl.countDown();
System.out.println(this.getName() + "执行完了,时间为" + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
} private static class DoneThread extends Thread
{
private CountDownLatch cdl; public DoneThread(String name, CountDownLatch cdl)
{
super(name);
this.cdl = cdl;
} public void run()
{
try
{
System.out.println(this.getName() + "要等待了, 时间为" + System.currentTimeMillis());
cdl.await();
System.out.println(this.getName() + "等待完了, 时间为" + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
} public static void main(String[] args) throws Exception
{
CountDownLatch cdl = new CountDownLatch(3);
DoneThread dt0 = new DoneThread("DoneThread1", cdl);
DoneThread dt1 = new DoneThread("DoneThread2", cdl);
dt0.start();
dt1.start();
WorkThread wt0 = new WorkThread("WorkThread1", cdl, 2);
WorkThread wt1 = new WorkThread("WorkThread2", cdl, 3);
WorkThread wt2 = new WorkThread("WorkThread3", cdl, 4);
wt0.start();
wt1.start();
wt2.start();
}

看一下运行结果:

DoneThread2要等待了, 时间为1444563077434
DoneThread1要等待了, 时间为1444563077434
WorkThread1启动了,时间为1444563077434
WorkThread3启动了,时间为1444563077435
WorkThread2启动了,时间为1444563077435
WorkThread1执行完了,时间为1444563079435
WorkThread2执行完了,时间为1444563080435
WorkThread3执行完了,时间为1444563081435
DoneThread1等待完了, 时间为1444563081435
DoneThread2等待完了, 时间为1444563081435

效果十分明显,解释一下:

1、启动2个线程DoneThread线程等待3个WorkThread全部执行完

2、3个WorkThread全部执行完,最后执行完的WorkThread3执行了秒符合预期

3、后三句从时间上看几乎同时出现,说明CountDownLatch设置为3,WorkThread3执行完,两个wait的线程马上就执行后面的代码了

这相当于是一种进化版本的等待/通知机制,它可以的实现的是多个工作线程完成任务后通知多个等待线程开始工作,之前的都是一个工作线程完成任务通知一个等待线程或者一个工作线程完成任务通知所有等待线程。


使用场景:

在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。 这个时候就可以使用CountDownLatch。CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。


CountDownLatch其实是很有用的,特别适合这种将一个问题分割成N个部分的场景,所有子部分完成后,通知别的一个/几个线程开始工作。比如我要统计C、D、E、F盘的文件,可以开4个线程,分别统计C、D、E、F盘的文件,统计完成把文件信息汇总到另一个/几个线程中进行处理

参考另一个实例

  1. public class CountDownLatchTest {
  2. // 模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。
  3. public static void main(String[] args) throws InterruptedException {
  4. // 开始的倒数锁
  5. final CountDownLatch begin = new CountDownLatch(1);
  6. // 结束的倒数锁
  7. final CountDownLatch end = new CountDownLatch(10);
  8. // 十名选手
  9. final ExecutorService exec = Executors.newFixedThreadPool(10);
  10. for (int index = 0; index < 10; index++) {
  11. final int NO = index + 1;
  12. Runnable run = new Runnable() {
  13. public void run() {
  14. try {
  15. // 如果当前计数为零,则此方法立即返回。
  16. // 等待
  17. begin.await();
  18. Thread.sleep((long) (Math.random() * 10000));
  19. System.out.println("No." + NO + " arrived");
  20. } catch (InterruptedException e) {
  21. } finally {
  22. // 每个选手到达终点时,end就减一
  23. end.countDown();
  24. }
  25. }
  26. };
  27. exec.submit(run);
  28. }
  29. System.out.println("Game Start");
  30. // begin减一,开始游戏
  31. begin.countDown();
  32. // 等待end变为0,即所有选手到达终点
  33. end.await();
  34. System.out.println("Game Over");
  35. exec.shutdown();
  36. }
  37. }

输出结果

  1. Game Start
  2. No.9 arrived
  3. No.6 arrived
  4. No.8 arrived
  5. No.7 arrived
  6. No.10 arrived
  7. No.1 arrived
  8. No.5 arrived
  9. No.4 arrived
  10. No.2 arrived
  11. No.3 arrived
  12. Game Over

可以看得出来 CountDownLatch 还是很有用的,在某些场合,一部分线程完成 之后 通知另一部分线程 , 更加灵活

java 多线程 27 :多线程组件之CountDownLatch的更多相关文章

  1. Java多线程20:多线程下的其他组件之CountDownLatch、Semaphore、Exchanger

    前言 在多线程环境下,JDK给开发者提供了许多的组件供用户使用(主要在java.util.concurrent下),使得用户不需要再去关心在具体场景下要如何写出同时兼顾线程安全性与高效率的代码.之前讲 ...

  2. java并发与多线程面试题与问题集合

    http://www.importnew.com/12773.html     https://blog.csdn.net/u011163372/article/details/73995897    ...

  3. Java中使用多线程、curl及代理IP模拟post提交和get访问

    Java中使用多线程.curl及代理IP模拟post提交和get访问 菜鸟,多线程好玩就写着玩,大神可以路过指教,小弟在这受教,谢谢! 更多分享请关注微信公众号:lvxing1788 ~~~~~~ 分 ...

  4. Java中使用多线程、curl及代理IP模拟post提交和get訪问

    Java中使用多线程.curl及代理IP模拟post提交和get訪问 菜鸟,多线程好玩就写着玩.大神能够路过不吝赐教.小弟在这受教.谢谢! 很多其它分享请关注微信公众号:lvxing1788 ~~~~ ...

  5. Java中的多线程技术全面详解

    本文主要从整体上介绍Java中的多线程技术,对于一些重要的基础概念会进行相对详细的介绍,若有叙述不清晰或是不正确的地方,希望大家指出,谢谢大家:) 为什么使用多线程 并发与并行 我们知道,在单核机器上 ...

  6. 【转】Java中的多线程学习大总结

    多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的. 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: 上图中基本上囊括了Java中多线程 ...

  7. 关于java基础、多线程、JavaWeb基础、数据库、SSM、Springboot技术汇总

    作者 : Stanley 罗昊 本人自行总结,纯手打,有疑问请在评论区留言 [转载请注明出处和署名,谢谢!] 一.java基础 1.多态有哪些体现形式? 重写.重载 2. Overriding的是什么 ...

  8. 程序员Java架构师多线程面试题和回答解析

    当我们在Java架构师面试的过程中常见的多线程和并发方面的问题肯定是必不可少的一部分.那么在面试之前我们更应该多准备一些关于多线程方面的问题. 面试官只是想确信面试者有足够的Java线程与并发方面的知 ...

  9. Java基础技术多线程与并发面试【笔记】

    Java基础技术多线程与并发 什么是线程死锁? ​死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,我们就可以称 ...

随机推荐

  1. 自动化安装smokeping-2.6.11脚本

    自动化安装Smokeping-2.6.11脚本 一.目的 1.1 监控目的 为方便监测各数据中心网络状况,自定义全国各节点,从而发现网络异常,判断网络故障. 1.2 本文目的 快速部署Smokepin ...

  2. mysql my.cnf 配置建议

    mysql的配置文件是/etc/my.cnf,通过修改它来配置mysql. /etc/my.cnf来自以下文件: 如果你的内存≤64M,则复制/usr/local/share/mysql/my-sma ...

  3. JVM调优总结(一)-- 堆和栈的基本概念

    数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型.基本类型的变量保存原始值,即:他代表的值就是数值本身:而引用类型的变量保存引用值.“引用值”代表了某个对象的引用,而不是对象本身, ...

  4. Android MD5校验码的生成与算法实现

    在Java中,java.security.MessageDigest (rt.jar中)已经定义了 MD5 的计算,所以我们只需要简单地调用即可得到 MD5 的128 位整数.然后将此 128 位计 ...

  5. 会动的Tabbar

    项目搭建 一.设计模式首先呢,小Q采用传统的MVC的设计模式,优点我们再来啰嗦一下啊:1.多个视图可以对应一个模型.按MVC设计模式,一个模型对应多个视图,可以减少代码的复制及代码的维护量,一旦模型发 ...

  6. svn搭建本地服务端

    使用VisualSVN Server来完成,下载地址:https://www.visualsvn.com/server/download/ 我安装的版本是3.3.1,安装的时候选择了标准版本,另外一个 ...

  7. ext2文件系统 - mke2fs

      上一遍博文的重点其实将ext2整体的组织框架,我们知道了ext2文件系统由块组组成,每个块组里面的组织形式.我们甚至直接把超级块和组描述符里面的内容,用十六进制形式展现了出来.这篇博文主要讲述如何 ...

  8. C++虚函数工作原理

    一.虚函数的工作原理      虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数.典型情况下,这一信息具有一种被称为 vptr(virtual table poi ...

  9. 百度地图 隐藏百度地图Logo

    /* 隐藏百度Logo */ .BMap_cpyCtrl, .anchorBL {display: none; }

  10. nginx 反向代理说明

    1 在大型项目开发中,可能会有多个应用部署在不同机器上,如果想让用户访问单个域名或IP访问到这些应用,可以使用 nginx 的反向代理,将应用的地址通过 nginx 代理,用户通过访问 nginx 地 ...