目录
3 基于AbstractQueuedSynchronizer的实现
1 前言
本人使用jdk8版本。
CountDownLatch
允许一个或多个线程等待其他线程完成操作,作用与Thread.join()相似,join可以参考:Thread.join()原理。
2 功能介绍
在JDK 1.5之后的并发包中提供的 CountDownLatch
也可以实现 join
的功能,并且比join
的功能更多,示例如下:
public class CountDownLatchTest {
static CountDownLatch c=new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException{
new Thread(new Runnable(){
@Override
public void run(){
System.out.println(1);
c.countDown();
System.out.println(2);
c.countDown();
}
}).start();
c.await();
System.out.println("3");
}
}
上面代码输出结果为1,2,3。main线程会一直阻塞在c.await()上,直到线程中两次c.countDown()使构造方法中传入的2变成0。
CountDownLatch
的构造函数接收一个int
类型的参数作为计数器,如果你想等待N
个点完成,这里就传入N
。
当我们调用 CountDownLatch
的 countDown
方法时,N
就会 -1
,CountDownLatch
的 await
方法会阻塞当前线程,直到 N 变成零
。
由于 countDown
方法可以用在任何地方,所以这里说的 N
个点,可以是 N个线程,也可以是 1个线程里的N个执行步骤。用在多个线程时,只需要把这个 CountDownLatch
的引用传递到线程里即可。
如果有某个解析 sheet
的线程处理得比较慢,我们不可能让主线程一直等待,所以可以使用另外一个带指定时间的 await
方法—— await(long time,TimeUnit unit)
,这个方法等待特定时间后,就会不再阻塞当前线程。
3 基于AbstractQueuedSynchronizer的实现
CountDownLatch有一个静态内部类sync,它是AbstractQueuedSynchronizer子类,并针对自己的功能重写了AbstractQueuedSynchronizer的构造方法、getCount()、tryAcquireShared()、tryReleaseShared()。CountDownLatch.await()调用的是同步器的acquireSharedInterruptibly(),CountDownLatch.countDown()调用的是同步器的releaseShared()。
可以参考:AbstractQueuedSynchronizer共享式同步状态获取与释放。
3.1 同步器Sync
private final Sync sync;
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count); // 设置锁被获取的个数
}
int getCount() {
return getState(); // 获取锁被获取的个数
}
// 若锁的获取数位0时获取成功,否则获取失败且当前线程阻塞
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 将锁的获取数-1,当减至0时返回true且阻塞的线程会被唤醒,否则false
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
3.2 构造方法
初始化Sync属性,同时设置锁已被获取的个数。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
3.3 await方法
acquireSharedInterruptibly()会先调用重写的tryAcquireShared()来尝试获取锁,若锁的数量>0,会阻塞在该方法,直到锁的数量=0将当前线程从该方法唤醒。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
3.4 countDown方法
countDown()会尝试调用重写的tryReleaseShared()来释放锁,只有getCount() == 0时返回true,进而执行doReleaseShared()来将阻塞的线程唤醒。
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
4 一次性使用
从上面介绍可知,要想使用CountDownLatch完成相关功能,首先要在构造时传入一个int值,即Sync同步器锁的状态,当功能实现后,Sync锁的状态肯定是0,否则线程会一直阻塞。CountDownLatch中除了构造方法中可设置Sync锁的状态外没有提供其它任何方法来设置,加上Sync属性是private的,所以无法从外部访问。
因此,在使用CountDownLatch后,建议将指向CountDownLatch的引用设为null,这样方便GC。
5 阿里面试题
1.countdowlatch和cyclicbarrier的内部原理和用法,以及相互之间的差别(比如countdownlatch的await方法和countDown是怎么实现的)。