1、CountDownLatch的作用:允许一个或多个线程等待其他线程完成操作。
意思就是使用CountDownLatch来阻塞一条或多条线程,当CountDownLatch的计数被线程减到0后,就唤醒阻塞的一条或多条线程。
2、CountDownLatch的使用案例:
案例一:使用一个计数为3的CountDownLatch 实例阻塞两条线程T1、T2,随机创建3条线程,每条线程去将计数-1,当3变成0的时候,就会唤醒阻塞的T1、T2线程。
public class CountDownLatchDemo {
//创建一个计数为3的CountDownLatch实例
public static final CountDownLatch countDownLatch = new CountDownLatch(3);
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
try {
//使用countDownLatch阻塞当前线程。
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1 wake........");
},"T1").start();
new Thread(()->{
try {
//使用countDownLatch阻塞当前线程。
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T2 wake........");
},"T2").start();
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//当前线程做计数-1操作,当3变成0的时候,阻塞的线程唤醒(可阻塞多个线程)。
countDownLatch.countDown();
}).start();
}
}
}
结果输出:
T1 wake........
T2 wake........
使用案例二:使用一个计数为1的CountDownLatch 去阻塞10条线程,使用main线程去将计数-1,就会唤醒10条线程。
public class CountDownLatchDemo1 {
public static final CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws InterruptedException {
for (int i=0;i<10;i++){
new Thread(()->{
try {
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"-----wake......");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T"+i).start();
}
Thread.sleep(1000L);
countDownLatch.countDown();
}
}
输出结果:
T0-----wake......
T3-----wake......
T9-----wake......
T2-----wake......
T6-----wake......
T5-----wake......
T1-----wake......
T7-----wake......
T4-----wake......
T8-----wake......
3、CountDownLatch的实现原理分析:
根据使用的方式我们可以看出,CountDownLatch类的await()方法阻塞线程、countDown()方法会将计数-1。当计数为0的时候唤醒被阻塞的所有线程。
单从CountDownLatch的表现出来的特性上来看,跟我们之前说的Condition特别类似,那么CountDownLatch的实现方式是使用Condition的等待队列来实现的吗?还有一种可能就是CountDownLatch也可能是使用AQS的同步队列来实现的,因为AQS也满足阻塞线程+唤醒线程的特性。
AQS 还是 Condition?????????
这个问题我们通过看源码的方式来确定一下:我们先看CountDownLatch的await()方法。
3.1、CountDownLatch的类结构:通过类结果其实我们就能看出其实CountDownLatch的实现是AQS同步器。
public class CountDownLatch {
//静态的内部同步类,实现了AQS
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 CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
//阻塞当前线程
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//将计数-1
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
3.2、CountDownLatch的构造函数:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
//设置AQS的state标记为CountDownLatch的计数
setState(count);
}
3.3、CountDownLatch的await()方法实现:
public void await() throws InterruptedException {
//使用同步器获取申请一个队列位置,构建的node 模式是shared 模式的即共享模式,这个过程会先检查是否中断。
sync.acquireSharedInterruptibly(1);
}
//接上面 sync.acquireSharedInterruptibly(1); 入参arg=1
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//先检查是否中断,如果是直接抛出InterruptedException异常。
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取一个资源,这个理主要是判断AQS中的state(CountDownLatch的计数)是否为0 ,如果为0返回1 否则返回-1.
if (tryAcquireShared(arg) < 0)
//如果CountDownLatch的计数不为0,那就以共享可中断模式获取资源,
doAcquireSharedInterruptibly(arg);
}
//接 doAcquireSharedInterruptibly(arg);
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//构建一个链表的节点模式是SHARED,如果需要初始化队列,那就初始化。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//获取AQS的head节点
final Node p = node.predecessor();
if (p == head) {
//如果是head节点,就判断CountDownLatch的计数是否为0
int r = tryAcquireShared(arg);
if (r >= 0) {
//如果CountDownLatch的计数是0,那就以迭代的形式去逐步唤醒队列中的所有线程。
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
/*如果不是head节点,或者是head节点但是CountDownLatch的计数不等于0,那就阻塞当
前线程,并将节点的waitState改为-1 SIGNAL状态。线程会阻塞在此处,等待
CountDownLatch的计数变为0后唤醒。唤醒后此处区分于ReentrantLock,因为唤醒后
此处不会自我阻塞。而是直接抛出异常。
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.4、上面跟踪到CountDownLatch.await()方法说道线程阻塞在parkAndCheckInterrupt()方法的地方等待被唤醒,接下来我们看看是如何唤醒的。我们来看看CountDownLatch.countDown()方法。
public void countDown() {
//使用同步来释放资源
sync.releaseShared(1);
}
//接 sync.releaseShared(1); 入参arg=1
public final boolean releaseShared(int arg) {
//将CountDownLatch的计数-1(AQS的state),当等于0的时候返回true,否则返回false
if (tryReleaseShared(arg)) {
//如果CountDownLatch的计数=0 那就去释放阻塞的线程。
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
/*自旋来唤醒head节点的next节点,上面我们说了当等待的线程节点,只要有一个呗唤醒,就会以迭
代的形式逐步唤醒后面的线程,也就是说前一个节点的线程唤醒后一个节点的线程。那么也就是
说,当某线程调用countDown()方法后发现计数为0 了,那么当前线程只会唤醒head节点的next节
点的线程,然后next节点的线程去唤醒下一个节点的线程,以次传递下去。
因此在此方法中只会唤醒head节点的next节点中的线程就会结束自旋。
*/
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;
}
}
3.5、上面跟踪到了doReleaseShared的方法中会唤醒head节点的next的线程,那么阻塞的线程就会允许,继续自己的下一次循环,我们来分析一下被唤醒的线程的下一次循环:
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//head节点的next节点的线程 来获取前一个节点,肯定是head了,没有疑问。
final Node p = node.predecessor();
if (p == head) {
//去判断countDownLatch的计数是否为0,当然是0了,这是我们的前提条件嘛,没有疑问。
int r = tryAcquireShared(arg);
if (r >= 0) {
//countDownLatch的计数为0了,去更换head节点,并唤醒下一个节点的线程,依次传递下去,直到将所有的线程唤醒位置。
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//通过最后一个调用countDown()的方法在此处唤醒了的head节点的next节点的线程,进入下一次循环。
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
到此CountDownLatch的原理分析结束。