笔者最近在解析基于AQS的ReentrantLock实现,ReentrantLock是可重入的独占锁,今天解析一下juc包中的共享锁CountDownLatch实现,仅当笔记。
CountDownLatch是一种共享锁,区别于独占锁,共享锁在某一个时刻可能有多个线程同时访问共享资源,当指定的线程都完成操作后,CountDownLatch才允许后续的操作继续进行。就好像百米赛跑,只有所有选手都跑完全程,裁判才能记录选手成绩,在此之前,裁判只能等待。
CountDownLatch与ReentrantLock一样,都是基于AQS实现,AQS基本原理此处不再赘述,想了解的读者请看我的另一篇文章,本文只解析CountDownLatch的内容。
CountDownLatch的接口如下,
CountDownLatch在初始化时,会设置共享资源数目,该数目一旦设置,无其他接口能进行更改。线程同步时最主要使用的为await和countDown方法,需要等待其他线程完成共享资源访问的线程会调用await方法,将自身阻塞,而被等待的线程在完成自身的功能操作后,会调用countDown方法,将共享资源减1,当共享资源为0时,所有调用await的线程都将被唤醒,继续后续代码。
在具体实现上,CountDownLatch使用了AQS的基本框架,下图以countDown和await两个主要方法为例,解析了具体的调用流程。
在实现CountDownLatch时,需要实现的方法为tryReleaseShared和tryAcquireShared方法。两个方法分别表示线程完成共享资源访问的具体操作和判断等待线程是否可以被唤醒。由于访问的共享资源被设置为AQS的state值,所有两个函数实际上就是对state值的判断和加减。具体代码实现如下:
//共享式锁需要实现的AQS的方法,AQS只提供了throw的默认实现
@Override
protected int tryAcquireShared(int arg) {
return (getState() == 0) ? 1 : -1;
}
//共享式锁需要实现的AQS的方法,AQS只提供了throw的默认实现
@Override
protected boolean tryReleaseShared(int arg) {
for (; ; ) {
int c = getState();
if (c == 0)
return false;
int nextC = c - 1;
if (compareAndSetState(c, nextC))
return nextC == 0;
}
}
在实现一个CountDownLatch之后,笔者自己写了个测试用例进行测试,创建5个子线程SubDevice,这些各自子线程会各自睡觉一段时间,表示执行时间,而主线程会等待所有的子线程完成操作后,才输出相应的结果。
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatchImpl countDownLatch = new CountDownLatchImpl(5);
for (int i = 1; i <= 5; i++) {
new SubDevice((5 - i) * 1000, countDownLatch).start();
}
countDownLatch.await();
System.out.println("main thread:all device load done.");
}
private static class SubDevice extends Thread {
private final long processTime;
private final CountDownLatchImpl countDownLatch;
public SubDevice(long processTime, CountDownLatchImpl countDownLatch) {
this.processTime = processTime;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
Thread.sleep(processTime);
System.out.println(Thread.currentThread() + " load done.");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
总结:
在解析了CountDownLatch之后,发现该组件在一些某个线程需要等待其他一些线程的处理结果的应用场景中有比较大的作用,但是由于state值在CountDownLatch设置之后无法更改,在一次使用之后无法重置,难以重复利用,这应该也是它最明显的一个缺点。