文章目录
CountDownLatch
1. CountDownLatch是什么?
一般被称作"计数器",作用大致就是数量达到了某个点之后计数结束,才能继续往下走。可以用作流程控制之类的作用,大流程分成多个子流程,然后大流程在子流程全部结束之前不动(子流程最好是相互独立的,除非能很好的控制两个流程的关联关系),子流程全部结束后大流程开始操作。
2.CountDownLatch的用法
// 创建countDownLatch对象。
CountDownLatch countDownLatch = new CountDownLatch(5);
// 释放锁,将锁的数量-1,在子线程中设置。
countDownLatch.countDown();
// 获取锁,在主线程设置,进行阻塞。
countDownLatch.await();
使用Demo
public class CountDownLatchDemo {
private static final CountDownLatch countDownLatch = new CountDownLatch(5);
public static void main(String[] args) {
for (int i = 0; i < 5;i++){
new Thread(new CountDownLatchRunnable()).start();
}
try {
Thread.sleep(1000);
// 主线程会阻塞到此,直到所有的子线程都执行完
countDownLatch.await();
System.out.println("____________");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class CountDownLatchRunnable implements Runnable{
@Override
public void run() {
System.out.println("前"+ Thread.currentThread().getName());
countDownLatch.countDown();
System.out.println("后"+ Thread.currentThread().getName());
}
}
}
3.CountDownLatch的使用场景
1.实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
2.开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
3.死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
4.CountDownLatch的源码解读
4.1 CountDownLatch的构造方法入手
4.2 countDownLatch.await();获取锁
进入CountDownLatch类的tryAcquireShared方法。会执行tryAcquireShared(arg)方法,doAcquireSharedInterruptibly(arg)方法
4.2.1 tryAcquireShared();尝试获取锁
// 参数无用
protected int tryAcquireShared(int acquires) {
// 判断锁的状态是否等于0,如果等于0,说明获取到锁了。如果不等于0,获取不到锁。
return (getState() == 0) ? 1 : -1;
}
图解
4.2.2 doAcquireSharedInterruptibly();进入阻塞
// arg 在此基本无用
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);
// 这些逻辑在CountDownLatch无任何意义,AQS有很多代码复用,但是在此时没有意义。
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);
}
}
图解:
4.3 countDownLatch.countDown();释放锁
4.3.1 tryReleaseShared;尝试释放锁
// 尝试释放锁,参数无用
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
// 获取当前锁的数量
int c = getState();
// 如果再次锁的数量等于0,说明队列中已经没有被阻塞的线程了,无需唤醒。
if (c == 0)
return false;
// 否则锁的数量-1
int nextc = c-1;
// 设置成新的值
if (compareAndSetState(c, nextc))
// 再次判断是否等于0,此时等于0的话,需要去唤醒队列中的线程。不等于0不需要。
return nextc == 0;
}
}
图解:
4.3.2 doReleaseShared;尝试唤醒
private void doReleaseShared() {
// cas 自旋锁
for (;;) {
// 获取头部节点
Node h = head;
if (h != null && h != tail) {
//
int ws = h.waitStatus;
// 去唤醒阻塞节点,CountDownLatch 一般只有一个需要被唤醒的节点
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
// 在CountDownLatch基本无用
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
在CountDownLatch,此处带的作用,仅仅是唤醒阻塞的主线程。因为代码复用,所以很多代码基本无用。
4.CountDownLatch的执行流程
CountDownLatch 是保证子线程执行完后执行主线程。因为执行的时间不可确定,所以就有两种情况会发生。第一种,主线程被阻塞,子线程来唤醒。第二种,子线程直接执行完了,组线程没有去被阻塞。无论是哪种,都不影响最终的结果。