CountDownLatch
我之前遇到过这么一个需求:“客户端同时下载视频、音频和大量试题压缩包”。我让线程池分配三个线程同时开启下载三类数据,等到它们都下载完成时再进行数据整合操作。问题来了,如何在没有线程安全问题情况下监听到这三个线程已经都执行完毕了呢?此时CountDownLatch类就是最佳选择。
CountDownLatch是一个同步的辅助工具类,允许一个或多个线程,等待其它一组线程完成操作,再继续执行。它其实就是一个倒计时同步锁。
class DownloadVideo implements Runnable{
private CountDownLatch latch;
public DownloadVideo(CountDownLatch latch){
this.latch = latch;
}
@Override
public void run() {
try {
//启动下载视频......
latch.countDown();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class DownloadAudio implements Runnable{
private CountDownLatch latch;
public DownloadVideo(CountDownLatch latch){
this.latch = latch;
}
@Override
public void run() {
try {
//下载音频......
latch.countDown();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class DownloadTestZip implements Runnable{
private CountDownLatch latch;
public DownloadVideo(CountDownLatch latch){
this.latch = latch;
}
@Override
public void run() {
try {
//下载音频......
latch.countDown();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class IntegrationData implements Runnable{
@Override
public void run() {
//创建CountDownLatch计数器,计数次数:3
CountDownLatch latch = new CountDownLatch(3);
Executor executor = Executors.newFixedThreadPool(3);
executor.execute(new DownloadVideo(latch));
executor.execute(new DownloadAudio(latch));
executor.execute(new DownloadTestZip(latch));
// 3个线程countDown()都执行之后才会释放当前线程,程序才能继续往后执行
latch.await();
//执行数据整合
}
}
CountDownLatch怎么理解呢?
CountDownLatch是在线程安全的条件下计数,没执行一次countDown方法计数器就会递减1,直到计数器为0,await方法以后的代码才会被执行。
使用情景举例:
玩LOL时,平台要求同组每位英雄都加载完成后,所有英雄才可以进入游戏环节。
CyclicBarrier
还是上面的项目实例,后来因为CountDownLatch我发现了CyclicBarrier这个类,对于这个项目实例CyclicBarrier也可以实现。
class DownloadVideo implements Runnable{
private CyclicBarrier cb;
public DownloadVideo(CyclicBarrier cb){
this.cb = cb;
}
@Override
public void run() {
try {
//启动下载视频......
cb.await();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class DownloadAudio implements Runnable{
private CyclicBarrier cb;
public DownloadAudio(CyclicBarrier cb){
this.cb = cb;
}
@Override
public void run() {
try {
//下载音频......
cb.await();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class DownloadTestZip implements Runnable{
private CyclicBarrier cb;
public DownloadTestZip(CyclicBarrier cb){
this.cb = cb;
}
@Override
public void run() {
try {
//下载音频......
cb.await();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class IntegrationData implements Runnable{
@Override
public void run() {
//整合数据
}
}
CyclicBarrier cb = new CyclicBarrier(3,new IntegrationData());
//开启线程执行下载视频、下载音频和下载试题压缩包任务
Executor executor = Executors.newFixedThreadPool(3);
executor.execute(new DownloadVideo(cb));
executor.execute(new DownloadAudio(cb));
executor.execute(new DownloadTestZip(cb));
当下载视频、下载音频和下载试题压缩包线程都执行到await方法时,此时数据整合线程才会执行。
CountDownLatch与CyclicBarrier的区别
CountDownLatch:线程安全,内部维护一计数器,每执行一次downCount方法,计数器减一,await方法会阻塞线程执行,直到计数器为0时,才会继续await方法以后的代码。
CyclicBarrier:线程安全,内部维护一计数器,每执行一次await方法,计数器减一,await方法会阻塞当前所在线程,直到计数器为0时,会唤醒同一个Condition上所有线程,继续执行。new CyclicBarrier(3,new IntegrationData()),当使用两个参数的构造时,需要传一个线程,此时,计数器被减至0时,会执行这个线程,当线程执行完毕之后,才会唤醒同一个Condition上所有线程继续执行。
//CyclicBarrier的dowait方法,await方法中调用了dowait方法。关键代码就在这个方法中。
private int dowait(boolean timed, long nanos)
//线程同步锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
//计数器减一
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
//如果有两个参数的构造传了线程,那么此时就执行这个线程。
if (command != null)
command.run();
ranAction = true;
//生命代切换
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
......
//以上执行完毕
for (;;) {
try {
//唤醒Condition上所有线程
if (!timed)
trip.await();
//唤醒超时时间
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
Thread.currentThread().interrupt();
}
}
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}