更多JUC源码解读系列文章请持续关注​​JUC源码解读文章目录JDK8​​!



文章目录


前言

假设CountDownLatch从10开始倒数,就相当于一个大门有10把锁,只有10把锁都打开了,才能进门,否则都会被堵在门口。而调用countDown()就是在解锁,在没有解开所有锁之前调用await()就会阻塞。

CountDownLatch countDownLatch = new CountDownLatch(10);
countDownLatch.await();//主线程调用await()被阻塞(获取锁失败)
countDownLatch.countDown(); //10个worker线程执行完任务countDown(),当state减到0,主线程被唤醒

代码示例

模拟一个线程调用​​countDownLatch.await()​​​,另外10个线程调用​​countDownLatch.countDown()​​。

public class Test1071 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " invoke countDownLatch.await()");
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + ",被唤醒。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("countDown..");
countDownLatch.countDown();
}
}).start();
}
}
}

//控制台输出
Thread-0 invoke countDownLatch.await()
countDown..
countDown..
countDown..
countDown..
countDown..
countDown..
countDown..
countDown..
countDown..
countDown..
Thread-0,被唤醒。。

CountDownLatch基本结构

​CountDownLatch​​​代码结构要比​​ReentrantLock​​​和​​ReentrantReadWriteLock​​简单很多,功能也简单,就是基于AQS共享锁实现一个倒数器的功能。

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();
}

protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}

protected 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 void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

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();
}

public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}

​CountDownLatch.Sync​​​没有子类,无需区分公平锁和非公平锁。​​await()​​​调用了​​AQS​​​中的模板方法​​acquireSharedInterruptibly​​​,​​countDown()​​​调用了​​AQS​​​中的​​releaseShared​​​,所以只需要看看​​tryAcquireShared​​​和​​tryReleaseShared​​​在​​CountDownLatch​​中的具体实现。

CountDownLatch.Sync#tryAcquireShared

线程调用​​CountDownLatch#await()​​​,进而调用了​​AbstractQueuedSynchronizer#acquireSharedInterruptibly​​​可中断获取共享锁。从​​CountDownLatch.Sync#tryAcquireShared​​源码看出,只有state=0的时候,才能获取锁,否则进入队列阻塞。

线程调用​​CountDownLatch#await(long, java.util.concurrent.TimeUnit)​​​,进而调用了​​AbstractQueuedSynchronizer#tryAcquireSharedNanos​​​可超时中断获取共享锁。如果线程调用了​​CountDownLatch#await(long, java.util.concurrent.TimeUnit)​​,当时间到了,state还没有减到0,也会直接返回false,从而往下执行,不再阻塞。

protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}

CountDownLatch.Sync#tryReleaseShared

线程调用​​CountDownLatch#countDown​​​,释放共享锁(​​AbstractQueuedSynchronizer#releaseShared​​​),当所有的共享锁释放,则唤醒同步队列中调用​​CountDownLatch#await()​​的线程。

​tryReleaseShared​​​中CAS自旋对state做减法,当state减到0时返回true,方可继续调用​​doReleaseShared​​唤醒后继。

protected 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;
}
}

总结:


  • ​CountDownLatch​​​的原理很简单,调用​​await()​​​和​​countDown()​​​属于两种线程,简称为​​await​​​线程和​​countDown​​​线程,​​await​​​线程调用​​await()​​​相当于获取锁失败,所以会进入同步队列阻塞,​​countDown​​​线程调用​​countDown()​​​相当于释放锁,只有全部释放了​​state=0​​​,才会唤醒​​await​​线程。
  • ​new CountDownLatch(10)​​​初始化给一定数值,​​CountDownLatch#countDown​​​相当于倒数,倒数到0,唤醒​​await​​线程。
  • 若有多个​​await​​​线程被阻塞,因为是共享锁,所以一个​​wait​​​线程被唤醒获取锁后会接连唤醒其他阻塞的​​wait​​线程。

PS: ​如若文章中有错误理解,欢迎批评指正,同时非常期待你的评论、点赞和收藏。我是徐同学,愿与你共同进步!