Java多线程编程中经常会碰到这样一种场景:某个线程需要等待一个或多个线程操作结束(或达到某种状态)才开始执行。比如裁判员需要等待运动员准备好后才发送开始指令,运动员要等裁判员发送开始指令后才开始比赛。

public class Player implements Runnable {

	private int id;
private CountDownLatch begin;
private CountDownLatch end; public Player(int i, CountDownLatch begin, CountDownLatch end) {
super();
this.id = i;
this.begin = begin;
this.end = end;
} @Override
public void run() {
try {
begin.await();// 等待begin的状态为0时开始
System.out.println("Play" + id + "开始时间:" + System.currentTimeMillis());
Thread.sleep((long) (Math.random() * 100));// 随机分配时间,即运动员完成时间
System.out.println("Play" + id + " arrived.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
end.countDown();// 使end状态减1,最终减至0
}
}
} public class CountDownLatchDemo {
private static final int PLAYER_AMOUNT = 5; public static void main(String[] args) {
//对于每位运动员,CountDownLatch减1后即结束比赛
CountDownLatch begin = new CountDownLatch(1);// 相当于裁判员
//对于整个比赛,所有运动员结束后才算结束
CountDownLatch end = new CountDownLatch(PLAYER_AMOUNT);
Player[] plays = new Player[PLAYER_AMOUNT]; for (int i = 0; i < PLAYER_AMOUNT; i++) {
plays[i] = new Player(i + 1, begin, end);
} // 设置特定的线程池,大小为5
ExecutorService exe = Executors.newFixedThreadPool(PLAYER_AMOUNT);
for (Player p : plays) {
exe.execute(p);// 分配线程
}
System.out.println("Race begins!");
begin.countDown();
try {
end.await();// 等待end状态变为0,即为比赛结束
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("Race ends!");
}
exe.shutdown();
}
}

程序运行结果如下:

Race begins!
Play1开始时间:1469438682994
Play4开始时间:1469438682994
Play2开始时间:1469438682994
Play3开始时间:1469438682994
Play5开始时间:1469438682994
Play3 arrived.
Play4 arrived.
Play1 arrived.
Play5 arrived.
Play2 arrived.
Race ends!

五个线程在main线程执行begin.countDown()后同时开始执行,每个线程执行完毕后都会执行end.countDown(),main线程等待end状态为0时停止执行。

再看一个例子:

public class CountDownLatchTest {
// 模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。
public static void main(String[] args) throws InterruptedException {
// 开始的倒数锁
final CountDownLatch begin = new CountDownLatch(1);
// 结束的倒数锁
final CountDownLatch end = new CountDownLatch(10);
// 十名选手
final ExecutorService exec = Executors.newFixedThreadPool(10);
for (int index = 0; index < 10; index++) {
final int NO = index + 1;
Runnable run = new Runnable() {
public void run() {
try {
// 如果当前计数为零,则此方法立即返回。
// 等待
begin.await();
Thread.sleep((long) (Math.random() * 10000));
System.out.println("No." + NO + " arrived");
} catch (InterruptedException e) {
} finally {
// 每个选手到达终点时,end就减一
end.countDown();
}
}
};
exec.submit(run);
}
System.out.println("Game Start");
// begin减一,开始游戏
begin.countDown();
// 等待end变为0,即所有选手到达终点
end.await();
System.out.println("Game Over");
exec.shutdown();
}
}

程序运行结果如下:

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

程序执行原理和前一个例子相同。

CountDownLatch工作原理相对简单,可以简单看成一个倒计时器,在构造方法中指定初始值,每次调用countDown()方法时讲计数器减1,而await()会等待计数器变为0。CountDownLatch关键接口如下

