1. 简介
CountDownLatch是一个简单的同步器,可以允许一个或多个线程等待其他的线程执行完毕后再执行后续的操作。
CountDownLatch其实和Thread.join()有点类似,等待其他线程执行完后再执行任务。
# CountDownLatch用法简介
CountDownLatch相当于一个计数器,初始化的时候赋予计数器一个值,每次调用countDown()方法,计数器的值就会减1,在计数器
未减到0时,如果在线程中执行了计数器的await()方法,就会等待计数器减为0,相当于陷入阻塞,之后其他线程执行完后调用countDown()方法
,让计数器减为0,之后线程流通,执行之后的任务,这也就是上面说的等待其他线程执行完后再执行任务。
2. 简单的使用
例子1:让一个线程等待多个线程执行完再执行
// 让main线程等待其他线程执行完后再执行
package CountDownLatch;
import java.util.concurrent.*;
public class Demo01 {
static int n = 5;
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(n); //CountDownLatch计数器,初始化为5
ExecutorService executors = Executors.newFixedThreadPool(n); //创建一个线程池,有5个线程
for(int i = 1; i <= n; i++){
final int tmp = i;
executors.execute(new RunnableImpl(i, latch)); //执行线程
}
long start = System.currentTimeMillis();
latch.await(); //让main线程在此等待,只有其他线程执行完后才可继续往下执行(计数器为0)
System.out.println("主线程等待所有子线程执行完成");
long end = System.currentTimeMillis();
System.out.println("等待时长:" + (end - start));
executors.shutdown(); // 关闭线程池
}
static class RunnableImpl implements Runnable{
CountDownLatch latch;
int id;
public RunnableImpl(int id, CountDownLatch latch){
this.id = id;
this.latch = latch;
}
public void run(){
try {
System.out.println("线程" + id + "开始执行任务");
TimeUnit.SECONDS.sleep(2);
System.out.println("线程" + id + "执行任务完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 子线程执行完后就让计数器减1,当计数器为0时有计数器await()的线程会执行
}
}
}
}
执行结果:
例子2:让多个线程同时开始
package CountDownLatch;
import java.util.concurrent.*;
public class Demo02 {
private static final int TASK_COUNT = 8;
private static final int THREAD_CORE_SIZE = 10;
static int n = 10;
static CountDownLatch latch1 = new CountDownLatch(1);
static CountDownLatch latch10 = new CountDownLatch(10);
public static void main(String[] args) throws InterruptedException {
for(int i = 1; i <= n; i++){
final int tmp = i;
new Thread(new RunnableImpl(tmp, latch1, latch10)).start();
}
//这里睡5秒是为了保证所有线程都进入,下面再用latch1.countDown()控制保证同时开始
TimeUnit.SECONDS.sleep(5);
latch1.countDown();
long mainWaitStartTimeMillis = System.currentTimeMillis();
latch10.await();
long mainWaitEndTimeMillis = System.currentTimeMillis();
System.out.println("所有子线程执行任务完成");
System.out.println("主线程等待时长:" + (mainWaitEndTimeMillis - mainWaitStartTimeMillis));
}
/**
* 工作线程
*/
static class RunnableImpl implements Runnable {
/**
* 任务id
*/
private int taskId;
/**
* CountDownLatch同步计数器
*/
private CountDownLatch latch1;
private CountDownLatch latch10;
@Override
public void run() {
try {
latch1.await(); //等待所有线程都准备好,之后main会放开闸门
System.out.println("线程" + taskId + "开始执行任务" + "开始执行时间" + System.currentTimeMillis());
TimeUnit.SECONDS.sleep(1);
System.out.println("线程" + taskId + "执行任务完成");
} catch (InterruptedException e) {
} finally {
latch10.countDown();
}
}
public RunnableImpl(int taskId, CountDownLatch latch1, CountDownLatch latch10) {
this.taskId = taskId;
this.latch1 = latch1;
this.latch10 = latch10;
}
}
}
结果:
CountDownLatch相当于一个栅栏的作用,相当于一个有n格血量的墙,每次调用countDown()射出一支箭墙的血量减1,只有当墙的血量减为0,调用了CountDownLatch的await的线程才可以继续往下执行。
3. CountDownLatch解析
3.1 结构
CountDownLatch只包含一个内部类Sync,没有锁的公平和非公平之分,Sync继承了AQS,CountDownLatch内部的锁是一个共享锁,也就是说多个线程可以同时调用。
3.2 源码解析
package java.util.concurrent;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
// 这个count就是初始化CountDownLatch传入的初始值
Sync(int count) {
// 将count的值赋给state,我们知道state这个变量是来控制加锁和解锁的
setState(count);
}
// 得到count的值,内部调用getState()获得state的值
int getCount() {
return getState();
}
// 尝试获取共享锁,当state为0返回1,当state为-1时就要排队
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();
// 当state为0,说明没有锁要释放,直接返回false
if (c == 0)
return false;
int nextc = c-1;
// 执行到这里说明有锁要释放,当state-1的值为0说明释放成功,不为0返回false表示并没完全释放
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
// 内部的Sync类,这个Sync继承了AQS
private final Sync sync;
// 构造方法
public CountDownLatch(int count) {
// count<0,抛出异常
if (count < 0) throw new IllegalArgumentException("count < 0");
// 初始化count,本质是初始化state
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));
}
// 调用AQS的releaseShared方法
public void countDown() {
sync.releaseShared(1);
}
// 获取state的值
public long getCount() {
return sync.getCount();
}
// 重写toString方法
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
3.2.1 await()方法
public void await() throws InterruptedException {
// 调用AQS的acquireSharedInterruptibly方法
sync.acquireSharedInterruptibly(1);
}
会调用AQS的acquireSharedInterruptibly方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquireShared返回1表示state为0没有获取到共享锁;返回-1表示state大于0获取到了共享锁
// 尝试获取共享锁,返回0说明state不为0,还有线程持有锁,之后调用doAcquireSharedInterruptibly方法
// 对于执行await的线程来说,在这里state为0,返回1,所以不会阻塞
// 这里主要的作用是如果state不为0时需要将线程包装成结点加入阻塞队列
if (tryAcquireShared(arg) < 0)
// 采用共享中断方式
doAcquireSharedInterruptibly(arg);
}
doAcquireSharedInterruptibly(int arg)方法
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 将调用await的线程包装成Node结点加入到AQS的阻塞队列中
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
// 获取当前节点的前驱结点
final Node p = node.predecessor();
// 前驱结点为head就说明当前节点有权利获取锁了
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// shouldParkAfterFailedAcquire会给当前节点找一个不是取消的前驱结点,并将该前驱结点的状态设为-1,
// 表示会唤醒后继结点
if (shouldParkAfterFailedAcquire(p, node) &&
// parkAndCheckInterrupt()会挂起当前线程
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.2.2 countDown()方法
public void countDown() {
//会调用AQS的releaseShared方法
sync.releaseShared(1);
}
releaseShared方法
public final boolean releaseShared(int arg) {
// 会调用CountDownLatch的尝试释放资源方法,释放资源后state为0返回true,state不为0返回false
if (tryReleaseShared(arg)) {
// 当state为0说明需要唤醒执行await方法的线程,这个时候头结点要唤醒后继结点
doReleaseShared();
return true;
}
return false;
}
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; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
4. 总结
-
CountDownLatch允许一个或多个线程等待其他线程执行完后再执行之后的任务。
-
CountDownLatch使用共享锁机制实现。
-
每次调用await会尝试获取锁,这是为了看看state是不是等于0。
-
调用countDown方法时会尝试释放锁,当state为0时会获取头结点也就是当前正在执行的线程然后唤醒后继结点。