倒计时器CountDownLatch是让一个或多个线程等待其他的线程执行完后再开始继续执行,是基于共享锁实现的。
话不多说,先看下怎么使用,下面是自己写的一个Demo
package com.pzx.test004;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss-");
static CountDownLatch countDownLatch = new CountDownLatch(5);
public static void main(String[] args) {
System.out.println(sdf.format(new Date())+Thread.currentThread().getName() + " started...");
MyThread myThread = new MyThread(countDownLatch);
for (int i=0; i<5; i++) {
Thread thread = new Thread(myThread);
thread.setName("t"+i);
thread.start();
}
System.out.println(sdf.format(new Date())+Thread.currentThread().getName() + " await...");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sdf.format(new Date())+Thread.currentThread().getName() + " continue...");
}
static class MyThread implements Runnable {
CountDownLatch cdl;
public MyThread(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
System.out.println(sdf.format(new Date())+Thread.currentThread().getName()+" start");
try {
System.out.println(sdf.format(new Date())+Thread.currentThread().getName()+" working");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cdl.countDown();
}
}
}
某一次输出结果:
2018-09-11 10:21:11-main started...
2018-09-11 10:21:11-t0 start
2018-09-11 10:21:11-t0 working
2018-09-11 10:21:11-t1 start
2018-09-11 10:21:11-t1 working
2018-09-11 10:21:11-t3 start
2018-09-11 10:21:11-t3 working
2018-09-11 10:21:11-main await...
2018-09-11 10:21:11-t2 start
2018-09-11 10:21:11-t2 working
2018-09-11 10:21:11-t4 start
2018-09-11 10:21:11-t4 working
2018-09-11 10:21:16-main continue...
从结果来看,main线程调用countdownlatch的await()后就在等待了,直到其他线程把倒计时数count(其实是AQS里的state)置0后才继续运行,也就是一定要调用至少5次countdown()方法。
接下来看下源码:
主要围绕await和countdown方法深入分析(其实也就是分析AQS共享锁部分和CountDownLatch重写的方法)
public class CountDownLatch {
// 内部类Sync,作为同步控制器,使用AQS的state来表示count
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
// 构造函数就是直接设置AQS的state的值
Sync(int count) {
setState(count);
}
// 获取state值
int getCount() {
return getState();
}
// 重写了AQS的方法,尝试获取共享锁,如果state等于0就相当于尝试获取成功
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 同样的重写AQS的方法,尝试释放共享锁,把state-1并设置,如果减到0了就true
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
// 一进去发现state=0,没什么好释放的,返回false
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
// 构造方法就是新建一个Sync对象,设置好AQS的state
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// await方法是取尝试获取共享锁,如果失败则使得当前线程阻塞,直到获取成功才返回
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// countDown方法是释放共享锁
public void countDown() {
sync.releaseShared(1);
}
}
// 看AQS里面的获取共享锁的方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果线程中断了,则清楚中断标志并抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取共享锁,也就是上面重写的方法,state==0才返回1,这时就代表可获取共享锁
// await相当于执行结束,这是当调用countdown()达到规定次数了才会state=0的,否则返回-1
if (tryAcquireShared(arg) < 0)
// 去获取共享锁
doAcquireSharedInterruptibly(arg);
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 以共享模式加入到CLH队列的尾巴上,返回加入的那个节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
// 开始自旋
for (;;) {
// 获取前置节点
final Node p = node.predecessor();
// 如果前置节点是头结点,那就去尝试获取锁
if (p == head) {
int r = tryAcquireShared(arg);
// state=0就代表可以获取了,设置当前节点为头结点
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);
}
}
// countdown方法主要是调用AQS的释放共享锁方法releaseShared
public final boolean releaseShared(int arg) {
// 尝试释放锁,见上面的重写的方法,tryReleaseShared返回true时,state已经为0了,
// 这时就要唤醒等待的线程了(即Demo中调用await方法的主线程)
if (tryReleaseShared(arg)) {
// 去释放共享锁
doReleaseShared();
return true;
}
return false;
}
// 释放共享锁,唤醒等待的线程
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果头结点处于需要唤醒后继节点的状态就设置它的waitStatus为0,然后唤醒h的后继
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒h的后继线程
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}