关联文章:
Java并发源码分析之AQS及ReentrantLock
Java并发源码分析之Semaphore
Java并发源码分析之ReentrantReadWriteLock
Java并发源码分析之Condition
Java并发源码分析之CyclicBarrier
工作原理概要
CountDownLatch是基于AQS框架实现的,在初始化时,会设置同步状态state的值,相当于一次获取了N个共享锁。主线程调用await()方法加入同步队列进行等待,然后子线程每次调用countDown()方法释放一个共享锁,直到共享锁的个数为0,表示释放锁完成,释放完成后再唤醒主线程。
类图
AbstractOwnableSynchronizer:抽象类,定义了独占锁线程,exclusiveOwnerThread
AbstractQueuedSynchronizer:抽象类,继AbstractOwnableSynchronizer,里面包含内部类Node
Sync:类,继承了AbstractQueuedSynchronizer,重写tryAcquireShared、tryReleaseShared方法
CountDownLatch:包含内部类Sync
使用demo
CountDownLatch适用于分步执行任务,使用多线程执行第一步,等待所有线程第一步执行完成,再执行第二步。例如,下载多个文件,使用多线程,每个线程下载一个,最后再将所有的文件打包成一个,非常适合使用countDownLatch。
public class CountDownLatchRunnable implements Runnable{
private CountDownLatch countDownLatch;
public CountDownLatchRunnable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
System.out.println("线程: " + Thread.currentThread().getName() + "开始执行第一步, Time:" + LocalDateTime.now());
int millis = (int) (Math.random() * 5000);
Thread.sleep(millis);
System.out.println("线程: " + Thread.currentThread().getName() + "执行第一步完成, Time:" + LocalDateTime.now());
this.countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
executor.execute(new CountDownLatchRunnable(countDownLatch));
}
try {
countDownLatch.await();
System.out.println("第一步执行完成, 线程: " + Thread.currentThread().getName() + "开始执行第二步, Time:" + LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行如下:
可以看到,最后一个线程执行完成之后,开始第二步的操作。
构造函数
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
// 设置同步状态state的值
setState(count);
}
CountDownLatch初始化中,设置了同步器Sync的同步状态state,相当于一开始同步器就持有count个共享锁。
在主线程中,开启子线程后,会执行await()方法进入同步队列等待。
CountDownLatch进入等待
1、CountDownLatch-await进入等待状态入口
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
2、AQS-acquireSharedInterruptibly加入队列中自旋等待申请锁
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果线程中断,抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 尝试申请共享锁
if (tryAcquireShared(arg) < 0)
// 申请失败,加入等待队列进行等待
doAcquireSharedInterruptibly(arg);
}
3、CountDownLatch-tryAcquireShared尝试申请共享锁
protected int tryAcquireShared(int acquires) {
// 如果state的值为0, 则认为获取到了锁
return (getState() == 0) ? 1 : -1;
}
由于CountDownLatch设置了state的值,在子线程释放共享锁完成之前,这里获取到的state的值不会为0。
4、AQS-doAcquireSharedInterruptibly加入同步队列中等待
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);
}
}
doAcquireSharedInterruptibly方法在Semaphore中解析过,这里不做赘述。在队列中自旋等待成功有效的第一个节点,然后执行tryAcquireShared方法获取共享锁,还会继续判断state是否为0,所有当所有子线程释放共享锁之后,主线程才会继续执行。
CountDownLatch释放共享锁
1、CountDownLatch-countDown释放共享锁入口
public void countDown() {
sync.releaseShared(1);
}
2、AQS-releaseShared释放共享锁
public final boolean releaseShared(int arg) {
// 尝试释放共享锁
if (tryReleaseShared(arg)) {
// 如果完全释放共享锁,唤醒同步队列中的节点
doReleaseShared();
return true;
}
return false;
}
3、CountDownLatch-tryReleaseShared尝试释放共享锁
protected boolean tryReleaseShared(int releases) {
// 自旋
for (;;) {
// 如果state的值大于0, 则认为可以继续释放锁
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
// 通过CAS将state - 1
if (compareAndSetState(c, nextc))
// 返回state是否为0,为0表示完全释放共享锁
return nextc == 0;
}
}
4、AQS-doReleaseShared唤醒同步队列中的节点
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;
}
if (h == head)
break;
}
}
doReleaseShared也在Semaphore中解析过,这里不做赘述。
对于文章中的demo来说,唤醒就是主线程。