CountDownLatch源码分析
CountDownLatch简介
简介:CountDownLatch是基于AQS共享锁的一个锁工具,用来线程同步问题,针对的是一个线程必须等待多个线程完成后才运行的场景,count就是代表线程的个数。
CountDownLatch基于AQS的共享锁,如果你对这块知识还不了解,可以看我之前写的文章:AQS源码详细分析,让你掌握AQS原理,独占锁、共享锁、Conditio
CountDownLatch举例
package juc;
import java.util.concurrent.CountDownLatch;
public class TestCountDownLatch {
public static void main(String[] args) {
final CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(){
@Override
public void run() {
System.out.println("线程1准备");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1结束");
countDownLatch.countDown();
}
}.start();
new Thread(){
@Override
public void run() {
System.out.println("线程2准备");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2结束");
countDownLatch.countDown();
}
}.start();
new Thread(){
@Override
public void run() {
try {
System.out.println("开始等待线程1、线程2");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1、线程2准备完毕");
}
}.start();
}
}
CountDownLatch源码分析
CountDownLatch结构
首先看下CountDownLatch的结构
从上图可以看出,CountDownLatch的构造方法只有一个CountDownLatch(int count)就是设置共享锁同时可以容纳的线程数。
CountDownLatch内部实现了一个Sync,Sync继承于AQS,并且是共享锁模式。
1、初始化
public CountDownLatch(int count) {
//如果小于0,抛出参数异常
if (count < 0) throw new IllegalArgumentException("count < 0");
//初始化共享锁AQS
this.sync = new Sync(count);
}
//将Sync从AQS继承的count,设置为count、
Sync(int count) {
setState(count);
}
2、await()
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);
}
protected int tryAcquireShared(int acquires) {
//等于0返回1,不等于0返回-1
return (getState() == 0) ? 1 : -1;
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//之前AQS时说过,将thread包装为Node入队,不再详细叙述
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);
}
}
await()十分简单,如果state==0,就返回,如果不等于0,就挂起,
相当于共享锁中的抢锁过程。
3、countDown()
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//释放锁
if (tryReleaseShared(arg)) {//当所有线程都调用countDown()后,state等于0,唤醒等待的线程,
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
// 利用CAS自旋,知道CAS将state-1,如果state为0,返回true,不为0,返回false。
//当所有线程都调用countDown()后,state等于0。
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
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;
}
}
unparkSuccessor(h)
unparkSuccessor(h)中会调用
LockSupport.unpark(s.thread);
唤醒线程,会回到之前await()阻塞的地方。
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//之前AQS时说过,将thread包装为Node入队,不再详细叙述
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())
//这里这里这里这里
//线程被唤醒,停止阻塞如果线程被中断的话,就抛出异常
//如果线程不被中断,就自旋去请求锁,请求锁成功然后返回
//await调用完成,返回
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
总结
从上述代码可以看出,一个线程调用countDown()会将state-1。
await的作用就是当state!=0的时候,线程会阻塞,也就是当count个线程成功调用countDown()后,state会等于0,await()会停止阻塞。
所以说CountDownLatch可以适用于一个线程等待若干个线程的场景。
TIP
1、当线程调用countDown()后,不会阻塞,会继续执行代码。
2、CountDownLatch只能使用一次,不可重复利用。
如果你了解了AQS的共享锁的原理,理解CountDownLatch毫无压力。
AQS里面还是有很多经典的思想的,感兴趣的同学可以看我之前写的AQS的文章:AQS源码详细分析,让你掌握AQS原理,独占锁、共享锁、Conditio