  • countDown() 如果当前计数器的值大于1,则将其减1;若当前值为1,则将其置为0并唤醒所有通过await等待的线程;若当前值为0,则什么也不做直接返回。
  • await() 等待计数器的值为0,若计数器的值为0则该方法返回;若等待期间该线程被中断,则抛出InterruptedException并清除该线程的中断状态。
  • await(long timeout, TimeUnit unit) 在指定的时间内等待计数器的值为0,若在指定时间内计数器的值变为0,则该方法返回true;若指定时间内计数器的值仍未变为0,则返回false;若指定时间内计数器的值变为0之前当前线程被中断,则抛出InterruptedException并清除该线程的中断状态。
  • getCount() 读取当前计数器的值,一般用于调试或者测试。

一个完整的比赛流程:

public class MyThread extends Thread {
private static final int THREAD_NUM = 10; private CountDownLatch comingTag;
private CountDownLatch waitTag;
private CountDownLatch waitRunTag;
private CountDownLatch beginTag;
private CountDownLatch endTag;
public MyThread(CountDownLatch comingTag, CountDownLatch waitTag, CountDownLatch waitRunTag, CountDownLatch beginTag, CountDownLatch endTag) {
super();
this.comingTag = comingTag;
this.waitTag = waitTag;
this.waitRunTag = waitRunTag;
this.beginTag = beginTag;
this.endTag = endTag;
} @Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "运动员正在赶往起点。。。");
Thread.sleep((int)(Math.random() * 10000));
comingTag.countDown();
System.out.println(Thread.currentThread().getName() + "等待裁判说准备。。。");
waitTag.await();
System.out.println(Thread.currentThread().getName() + "预备。。。");
Thread.sleep((int)(Math.random() * 10000));
waitRunTag.countDown();
beginTag.await();
System.out.println(Thread.currentThread().getName() + "起跑。。。" + System.currentTimeMillis());
Thread.sleep((int)(Math.random() * 10000));
System.out.println(Thread.currentThread().getName() + "到达终点。。。");
endTag.countDown();
} catch (Exception e) {
e.printStackTrace();
}
} public static void main(String[] args) {
try {
CountDownLatch comingTag = new CountDownLatch(THREAD_NUM);
CountDownLatch waitTag = new CountDownLatch(1);
CountDownLatch waitRunTag = new CountDownLatch(THREAD_NUM);
CountDownLatch beginTag = new CountDownLatch(1);
CountDownLatch endTag = new CountDownLatch(THREAD_NUM); MyThread[] threads = new MyThread[THREAD_NUM];
for (int i = 0; i < threads.length; i++) {
threads[i] = new MyThread(comingTag, waitTag, waitRunTag, beginTag, endTag);
threads[i].start();
}
System.out.println("裁判员正在等待选手到场。。。");
comingTag.await();
System.out.println("裁判员看到全部运动员已到场。。。准备5秒。。。");
Thread.sleep(5000);
waitTag.countDown();
System.out.println("各就各位,预备。。。");
waitRunTag.await();
System.out.println("发令枪响起。。。");
beginTag.countDown();
endTag.await();
System.out.println("所有运动员到达终点,比赛结束。。。");
} catch (Exception e) {
e.printStackTrace();
}
}
}

程序运行结果如下:

Thread-0运动员正在赶往起点。。。
Thread-4运动员正在赶往起点。。。
Thread-3运动员正在赶往起点。。。
Thread-2运动员正在赶往起点。。。
Thread-1运动员正在赶往起点。。。
Thread-6运动员正在赶往起点。。。
Thread-5运动员正在赶往起点。。。
Thread-7运动员正在赶往起点。。。
Thread-8运动员正在赶往起点。。。
裁判员正在等待选手到场。。。
Thread-9运动员正在赶往起点。。。
Thread-0等待裁判说准备。。。
Thread-5等待裁判说准备。。。
Thread-3等待裁判说准备。。。
Thread-7等待裁判说准备。。。
Thread-1等待裁判说准备。。。
Thread-6等待裁判说准备。。。
Thread-4等待裁判说准备。。。
Thread-9等待裁判说准备。。。
Thread-8等待裁判说准备。。。
Thread-2等待裁判说准备。。。
裁判员看到全部运动员已到场。。。准备5秒。。。
各就各位,预备。。。
Thread-0预备。。。
Thread-5预备。。。
Thread-3预备。。。
Thread-7预备。。。
Thread-4预备。。。
Thread-8预备。。。
Thread-6预备。。。
Thread-1预备。。。
Thread-2预备。。。
Thread-9预备。。。
发令枪响起。。。
Thread-4起跑。。。1469453409388
Thread-5起跑。。。1469453409388
Thread-6起跑。。。1469453409388
Thread-7起跑。。。1469453409388
Thread-2起跑。。。1469453409388
Thread-9起跑。。。1469453409388
Thread-0起跑。。。1469453409388
Thread-1起跑。。。1469453409388
Thread-8起跑。。。1469453409388
Thread-3起跑。。。1469453409388
Thread-6到达终点。。。
Thread-5到达终点。。。
Thread-2到达终点。。。
Thread-1到达终点。。。
Thread-3到达终点。。。
Thread-0到达终点。。。
Thread-9到达终点。。。
Thread-7到达终点。。。
Thread-8到达终点。。。
Thread-4到达终点。。。
所有运动员到达终点,比赛结束。。。

