CountDownLatch是一个同步辅助类,允许一个线程等待其他线程执行完毕再继续执行。比如现在有一个线程0在执行任务A,执行任务过程中需要等待其他线程1、线程2等先完成任务B任务C的执行,那么线程0会暂停任务A的执行,线程0阻塞,等到线程1线程2执行完毕时线程0才会继续执行。
CountDownLatch使用AQS实现线程同步,初始化时要设置一个count,表示所需要等待执行完毕的线程数。每个线程执行完毕调用countDown()方法对count减一,所有线程执行完毕时,count = 0,原线程被唤醒将继续执行。
新建一个Worker类:Worker类执行过程中睡眠3s再继续执行,执行完毕时在finally里执行countDown()方法。
public class Worker implements Runnable{
String workerName;
CountDownLatch countDownLatch;
public Worker(String workerName,CountDownLatch countDownLatch){
this.workerName = workerName;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println( workerName + " is working ...");
try {
// do something
Thread.sleep(3000);
System.out.println(workerName + "completed work!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
//System.out.println("worker not countDown !");
}
}
}
public class ThreadsTest {
public static void main(String[] args) throws InterruptedException {
int n = 3;
CountDownLatch latch = new CountDownLatch(n);
System.out.println("CountDownLatch test start......");
for(int i = 1; i <= n; i++){
String name = "worker-" + i;
Worker worker = new Worker(name,latch);
Thread thread = new Thread(worker);
thread.start();
}
latch.await();
System.out.println("CountDownLatch test end !");
}
}
查看执行效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xr0fX2gk-1598112671761)(B6445B1B8C374CD6930E52E601111703)]
如果我们不执行 latch.await()方法会怎样呢?这里先将该方法注释掉进行测试
// latch.await();
可以看到此时主线程新建3个worker线程后直接继续执行后面的操作,没有线程同步等待的效果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g1j2Pr5b-1598112671763)(709AFA4800ED407BB8ACDAADF8468AC6)]
finally {
//countDownLatch.countDown();
System.out.println("worker not countDown !");
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uUeFriRk-1598112671764)(48FD6F90B30D44909A36B60CB9050427)]
由于没有进行countDown操作,latch.await()会一直阻塞着主线程,导致后面操作无法得到执行。
这个过程到底是如何实现的呢,剖析源码窥探其内部原理
初始化
// CountDownLatch 类部分代码:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* 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;
}
}
源码的注释写的很清楚:Sync是CountdownLatch的同步控制器,而Sync是使用AQS实现的。
初始化AQS的同步变量state为线程数count,它是volatile修饰的状态变量。
countDown()方法:它调用了releaseShared()方法使得count–,达到线程调度的目的;根据源码注释可知当count = 0 时这个方法最终不会执行任何实际操作。
/**
/**
* Decrements the count of the latch, releasing all waiting threads if
* the count reaches zero.
*
* If the current count is greater than zero then it is decremented.
* If the new count is zero then all waiting threads are re-enabled for
* thread scheduling purposes.
*
*
If the current count equals zero then nothing happens.
*/
public void countDown() {
sync.releaseShared(1);
}
接着看releaseShared()方法,它是父类AQS的方法,调用其实现类的tryReleaseShared()方法进行判断,如果tryReleaseShared返回true则释放共享锁,并返回true,否则返回false。
// AQS releaseShared()方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 释放共享锁,后面再详细剖析该方法
doReleaseShared();
return true;
}
return false;
}
先看CountDownLatch实现的tryReleaseShared()方法:
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
// for (;;)表示一直循环直至return
for (;;) {
// 获取同步状态变量state
int c = getState();
// 同步变量为0说明当前count = 0,所有线程都已执行完毕直接返回false
if (c == 0)
return false;
int nextc = c-1;
// CAS 它是一个原子性的操作,利用CAS实现对state - 1的操作
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
通过源码分析可以知道,如果计数器count == 0,则返回false,这里的count指的就是同步状态变量state;如果不为0,则利用CAS的原子操作,执行count–操作,执行完毕返回 count == 0,也就是如果执行完毕count == 0 返回true,否则返回false。
当返回false时,所做的操作其实仅仅是对count执行减一的操作,而当返回true时,需要调用doReleaseShared()方法来释放共享锁。
分析doReleaseShared()方法之前先看一下 CountDownLatch的await()方法:
// CountDownLatch 类 await()方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// AQS类 acquireSharedInterruptibly()方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果线程被中断抛出异常
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
该方法实际的执行流程是先判断该线程是否被中断,如果被中断则抛出错误,如果没有被中断则调用 tryAcquireShared()尝试加共享锁的方法。如果当前 state == 0 说明未执行完毕的线程数为0,直接返回1,否则说明还有还有线程仍在执行中,返回 -1,继续执行doAcquireSharedInterruptibly()方法:
// CountDownLatch 类 tryAcquireShared()方法
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// AQS 类 doAcquireSharedInterruptibly()方法
/**
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 将自己加入等到队列中,阻塞当前线程
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/** 释放共享锁,唤醒等待队列中的线程
* Release action for shared mode -- signals successor and ensures
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
*/
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
这里用到了AQS的阻塞等待队列和CLH锁,不在这进行描述。其实现过程简单概括就是:将当前线程放入等待队列中,当所有线程执行完毕count == 0 时再唤醒当前线程继续执行后续逻辑。