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

java并发编程系列之CountDownLatch的使用

2016-12-09 15:42 411 查看
说到定时器,我们就会想到java中的Timer。在jdk1.5中,也提供了一个类似定时器功能的类CountDownLatch,只不过两者有些区别,CountDownLatch类同一时刻只能由一个线程去操作也就是说,在多线程并发下,同时只能由其中的一个线程去操作这个计时器。CountDownLatch 的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如这个想要继续往下执行的任务调用一个 CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的 countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。看到这里,大家肯定想起了我们前面的那个搞活动的例子,上次是用CyclicBarrier来实现的,下面我们就用CountDownLatch来实现这个例子。示例代码如下:

/**
* 描述:CountDownLatch的使用示例
* @author chhliu
* 创建时间:2015-9-17 下午9:41:37
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 新建一个计数器,初始值为5
final CountDownLatch latch = new CountDownLatch(5);

/*
* 新建5个线程,此处线程数必须和CountDownLatch中设置的倒数个数一致,否则会出问题
* 如果此处设置成4的话,就会一直等待,直到第5个线程到来,但没有第5个线程,就会一直死等
*/
ExecutorService service = Executors.newFixedThreadPool(5);
// 提交5个任务
for (int i = 0; i < 5; i++) {
// 假设每隔1秒中,有一个人到达
Thread.sleep(1000);
service.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()
+ "到达园博园了!");
// 计数器减1
latch.countDown();
// 如果计数器不为0,则线程一直阻塞
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有的人都到达了……");
}
});
}
// 释放线程池资源
service.shutdown();
}
}
测试结果如下:

pool-1-thread-1到达园博园了!
pool-1-thread-2到达园博园了!
pool-1-thread-3到达园博园了!
pool-1-thread-4到达园博园了!
pool-1-thread-5到达园博园了!
所有的人都到达了……
所有的人都到达了……
所有的人都到达了……
所有的人都到达了……
所有的人都到达了……

从测试结果来看,每调用一次countDown方法,则计数器减1,并且线程会被阻塞,知道为0,所有的线程才被唤醒,继续往下走,记住,此处是一次唤醒全部的线程。下面我们来看下这个类的原型:

public class CountDownLatch {
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
public int tryAcquireShared(int acquires) {
return getState() == 0? 1 : -1;
}

public boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}

private final Sync sync;
// 构造方法,同时指定计数器的个数
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// 阻塞线程
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。如果当前计数大于零,则出于线程调度目的,将阻塞当前线程,且在超时或者被中断之前,该线程将一直处于阻塞状态:
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。 如果当前计数等于零,则不发生任何操作。
public void countDown() {
sync.releaseShared(1);
}
// 获取当前剩下的计数个数
public long getCount() {
return sync.getCount();
}
}

前面说到,如果线程数设置成4,而计数器设置成5,那么会发生死等的现象,如果我们不想死等,可以使用await(long timeout, TimeUnit unit)方法,中断被阻塞的线程,使整个流程继续往下走,测试结果如下:

pool-1-thread-1到达园博园了!
pool-1-thread-2到达园博园了!
pool-1-thread-3到达园博园了!
pool-1-thread-4到达园博园了!
所有的人都到达了……
pool-1-thread-1到达园博园了!
所有的人都到达了……
所有的人都到达了……
所有的人都到达了……
所有的人都到达了……

从测试结果来看,pool-1-thread-1这个线程被中断了,并且再一次的被启用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: