并发编程-CountDownLatch与join

前言

最近比较闲,继续前阵子想学的东西-并发编程,还是从最基础的东西开始。在jdk1.5中提供了许多并发编程的类,今天来学西一个简单的辅助类-CountDownLatch。

CountDownLatch的概念

CountDownLatch是在java.util.concurrent包下用来实现多线程之间同步的工具类,可以理解为它间接的了实现了线程之间的通信功能。我们可以通过它来实现主线程等待一个线程组完成工作后继续执行,CountDownLatch通过主线程初始化一个计数器,然后等待子线程,各个子线程完成工作后执行计数器减一的操作,当计数器值减为0时,代表所有的子线程都已完成作业,则主线程可继续执行。

CountDownLatch的用法

CountDownLatch的构造器:

1
2
//初始化计数器值count
public CountDownLatch(int count) { };

CountDownLatch的三个核心方法

1
2
3
4
5
6
//调用此方法的线程会被挂起(应该是同步阻塞)它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };
//设置挂起的超时时间,如果超时,不管count值,主线程都会继续执行,这里超时会抛出异常,需要捕获,生产建议使用超时时间,避免死等造成内存溢出
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };

通过CountDownLatch实现两个子进程与主线程的同步

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
public static void main(String[] args) {
final int workerNum = 2;
final CountDownLatch latch = new CountDownLatch(workerNum);
for (int i=0;i<workerNum;i++){
new Worker(latch).start();
}
try {
System.out.println("等待子线程执行...");
long time1 = System.currentTimeMillis();
latch.await();
System.out.println("子线程执行完毕,等待时间:"+ String.valueOf(System.currentTimeMillis()-time1));
System.out.println("主线程继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class Worker extends Thread{
private CountDownLatch latch;
public Worker(final CountDownLatch latch){
this.latch = latch;
}
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"开始执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

执行结果:

1
2
3
4
5
6
7
等待子线程执行...
子线程Thread-1开始执行
子线程Thread-0开始执行
子线程Thread-1执行完毕
子线程Thread-0执行完毕
子线程执行完毕,等待时间:3002
主线程继续执行

与join的区别

实现线程的同步,我们很容易想到join,看看join实现:

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
public static void main(String[] args) {
try {
Worker a = new Worker();
Worker b = new Worker();
a.start();
b.start();
System.out.println("等待子线程执行...");
long time1 = System.currentTimeMillis();
a.join();
b.join();
System.out.println("子线程执行完毕,等待时间:"+ String.valueOf(System.currentTimeMillis()-time1));
System.out.println("主线程继续执行");
}catch (Exception e){
e.printStackTrace();
}
}
static class Worker extends Thread{
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"开始执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

执行结果:

1
2
3
4
5
6
7
等待子线程执行...
子线程Thread-1开始执行
子线程Thread-0开始执行
子线程Thread-1执行完毕
子线程Thread-0执行完毕
子线程执行完毕,等待时间:3004
主线程继续执行

我们可以看到打印结果是一样的,那join和CountDownLatch有什么区别呢?
我觉得可以这么理解,首先主线程通过调用子线程的join方法后,必须等到子线程执行完成后,才能会继续执行,而使用CountDownLatch则只需等到计数器值为0,主线程即可继续执行下去,不必等到子线程执行完,也就说我们可以继续在子线程执行完latch.countDown()后继续执行一些任务,在实际业务场景中做到更加精细的控制。

1
2
3
4
5
6
7
8
9
10
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"开始执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
System.out.println("子线程"+Thread.currentThread().getName()+"执行别的东西");
} catch (InterruptedException e) {
e.printStackTrace();
}

执行结果:

1
2
3
4
5
6
7
8
9
等待子线程执行...
子线程Thread-1开始执行
子线程Thread-0开始执行
子线程Thread-0执行完毕
子线程Thread-0执行别的东西
子线程Thread-1执行完毕
子线程执行完毕,等待时间:3001
主线程继续执行
子线程Thread-1执行别的东西

说到这里我们可能会想到Lock与synchronized 的区别,差不多也是这个套路吧。另一方面,我们还可以将CountDownLatch用于一组线程互相等待至某个状态,然后这一组线程再同时执行的场景,子线程设置 latch.await(),主线程里通过latch.countDown()触发所有子线程执行。

坚持原创技术分享,您的支持将鼓励我继续创作!