前言

在多线程环境下,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. I/O事件处理模型之Reactor和Proactor 【转】

    http://blog.ddup.us/?p=280 这篇博客说的很清楚,赞一个: 在编写服务端软件的时候,如何处理各种I/O事件是其中很重要的一部分.在Unix Network Programmin ...

  2. HDU 1907 John (Nim博弈)

    John Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total Submis ...

  3. SpringBoot中Redis的set、map、list、value、实体类等基本操作介绍

    今天给大家介绍一下SpringBoot中Redis的set.map.list.value等基本操作的具体使用方法 上一节中给大家介绍了如何在SpringBoot中搭建Redis缓存数据库,这一节就针对 ...

  4. ML之监督学习算法之分类算法一 ——— 决策树算法

    一.概述 决策树(decision tree)的一个重要任务是为了数据中所蕴含的知识信息,因此决策树可以使用不熟悉的数据集合,并从中提取出一系列规则,在这些机器根据数据创建规则时,就是机器学习的过程. ...

  5. class-dump在osx 10.11以后安装方法

      当Mac升级了OSX 10.11后,配置class-dump的时候,会发现逆向书上推荐的class-dump存放目录/usr/bin,class-dump存放不进去,尝试过用sudo 还是不被允许 ...

  6. Swift 下标脚本

    前言 在访问一个数组实例的元素时,可以使用 Array[index] 的形式.在访问一个字典实例的元素时,可以使用 Dictionary[index] 的形式.这种方括号的形式就是 "下标脚 ...

  7. 【struts2】自定义更强大的logger拦截器

    Struts2自带的logger拦截器只是打印出了Action所对应的URL以及执行的方法名称,这对实际开发来说是肯定不够的.实际开发中为了调试方便,要记录的信息比较多,通常需要把这次请求相关的几乎所 ...

  8. win7怎么快速截取图片

    点击开始--运行或者winkey + r 键直接进入运行. 2 在输入框输入snippingtool,点击确定. 3 这就找到截图工具,如图. END 方法/步骤2   进入c盘--Windows-- ...

  9. Groovy 学习手册(2)

    二. 工具 1. 控制台 groovyConsole: Groovy 控制台是一个非常易于使用和简单的轻量级的编辑器.你可以在里面做很多事情. 在编辑器里面可以书写代码,Windows 下,按下Ctr ...

  10. 向量运算 与 JavaScript

    二维向量都包含两个值:方向(direction)及大小(magnitude)   这两个值可以表达出各种各样的物理特性来,比如力和运动.如两个物体间的碰撞检测.   向量的大小   虽说二维向量是对大 ...