CountDownLatch

CountDownLatch

了解CounDownLatch

CountDownLatch是什么

CountDownLatch是Java1.5中被引入的,在java.util.concurrent包下的一个同步工具类,允许一个或多个线程等待,直到其他线程执行完后再执行

源码浅析

从CountDownLatch中可以看出,其核心技术就是AQS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class CountDownLatch {

// 内部类,继承AbstractQueuedSynchronizer抽象类。
// 作为CountDownLatch的同步控制,使用AQS的state表示计数
private static final class Sync extends AbstractQueuedSynchronizer {...}


private final Sync sync;


// CountDownLatch的构造器。
// count参数:
// 官方:必须在线程通过await()方法之前执行countDown()方法的次数
//
// 个人:count是一个用户传入的数值,CountDownLatch内部维护这个count(实际上是AQS在维 护),然后CountDownLatch通过countDown()方法里减少count的值,而await()方法 在count减少到0之前一直阻塞,到0时await()才继续执行。习惯用法就是 count表示线 程,当线程执行完之后调用countDown()方法将count减一,而要等待其他线程执行完才执 行的任务调用await()
public CountDownLatch(int count) {...}


// 官方文档:当前线程会一直等待阻塞,直到该latch倒数为0,除非该线程是中断的。
//
// 如果当前count是0,将立即返回
// 如果当前count大于0,那么当前线程将不会进行线程调度,而且直到以下两种情况发生之前一 直都处于休眠状态
// 1. 因为countDown()方法的调用,使得count方法达到0;
// 2. 其他线程中断了当前线程
//
// 如果当前线程:在方法中设置了中断状态,或者在等待的过程中,被中断了
// 将会抛出InterruptedException
//
// 个人理解:调用await的线程会因为count没有到0而被阻塞休眠,而一旦到0,该方法会立即返回
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {...}

// 官方文档:减少count值,如果count值到0的时候,将释放所有在等待的线程
// 如果当前count大于0,则直接递减,如果新的count为0,重新将所有等待线程进行线程调度
//
// 个人理解:emmmm就是将count减一,如果到0时将不能增加
public void countDown() {...}

// 官方文档:返回当前count值。(常用于调试和测试目的)
//
// 个人理解:字面意思
public long getCount() {...}


public String toString() {...}
}

Sync内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;

Sync(int count) {
setState(count);
}

int getCount() {
return getState();
}

// 重写了AQS中的tryReleaseShared(),当Sync调用的时候,将会调用此方法
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}

// 重写了AQS中的tryReleaseShared(),当Sync调用的时候,将会调用此方法
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;
}
}
}

继承了AQS里面的部分方法,同时重写了两个方法。作为CountDownLatch的同步控制。从CountDownLatch源码可以看出,核心是AQS,而调用AQS的是AQS的子类Sync。

await()方法

1
2
3
4
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

由代码可以看到实际执行方法将由Sync这个继承自AQS的内部类执行。而上面展示的tryAcquireShareNanos()是AQS里面实现的方法。

countDown()方法

1
2
3
public void countDown() {
sync.releaseShared(1);
}

该方法跟await()这个方法是组合一起用的,里面也是使用了AQS的方法releaseShared()

getCount()方法

1
2
3
public long getCount() {
return sync.getCount();
}

这个方法里面调用了Sync实例里面的方法getCount(),返回当前count的值。

CountDownLatch工作

上面简单地介绍了CountDownLatch的内部实现(emmm虽然大部分都交给了AOS的实现),那么回到CountDownLatch这个名字本身,为什么叫CountDownLatch?

CountDownLatch = Count + Down + Latch:计数 + 减 + 门闩(shuan 第一声)

门闩

从名字中可以看出重点就是count, latch。count指的是内部维护了用户指定的count值,latch指的是其这个类展现出来的特性像门闩一样。

结合其用途理解就是,有线程A1, A2, A3, … An,线程B。B必须在各种A之前完成。此时用CountDownLatch,一开始设置好count(count大小习惯上等于A任务的数量),然后同时将这个CountDownLatch设置给A,B任务(两者调用latch的区别就是A任务调用countDown(),而B任务调用awai()方法)。A任务在结束时调用countDown()方法,B任务一开始调用await()方法。此时latch就像一个门闩,在B执行之前,阻隔了所有A任务。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class TestCountDownLatch2 {

private static final int SIZE = 10;

public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(SIZE);
for (int i = 0; i < SIZE; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + " 开始执行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId() + "已到达终点");
latch.countDown();
}
}).start();
}

try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("全部线程已执行完毕");
}

}

执行结果:

执行结果

(结果说明:别问我为什么是顺序执行,虚拟机,操作系统不一样,运行结果就不一样)

上述代码中,main线程就是所说的B线程,而new出来的所有线程就是A线程,main线程中的打印语句(”全部线程已执行完毕”)等到所有10个线程执行完成了再执行。

参考资料

-------------本文结束感谢您的阅读-------------