CountDownLatch(闭锁)
综述
CountDownLatch作为同步工具类中的一种。它的功能极其简单,它允许一个或多个线程等待(进入阻塞状态),直到其他线程完成操作。与CyclicBarrier不同,CountDownLatch是等待事件,而CyclicBarrier是等待线程。以下是我写的两个示例。
示例
服务员与顾客问题
在我们去餐厅吃饭时,服务员不会在人没来齐之前上菜, 所以服务员会等到所有用户来到餐厅后才开始上菜。换到Java中可以将服务员和顾客看成线程,借助CountDownLatch可以很方便的实现该功能。
public class CountDownLatchTest {
public static void main(String[] args) {
CountDownLatch cdl = new CountDownLatch(5);
new Thread(new Waiter(cdl)).start();
for (int i = 0; i < 5; i++) {
new Thread(new Customer(i+"",cdl)).start();
}
}
private static class Waiter implements Runnable{
private final CountDownLatch countDownLatch;
public Waiter(CountDownLatch countDownLatch){
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
System.out.println("服务员等待顾客");
//服务员等待顾客
countDownLatch.await();
System.out.println("服务员开始上菜...");
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
}
private static class Customer implements Runnable{
private final String name;
private final CountDownLatch countDownLatch;
public Customer(String name,CountDownLatch countDownLatch){
this.name = name;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
//模拟顾客到餐厅所需的时间
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
} catch (InterruptedException exception) {
exception.printStackTrace();
}
//到达餐厅
countDownLatch.countDown();
System.out.println("顾客"+name+" 已到达餐厅");
}
}
}
运行结果如下:

由此可见,该测试运行成功,服务员成功在所有顾客到达之后才开始上菜。观察代码可以发现,除了我们CountDownLatch构造方法传入的参数5以外,我们仅仅只使用了CountDownLatch对象的两个方法。countDown()和await()。
详解
由以上示例可以发现,CountDownLatch对象中维护一个递减计数器,该计数器参数通过构造方法传入,通过调用countDown()方法来递减对象内部的计数器,直到计数器递减为0时,调用await()方法的线程将会被放行。在此之前,await()方法会让线程阻塞并能响应中断。

CountDownLatch的方法很少,暴露给外部类的方法只有await()以及带时间参数重载的await(),countDown()和getCount()。
void await()
将导致当前线程阻塞,直到CountDownLatch递减计数到零为止,除非该线程被中断。
如果当前count为零,则此方法立即返回。
如果当前count大于零,则出于线程调度目的,当前线程将被禁用,并且在发生以下两种情况之一之前,它处于休眠状态:
- 由于countDown方法的调用,计数达到零。 或者
- 其他一些线程中断当前线程。
如果当前线程:
- 在进入此方法时已设置其中断状态; 或者
- 在等待期间被打断,
然后抛出InterruptedException并清除当前线程的中断状态。
public void await() throws InterruptedException {
//调用sync对象的能够响应中断的获取共享许可
sync.acquireSharedInterruptibly(1);
}
boolean await(long timeout, TimeUnit unit)
除了拥有以上方法的功能外,在经过设置的时间之后,如果count仍然大于0,那么将返回false,否则立即返回true,也就是说,有线程调用此方法时,只会等待传入的时间,超时或者时间小于0,那么就立刻退出返回false。
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
void countDown()
当调用此方法时,count会尝试-1,如果-1之后count==0,那么将会重新调度所有等待的线程。如果count之前就等于0,那么调用此方法将会无事发生。
public void countDown() {
//释放共享许可
sync.releaseShared(1);
}
long getCount()
获取当前计数
public long getCount() {
return sync.getCount();
}
由此可以发现,CountDownLatch的方法都与Sync的类有关,而Sync类是CountDownLatch的内部类,它继承自AbstractQueuedSynchronizer(AQS),AQS是一个用于构建锁和同步器的框架,许多同步器都可以通过AQS轻易的构造出来,比如ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock等。
了解了AQS之后,再来看看CountDownLatch的内部类Sync
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
//实现的AQS中共享的方式获取
protected int tryAcquireShared(int acquires) {
//当count==0时返回值>1
return (getState() == 0) ? 1 : -1;
}
//实现的AQS中共享的方式释放
protected boolean tryReleaseShared(int releases) {
//以CAS的方式来尝试释放
for (;;) {
int c = getState();
//当count == 0直接返回false
if (c == 0)
return false;
//首先获取countDown之后的值
int nextc = c-1;
//当count的期望值是c时,将其进行countDown,防止多线程调用该方法时产生无效值
if (compareAndSetState(c, nextc))
//当countDown之后的值为0时,返回ture
return nextc == 0;
}
}
}
这里有个setState()和getState(),compareAndSetState方法,这是因为在AQS中管理着一个整数状态信息,该state以实现的同步器的不同有着不同的涵义,比如在CountDownLatch中,state用来表示当前的计数值,计数值为0时,将解除所有等待线程的阻塞。
//AbstractQueuedSynchronizer部分源码
/**
* The synchronization state.
*/
private volatile int state;
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
* @return current state value
*/
protected final int getState() {
return state;
}
/**
* Sets the value of synchronization state.
* This operation has memory semantics of a {@code volatile} write.
* @param newState the new state value
*/
protected final void setState(int newState) {
state = newState;
}
以下是countDown方法中调用的releaseShared
public final boolean releaseShared(int arg) {
//这里的tryReleaseShared方法是CountDownLatch中实现的
if (tryReleaseShared(arg)) {
//真正的释放操作,由AQS实现
doReleaseShared();
return true;
}
return false;
}
以下是await方法中调用的acquireSharedInterruptibly方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//如果当前线程被设置为中断,那么抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//这里的tryAcquireShared方法是CountDownLatch中实现的
if (tryAcquireShared(arg) < 0)
//真正的获取操作,由AQS实现
doAcquireSharedInterruptibly(arg);
}
总结
CountDownLatch是基于AQS实现的一个同步器,其内部的state被CountDownLatch当作计数器使用,并且使用共享的方式获取和释放state,await方法调用的acquireSharedInterruptibly使得能够响应中断,但是与CyclicBarrier栅栏不同的是,闭锁无法重置,也就是说每次使用闭锁,对象都需要重新建立。
未完待续…
CountDownLatch是一个同步工具类,用于线程间的等待。本文通过服务员与顾客的场景,展示了CountDownLatch如何实现线程等待。服务员在所有顾客到达前不会上菜,这对应于线程在计数器减至零前阻塞。CountDownLatch提供了await()和countDown()方法,前者使线程等待,后者减少计数。当计数为0时,所有等待的线程会被唤醒。此外,还介绍了内部类Sync如何基于AQS实现计数器的管理。

1686

被折叠的 条评论
为什么被折叠?



