Java多线程系列(九)—CountDownLatch源码分析
CountDownLatch是多线程辅助类,主要用作栅栏功能,也就是让一个线程等待其他线程完成之后再执行;
(0) CountDownLatch的实例
class MyThread2 extends Thread{
private CountDownLatch cd;
public MyThread2(String name,CountDownLatch cd){
super(name);
this.cd = cd;
}
@Override
public void run() {
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" is countDownLatch");
cd.countDown();
}
}
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch cd = new CountDownLatch(2);
MyThread2 t1 = new MyThread2("t1",cd);
MyThread2 t2 = new MyThread2("t2",cd);
t1.start();
t2.start();
try {
cd.await();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("main function is going to continue");
}
}
输出结果
t1 is countDownLatch
t2 is countDownLatch
main function is going to continue
(1)countdownlatch作用和应用场景
CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁await()上等待的线程就可以恢复执行任务。
CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。
CountDownLatch的关键在于多线程调用CountDown()方法,而CountDown()方法是原子操作
(2)countdownlatch主要函数
CountDownLatch(int count)
构造一个用给定计数初始化的 CountDownLatch。
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()
(3)CountDownLatch语义实现
1. CountDownLatch类的结构
- CountDownLatch类的底层实现依赖于内部类Sync,Sync继承于同步器AQS;
2. CountDownLatch类的初始化
- 初始化countdownlatch线程计数器实例,设定线程数目
CountDownLatch latch = new CountDownLatch(3); //设置线程计数器的初始值为3
- CountDownLatch构造器函数传值设置AQS的state值,进而通过共享锁的个数实现线程计数器
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
- Sync类的初始化传值,设定Sync类的state的值为count
Sync(int count) {
setState(count);
}
protected final void setState(long newState) {
thsi.state = newState;
}
3. CountDownLatch通过await()方法阻塞需要等待的线程
- await()方法调用流程
- 调用await()方法阻塞当前线程
countDownLatch.await()
- await()方法调用Sync类的acquireSharedInterruptibly()方法实现线程的阻塞;
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
- 调用tryAcquireShared()判断到达栅栏情况,如果其他线程都已经执行完到达栅栏则返回1,否则返回-1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
- 如果还有线程未到达栅栏则调用doAcquireSharedInterruptibly()让当前线程一直等待,直到获取成功
private void doAcquireSharedInterruptibly(long arg)
throws InterruptedException {
// 创建"当前线程"的Node节点,且Node中记录的锁是"共享锁"类型;并将该节点添加到CLH队列末尾。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
// 获取上一个节点。
// 如果上一节点是CLH队列的表头,则调用tryAcquireShared()判断到达栅栏情况
final Node p = node.predecessor();
if (p == head) {
long r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// (上一节点不是CLH队列的表头) 当前线程一直等待,直到被唤醒
// 如果线程在等待过程中被中断过,则再次中断该线程(还原之前的中断状态)。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4. CountDownLatch通过countDown()方法将栅栏状态state减一
- countDown()方法执行流程
- 调用countDown()方法将栅栏状态state减一
countDownLatch.countDown()
- countDown()方法调用Sync类的releaseShared()方法
public void countDown() {
sync.releaseShared(1);
}
- 调用releaseShared()方法,判断当前state状态,如果栅栏state已经为0,则调用doRelease()方法释放阻塞的线程
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- 调用tryReleaseShared()方法判断栅栏state状态
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;
}
}
- 调用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;
}
}
5. CountDownLatch和CyclicBarrier区别
CountDownLatch是一个线程等待多个线程完成某个事情之后才会执行;CyclicBarrier是多个线程相互等待,只有多个线程都到达同步点线程才能继续运行;区别在于一个线程等待和多个线程相互等待;
CyclicBarrier初始时还可带一个Runnable的参数,此Runnable任务在CyclicBarrier的数目达到后,优先于其它被唤醒的线程提前被执行。
在底层实现上,CyclicBarrier的语义实现是基于互斥锁ReentrantLock和其对应的Condition;CountDownLatch语义的实现是通过内部类Sync;
总结
CountDownLatch实现了栅栏功能,也就是一个线程等待其他线程运行到栅栏点后才继续运行,主要通过await()方法和countDown()方法实现;
当一个线程调用await()方法时会判断栅栏state的状态,如果不为0则阻塞加入同步队列,其他线程执行countDown()方法时会将state减一并判断state的值是否为0,如果为0则唤醒所有阻塞在同步队列中的线程;