参考资料: http://www.cnblogs.com/dolphin0520/p/3920397.html 《Java编程思想》 http://www.itzhai.com/the-introduction-and-use-of-a-countdownlatch.html http://leaver.me/archives/3220.html http://developer.51cto.com/art/201403/432095.htm http://blog.csdn.net/yanhandle/article/details/9016329 http://blog.csdn.net/cutesource/article/details/5780740 http://www.cnblogs.com/whgw/archive/2011/09/29/2195555.html
一.CountDownLatch用法 CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。 CountDownLatch不可以重用,而CyclicBarrier可以重用
CountDownLatch类只提供了一个构造器:
1 public CountDownLatch (int count) { };
然后下面这3个方法是CountDownLatch类中最重要的方法:
1 2 3 public void await () throws InterruptedException { }; public boolean await (long timeout, TimeUnit unit) throws InterruptedException { }; public void countDown () { };
下面看一个例子大家就清楚CountDownLatch的用法了:
1 2 3 4 5 6 7 8 9 执行结果: 线程Thread-0正在执行 线程Thread-1正在执行 等待2个子线程执行完毕... 线程Thread-0执行完毕 线程Thread-1执行完毕 2个子线程已经执行完毕 继续执行主线程
二.CyclicBarrier用法 字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。 我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
CyclicBarrier类位于java.util.concurrent包下,CyclicBarrier提供2个构造器:
1 2 3 4 5 6 7 public CyclicBarrier (int parties, Runnable barrierAction) {} public CyclicBarrier (int parties) {} 参数parties指让多少个线程或者任务等待至barrier状态; 参数barrierAction为当这些线程都达到barrier状态时会执行的内容。
然后CyclicBarrier中最重要的方法就是await方法,它有2个重载版本:
1 2 public int await () throws InterruptedException, BrokenBarrierException { };public int await (long timeout, TimeUnit unit) throws InterruptedException,BrokenBarrierException,TimeoutException { };
第一个版本比较常用,用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;
第二个版本是让这些线程等待至一定的时间,如果还有线程没有到达barrier状态就直接让到达barrier的线程执行后续任务。
下面举几个例子就明白了:
假若有若干个线程都要进行写数据操作,并且只有所有线程都完成写数据操作之后,这些线程才能继续做后面的事情,此时就可以利用CyclicBarrier了:
1 2 3 4 5 6 7 8 9 10 11 12 线程Thread-0 正在写入数据... 线程Thread-3 正在写入数据... 线程Thread-2 正在写入数据... 线程Thread-1 正在写入数据... 线程Thread-2 写入数据完毕,等待其他线程写入完毕 线程Thread-0 写入数据完毕,等待其他线程写入完毕 线程Thread-3 写入数据完毕,等待其他线程写入完毕 线程Thread-1 写入数据完毕,等待其他线程写入完毕 所有线程写入完毕,继续处理其他任务... 所有线程写入完毕,继续处理其他任务... 所有线程写入完毕,继续处理其他任务... 所有线程写入完毕,继续处理其他任务...
从上面输出结果可以看出,每个写入线程执行完写数据操作之后,就在等待其他线程写入操作完毕。
当所有线程线程写入操作完毕之后,所有线程就继续进行后续的操作了。
如果说想在所有线程写入操作完之后,进行额外的其他操作可以为CyclicBarrier提供Runnable参数:
从结果可以看出,当四个线程都到达barrier状态后,会从四个线程中选择一个线程去执行Runnable。
下面看一下为await指定时间的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 线程Thread-0 正在写入数据... 线程Thread-2 正在写入数据... 线程Thread-1 正在写入数据... 线程Thread-2 写入数据完毕,等待其他线程写入完毕 线程Thread-0 写入数据完毕,等待其他线程写入完毕 线程Thread-1 写入数据完毕,等待其他线程写入完毕 线程Thread-3 正在写入数据... java.util.concurrent.TimeoutException Thread-1 所有线程写入完毕,继续处理其他任务... Thread-0 所有线程写入完毕,继续处理其他任务... at java.util.concurrent.CyclicBarrier.dowait(Unknown Source) at java.util.concurrent.CyclicBarrier.await(Unknown Source) at com.cxh.test1.Test$Writer.run(Test.java:58 ) java.util.concurrent.BrokenBarrierException at java.util.concurrent.CyclicBarrier.dowait(Unknown Source) at java.util.concurrent.CyclicBarrier.await(Unknown Source) at com.cxh.test1.Test$Writer.run(Test.java:58 ) java.util.concurrent.BrokenBarrierException at java.util.concurrent.CyclicBarrier.dowait(Unknown Source) at java.util.concurrent.CyclicBarrier.await(Unknown Source) at com.cxh.test1.Test$Writer.run(Test.java:58 ) Thread-2 所有线程写入完毕,继续处理其他任务... java.util.concurrent.BrokenBarrierException 线程Thread-3 写入数据完毕,等待其他线程写入完毕 at java.util.concurrent.CyclicBarrier.dowait(Unknown Source) at java.util.concurrent.CyclicBarrier.await(Unknown Source) at com.cxh.test1.Test$Writer.run(Test.java:58 ) Thread-3 所有线程写入完毕,继续处理其他任务...
上面的代码在main方法的for循环中,故意让最后一个线程启动延迟,因为在前面三个线程都达到barrier之后,等待了指定的时间发现第四个线程还没有达到barrier,就抛出异常并继续执行后面的任务。
另外CyclicBarrier是可以重用的,看下面这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 线程Thread-0 正在写入数据... 线程Thread-1 正在写入数据... 线程Thread-3 正在写入数据... 线程Thread-2 正在写入数据... 线程Thread-1 写入数据完毕,等待其他线程写入完毕 线程Thread-3 写入数据完毕,等待其他线程写入完毕 线程Thread-2 写入数据完毕,等待其他线程写入完毕 线程Thread-0 写入数据完毕,等待其他线程写入完毕 Thread-0 所有线程写入完毕,继续处理其他任务... Thread-3 所有线程写入完毕,继续处理其他任务... Thread-1 所有线程写入完毕,继续处理其他任务... Thread-2 所有线程写入完毕,继续处理其他任务... CyclicBarrier重用 线程Thread-4 正在写入数据... 线程Thread-5 正在写入数据... 线程Thread-6 正在写入数据... 线程Thread-7 正在写入数据... 线程Thread-7 写入数据完毕,等待其他线程写入完毕 线程Thread-5 写入数据完毕,等待其他线程写入完毕 线程Thread-6 写入数据完毕,等待其他线程写入完毕 线程Thread-4 写入数据完毕,等待其他线程写入完毕 Thread-4 所有线程写入完毕,继续处理其他任务... Thread-5 所有线程写入完毕,继续处理其他任务... Thread-6 所有线程写入完毕,继续处理其他任务... Thread-7 所有线程写入完毕,继续处理其他任务...
从执行结果可以看出,在初次的4个线程越过barrier状态后,又可以用来进行新一轮的使用。而CountDownLatch无法进行重复使用。
三.Semaphore用法 Semaphore翻译成字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
1 2 3 4 5 6 7 8 9 Semaphore类位于java.util.concurrent包下,它提供了2 个构造器: public Semaphore (int permits) { sync = new NonfairSync(permits); } public Semaphore (int permits, boolean fair) { sync = (fair)? new FairSync(permits) : new NonfairSync(permits); }
下面说一下Semaphore类中比较重要的几个方法,首先是acquire()、release()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void acquire () throws InterruptedException { }public void acquire (int permits) throws InterruptedException { }public void release () { }public void release (int permits) { } acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。 release()用来释放许可。注意,在释放许可之前,必须先获获得许可。 这4 个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法: public boolean tryAcquire () { };public boolean tryAcquire (long timeout, TimeUnit unit) throws InterruptedException { }; public boolean tryAcquire (int permits) { };public boolean tryAcquire (int permits, long timeout, TimeUnit unit) throws InterruptedException { };
另外还可以通过availablePermits()方法得到可用的许可数目。
下面通过一个例子来看一下Semaphore的具体使用:
假若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 工人0 占用一个机器在生产... 工人1 占用一个机器在生产... 工人2 占用一个机器在生产... 工人4 占用一个机器在生产... 工人5 占用一个机器在生产... 工人0 释放出机器 工人2 释放出机器 工人3 占用一个机器在生产... 工人7 占用一个机器在生产... 工人4 释放出机器 工人5 释放出机器 工人1 释放出机器 工人6 占用一个机器在生产... 工人3 释放出机器 工人7 释放出机器 工人6 释放出机器
总结: 1) CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行; 而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
2) Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。