您的位置:首页 > 编程语言 > Java开发

java 多线程 27 :多线程组件之CountDownLatch

2017-03-23 18:07 531 查看
前言在多线程环境下,JDK给开发者提供了许多的组件供用户使用(主要在java.util.concurrent下),使得用户不需要再去关心在具体场景下要如何写出同时兼顾线程安全性与高效率的代码。之前讲过的线程池、BlockingQueue都是在java.util.concurrent下的组件,Timer虽然不在java.util.concurrent下,但也算是。后两篇文章将以例子的形式简单讲解一些多线程下其他组件的使用,不需要多深刻的理解,知道每个组件大致什么作用就行。本文主要讲解的是CountDownLatchCountDownLatchCountDownLatch主要提供的机制是当多个(具体数量等于初始化CountDownLatch时count参数的值)线程都达到了预期状态或完成预期工作时触发事件,其他线程可以等待这个事件来触发自己的后续工作。值得注意的是,CountDownLatch是可以唤醒多个等待的线程的。通俗点讲用给定的计数初始化CountDownLatch设置一个值。由于调用了countDown()方法,所以在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,await的所有后续调用都将立即返回,调用一次countDown()计数count-1,直到为0时,会通知所有await的线程,注意,这里计数是不能被重置的,也就是只能使用通知一次[/b][/b]为了到达自己预期状态(初始化设置的值)的线程会调用CountDownLatch的countDown方法(该方法会在初始化的值上count-1),等待的线程会调用CountDownLatch的await方法(当count计数器到达0时调用)。如果CountDownLatch初始化的count值为1,那么这就退化为一个单一事件了,即是由一个线程来通知其他线程,效果等同于对象的wait和notifyAll,count值大于1是常用的方式,目的是为了让多个线程到达各自的预期状态,变为一个事件进行通知,线程则继续自己的行为。[/b][/b]CountDownLatch个重要的方法说明countDown()
publicvoidcountDown()

[code]递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。
如果当前计数等于零,则不发生任何操作。
[/code]await()
publicbooleanawait(longtimeout,
TimeUnitunit)
throwsInterruptedException
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回true值。
如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:1.由于调用countDown()方法,计数到达零;
2.或者
其他某个线程中断当前线程;3.或者已超出指定的等待时间。如果计数到达零,则该方法返回true值。
另外还要注意的几点,如果当前线程:
1.在进入此方法时已经设置了该线程的中断状态;
2.或者
在等待时被中断,则抛出InterruptedException,并且清除当前线程的已中断状态。如果超出了指定的等待时间,则返回值为false。如果该时间小于等于零,则此方法根本不会等待。
参数:
timeout-要等待的最长时间
unit-timeout参数的时间单位。
返回:
如果计数到达零,则返回true;如果在计数到达零之前超过了等待时间,则返回false
抛出:
InterruptedException-如果当前线程在等待时被中断
[/code]看一个例子:
privatestaticclassWorkThreadextendsThread{privateCountDownLatchcdl;privateintsleepSecond;publicWorkThread(Stringname,CountDownLatchcdl,intsleepSecond){super(name);this.cdl=cdl;this.sleepSecond=sleepSecond;}publicvoidrun(){try{System.out.println(this.getName()+"启动了,时间为"+System.currentTimeMillis());Thread.sleep(sleepSecond*1000);cdl.countDown();System.out.println(this.getName()+"执行完了,时间为"+System.currentTimeMillis());}catch(InterruptedExceptione){e.printStackTrace();}}}privatestaticclassDoneThreadextendsThread{privateCountDownLatchcdl;publicDoneThread(Stringname,CountDownLatchcdl){super(name);this.cdl=cdl;}publicvoidrun(){try{System.out.println(this.getName()+"要等待了,时间为"+System.currentTimeMillis());cdl.await();System.out.println(this.getName()+"等待完了,时间为"+System.currentTimeMillis());}catch(InterruptedExceptione){e.printStackTrace();}}}publicstaticvoidmain(String[]args)throwsException{CountDownLatchcdl=newCountDownLatch(3);DoneThreaddt0=newDoneThread("DoneThread1",cdl);DoneThreaddt1=newDoneThread("DoneThread2",cdl);dt0.start();dt1.start();WorkThreadwt0=newWorkThread("WorkThread1",cdl,2);WorkThreadwt1=newWorkThread("WorkThread2",cdl,3);WorkThreadwt2=newWorkThread("WorkThread3",cdl,4);wt0.start();wt1.start();wt2.start();}
看一下运行结果:
DoneThread2要等待了,时间为1444563077434DoneThread1要等待了,时间为1444563077434WorkThread1启动了,时间为1444563077434WorkThread3启动了,时间为1444563077435WorkThread2启动了,时间为1444563077435WorkThread1执行完了,时间为1444563079435WorkThread2执行完了,时间为1444563080435WorkThread3执行完了,时间为1444563081435DoneThread1等待完了,时间为1444563081435DoneThread2等待完了,时间为1444563081435
效果十分明显,解释一下:1、启动2个线程DoneThread线程等待3个WorkThread全部执行完2、3个WorkThread全部执行完,最后执行完的WorkThread3执行了秒符合预期3、后三句从时间上看几乎同时出现,说明CountDownLatch设置为3,WorkThread3执行完,两个wait的线程马上就执行后面的代码了这相当于是一种进化版本的等待/通知机制,它可以的实现的是多个工作线程完成任务后通知多个等待线程开始工作,之前的都是一个工作线程完成任务通知一个等待线程或者一个工作线程完成任务通知所有等待线程。使用场景:[/b]在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。这个时候就可以使用CountDownLatch。CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。CountDownLatch其实是很有用的,特别适合这种将一个问题分割成N个部分的场景,所有子部分完成后,通知别的一个/几个线程开始工作。比如我要统计C、D、E、F盘的文件,可以开4个线程,分别统计C、D、E、F盘的文件,统计完成把文件信息汇总到另一个/几个线程中进行处理参考另一个实例
publicclassCountDownLatchTest{
//模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。
publicstaticvoidmain(String[]args)throwsInterruptedException{
//开始的倒数锁
finalCountDownLatchbegin=newCountDownLatch(1);
//结束的倒数锁
finalCountDownLatchend=newCountDownLatch(10);
//十名选手
finalExecutorServiceexec=Executors.newFixedThreadPool(10);
for(intindex=0;index<10;index++){
finalintNO=index+1;
Runnablerun=newRunnable(){
publicvoidrun(){
try{
//如果当前计数为零,则此方法立即返回。
//等待
begin.await();
Thread.sleep((long)(Math.random()*10000));
System.out.println("No."+NO+"arrived");
}catch(InterruptedExceptione){
}finally{
//每个选手到达终点时,end就减一
end.countDown();
}
}
};
exec.submit(run);
}
System.out.println("GameStart");
//begin减一,开始游戏
begin.countDown();
//等待end变为0,即所有选手到达终点
end.await();
System.out.println("GameOver");
exec.shutdown();
}
}
[/code]输出结果
GameStart
No.9arrived
No.6arrived
No.8arrived
No.7arrived
No.10arrived
No.1arrived
No.5arrived
No.4arrived
No.2arrived
No.3arrived
GameOver
[/code]可以看得出来CountDownLatch还是很有用的,在某些场合,一部分线程完成之后通知另一部分线程,更加灵活[/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: