章节概览:
1、概述
CountDownLatch是一个用来控制并发的很常见的工具,它允许一个或者多个线程等待其他的线程执行完其操作。比如我需要统计多篇文章中出现不同单词的数量,我们会为每篇文章分配一个线程进行统计,统计完成之后,会保存一个单词统计列表。等所有的统计线程都执行完成以后,对这些统计出来的结果用一个线程去汇总,这就可以使用CountDownLatch。
2、CountDownLatchDemo 描述
案例中,有2个线程,thread1,thread2。 主线程为main函数的线程。通过countDownLatch控制工具。必须等到thread1,thread2,执行完成在去执行main函数线程,即: System.out.println(“hello world CountDownLatchDemo”);
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException{
CountDownLatch countDownLatch = new CountDownLatch(2);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//do something
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread .currentThread().getName() + " is done");
countDownLatch.countDown();
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//do something
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is done");
countDownLatch.countDown();
}
}, "thread2");
thread1.start();
thread2.start();
countDownLatch.await();
// 等待Thread1,Thread2执行完成以后,在去执行下面的任务
System.out.println("hello world CountDownLatchDemo");
}
}
输出结果:
thread1 is done
thread2 is done
hello world CountDownLatchDemo
3、CountDownLatch 构造函数和核心成员分析
public class CountDownLatch {
// 构造函数十分简单,就是输入需要等待的线程个数计数器count,去初始化同步策略
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// CountDownLatch 的同步策略方式,其实现是通过AQS队列同步器实现的
private final Sync sync;
}
4、Sync 源码分析
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) {
// 初始化设置state的状态值
setState(count);
}
// 获取当前的Count的值
int getCount() {
return getState();
}
// 获取共享锁
protected int tryAcquireShared(int acquires) {
// 判断当前状态是否为 0 ,如果是0 ,返回1 ,不是的话返回 -1
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;
}
}
}
5、countDown流程源码分析
5.1、释放当前持有的状态 java.util.concurrent.CountDownLatch#countDown
// 调用countDown方法,将当前的状态值count -1
// 当前有线程执行完成,将释放当前持有的状态标记
public void countDown() {
详情:java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared
sync.releaseShared(1);
}
5.2、 java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared
共享锁的释放,分为两个步骤进行释放。
- 步骤一:尝试去释放当前共享状态,返回释放结果
- 步骤二:如果返回结果为true,则唤醒后续节点
public final boolean releaseShared(int arg) {
// 释放当前持有的状态
// 参考:5.3、 java.util.concurrent.CountDownLatch.Sync#tryReleaseShared 尝试释放锁
if (tryReleaseShared(arg)) {
// 唤醒后续节点
doReleaseShared();
return true;
}
return false;
}
5.3、 java.util.concurrent.CountDownLatch.Sync#tryReleaseShared 尝试释放锁
释放当前持有的state的状态值
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
// 获取当前的state的状态值
int c = getState();
// 如果当前的状态值 c == 0,直接返回false,不进行后续节点的唤醒操作
if (c == 0)
return false;
// 获取下一个state的状态值
int nextc = c-1;
// 通过cas设置下一个状态的值
if (compareAndSetState(c, nextc))
// 如果下一个状态值为 0,说明当前调用线程是最后一个执行的线程,直接返回true,唤醒后续节点
return nextc == 0;
}
}
5.4、java.util.concurrent.locks.AbstractQueuedSynchronizer#doReleaseShared 唤醒后续节点
private void doReleaseShared() {
// 死循环,出口为当前 h = head节点,结束
// 出口可能存在的情况:
// 1、h == null, h == tail 即 head 为最后的一个节点,无后续需要唤醒的节点了
// h 依然持有当前线程,没有其他线程重置head节点
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
}
// 如果在修改head节点的过程中,有其他的线程获得了当前的资源,或者唤醒的线程获得当前资源
// 则需要重新设置head节点的属性,并且进行后续节点的唤醒
if (h == head) // loop if head changed
break;
}
}
6、await 流程源码分析
6.1、 java.util.concurrent.CountDownLatch#await()
public void await() throws InterruptedException {
// 获取共享可中断锁
sync.acquireSharedInterruptibly(1);
}
6.2、java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果当前线程被中断,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 获取共享锁
// 参考:6.3、java.util.concurrent.CountDownLatch.Sync#tryAcquireShared
if (tryAcquireShared(arg) < 0)
// 说明前置线程没有执行完成,需要进入等待状态
doAcquireSharedInterruptibly(arg);
}
6.3、java.util.concurrent.CountDownLatch.Sync#tryAcquireShared
protected int tryAcquireShared(int acquires) {
// 如果当前状态为0,则返回 1,否则返回-1
// 如果发返回为0,说明当前前置线程都已经执行完成,无需等待
// 如果返回 -1,说明前置线程没有执行完成,需要进入等待状态。等待前面的线程执行完成
return (getState() == 0) ? 1 : -1;
}
6.4、java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly
当前进行加入到AQS的队列同步器中。
这块代码和之前分析的读写锁的尝试获获取共享锁代码基本相同。唯一不同的是加了可中断的逻辑。
此处代码不做分析,如有疑问请参考:Java多线程之ReentrantReadWriteLock实现原理和源码分析(七)
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);
}
}
至此,CountDownLatch 源码基本分析完成
7、总结
CountDownLatch 源码分析完成,主要通过两个方法进行控制,分别为:countDown,await。其内部源码实现逻辑和读写锁有点相似。比如初始化的count的值,相当于初始化了写锁。如果写锁的个数不为0,说明一直被占用,不能唤醒。而await,相当于读锁,尝试去获取读锁,获取不到,加入同步器队列,等待唤醒。同时它也是个共享锁,如果获得唤醒机会机会的话,它会唤醒后续的所有等待节点。就是我们说的:它允许一个或者多个线程等待其他的线程执行完其操作。如果疑问欢迎讨论