一.CountDownLatch的基本使用
CountDownLatch的应用:
- 使得主线程要等待所有子线程完成工作之后,再继续执行后面的代码
具体使用也很简单
- 所有子线程run方法的finally中调用 countDownLatch.countDown();
- 在开启所有子线程后,主线程中调用 countDownLatch.await();
public class TestCountDownLatch {
@Test
public void test() throws InterruptedException {
System.out.println("");
final CountDownLatch latch = new CountDownLatch(5);
LatchDemo latchDemo = new LatchDemo(latch);
long start = System.currentTimeMillis();
for(int i = 0; i < 5; i++){
new Thread(latchDemo).start();
}
latch.await(); // 只有latch 中的state = 0,这里才执行成功。
long end = System.currentTimeMillis();
System.out.println(" ");
System.out.println("花费时间" + (end - start));
}
}
class LatchDemo implements Runnable{
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch){
this.latch = latch;
}
@Override
public void run() {
try{
for(int i = 0; i < 50000; i++){
if(i % 2 == 0){
System.out.print(i + " ");
}
}
}finally {
latch.countDown(); // 每执行一个线程,就执行一次countDown,那么就会导致state-1;
}
}
}
二.CountDownLatch的源码解析
源码分析,一般都是通过使用方法开始进行分析的,首先分析初始化
CountDownLatch中的共享锁的具体实现是一个集成了AQS的Sync类
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
// 每个锁的具体实现,这里都是不同的
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 每个锁的具体实现,这里都是不同的
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
后面具体分析上面的方法,现在由使用的层面来分析源码
2.1 CountDownLatch 初始化
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
setState(count);
}
实际没有什么好分析的,初始化,就是设置设置共享锁的状态为count值,即初始化就指定了,这个锁里面有多少个线程占有了。
2.2 子线程中调用的countDownLatch.countDown()方法
这个方法,最终的目的是使得锁状态 state = state-1,下面来看是怎么实现的
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
/*
这里的tryReleaseShared(arg)的返回值,通过下面方法的分析,
可以得到,如果state == 0才会返回true,调用doReleaseShared()方法,
即只有最后一个线程调用的时候,才会返回true,然后唤醒队列中被阻塞的线程,
实际队列中只有调用了 await() 的被阻塞的主线程。
*/
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
上面的代码中,最重要的是CountDownLatch自己实现的tryReleaseShared(arg)方法
protected boolean tryReleaseShared(int releases) {
//通过自旋保证一定会释放
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
// 这里会把锁状态减1,达到了目的
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
当锁状态 state == 0 之后,就会调用 doReleaseShared();方法,这个方法的目的是唤醒等待队列中所有的共享节点。
2.3 主线程中的countDownLatch.await()
这个方法的目的是,等待所有子线程执行完,才返回,继续执行后面的代码
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//这里要关注的也是这个tryAcquireShared的方法
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
下面这个方法虽然是共享锁根据各自功能有不同实现,但是在AQS中规定了如果要重写这个方法对于返回值有个规定:
- 1.返回值小于0表示获取锁失败,需要进入等待队列。
- 2.如果返回值等于0表示当前线程获取共享锁成功,但它后续的线程是无法继续获取的,也就是不需要把它后面等待的节点唤醒。
- 3.如果返回值大于0,表示当前线程获取共享锁成功且它后续等待的节点也有可能继续获取共享锁成功,也就是说此时需要把后续节点唤醒让它们去尝试获取共享锁。
在CountDownLatch中没有那么复杂,就只有小于0和大于0
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
非常简短的代码,如果state == 0 就返回1,大于0,否则返回小于0
,即结合上面的判断,当子线程没有执行完的时候,主线程会去执行
doAcquireSharedInterruptibly方法,去队列中获取锁
// 这个方法也是基类AQS中自带的方法
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
// 仅在CountDownLatch中,这里主要是利用自旋,等待主线程
// 获取锁对象,在这里第一次如果获取锁失败后,就会被阻塞,直到
// 子线程调用CountDown()时,state变成0,主线程被重新唤醒,
//获取到锁,最后离开这个循环,继续执行主线程后面的代码
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.await()方法的时候,如果state != 0 就会阻塞,直到调用countDownLatch.countDown()使得state==0时才被唤醒,获取锁,继续执行后面的方法。