Java并发编程核心方法与框架-CountDownLatch的使用的更多相关文章

  1. Java并发编程核心方法与框架-Fork-Join分治编程(一)

    在JDK1.7版本中提供了Fork-Join并行执行任务框架,它的主要作用是把大任务分割成若干个小任务,再对每个小任务得到的结果进行汇总,这种开发方法也叫做分治编程,可以极大地利用CPU资源,提高任务 ...

  2. Java并发编程核心方法与框架-TheadPoolExecutor的使用

    类ThreadPoolExecutor最常使用的构造方法是 ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAli ...

  3. Java并发编程核心方法与框架-Semaphore的使用

    Semaphore中文含义是信号.信号系统,这个类的主要作用就是限制线程并发数量.如果不限制线程并发数量,CPU资源很快就会被耗尽,每个线程执行的任务会相当缓慢,因为CPU要把时间片分配给不同的线程对 ...

  4. Java并发编程核心方法与框架-CompletionService的使用

    接口CompletionService的功能是以异步的方式一边生产新的任务,一边处理已完成任务的结果,这样可以将执行任务与处理任务分离.使用submit()执行任务,使用take取得已完成的任务,并按 ...

  5. Java并发编程核心方法与框架-CyclicBarrier的使用

    CyclicBarrier类似于CountDownLatch也是个计数器,不同的是CyclicBarrier数的是调用了CyclicBarrier.await()进入等待的线程数,当线程数达到了Cyc ...

  6. Java并发编程核心方法与框架-ScheduledExecutorService的使用

    类SchedukedExecutorService的主要作用是可以将定时任务与线程池功能结合. 使用Callable延迟运行(有返回值) public class MyCallableA implem ...

  7. Java并发编程核心方法与框架-ExecutorService的使用

    在ThreadPoolExecutor中使用ExecutorService中的方法 方法invokeAny()和invokeAll()具有阻塞特性 方法invokeAny()取得第一个完成任务的结果值 ...

  8. Java并发编程核心方法与框架-Future和Callable的使用

    Callable接口与Runnable接口对比的主要优点是Callable接口可以通过Future获取返回值.但是Future接口调用get()方法取得结果时是阻塞的,如果调用Future对象的get ...

  9. Java并发编程核心方法与框架-Executors的使用

    合理利用线程池能够带来三个好处 降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行. 提高线程的可管理性.线程是稀 ...

随机推荐

  1. Too many connections解决方案

    原因:  my.ini 中设定的并发连接数太少或者系统繁忙导致连接数被占满. 连接数超过了 MySQL 设置的值,与 max_connections 和 wait_timeout  都有关. wait ...

  2. linux定时器(crontab)实例

    linux实验示例----实现每2分钟将“/etc”下面的文件打包存储到“/usr/lobal”目录下 ·Step1:编辑当前用户的crontab并保存终端输入:>crontab -u root ...

  3. perl 变量 $/ 的用法解析

    默认状态下,很显然都是用\n来区分行,\n也被我们称作为换行符.当读取序列时,按行来读取时,就是以换行符为标准. perl中"行"的概念就由$/决定. { $data = &quo ...

  4. Linux下的删除命令

    Linux:rm Windows:del rm parameter: -f, --force    忽略不存在的文件,从不给出提示.-i, --interactive 进行交互式删除-r, -R, - ...

  5. QIBO /do/jf.php EvilCode Execution Injected By /hack/jfadmin/admin.php

    catalog . 漏洞描述 . 漏洞触发条件 . 漏洞影响范围 . 漏洞代码分析 . 防御方法 . 攻防思考 1. 漏洞描述 这个漏洞的成因简单来说可以归纳为如下几点 . 类似于ECSHOP的的模版 ...

  6. BackGroundWorker控件的使用注意

    该控件有三个事件: DoWork .ProgressChanged 和 RunWorkerCompleted 在程序中调用RunWorkerAsync方法则会启动DoWork事件的事件处理,当在事件处 ...

  7. Myeclipse的show in breadcrumb

    m如何取消eclipse的show in breadcrumb 不小心点了show in breadcrumb,在编辑器界面上面多一层路径条,多余碍事,不晓得怎么取消,搞了半天终于弄好,方法如下: 点 ...

  8. python 集合、函数和文件操作

    1.set集合 set集合是一个无序.不可重复.可嵌套的序列,基本功能是进行成员关系测试和删除重复元素,可以使用大括号({})或者 set()函数创建集合,注意:创建一个空集合必须用 set() 而不 ...

  9. A.Kaw矩阵代数初步学习笔记 4. Unary Matrix Operations

    “矩阵代数初步”(Introduction to MATRIX ALGEBRA)课程由Prof. A.K.Kaw(University of South Florida)设计并讲授. PDF格式学习笔 ...

  10. FZU 1894 志愿者选拔(单调队列)

    传送门 Description 世博会马上就要开幕了,福州大学组织了一次志愿者选拔活动.参加志愿者选拔的同学们排队接受面试官们的面试.参加面试的同学们按照先来先面试并且先结束的原则接受面试官们的考查. ...