本文旨在以最简单的例子让读者了解CountDownLatch到底能做什么,不进行深入探讨。
CountDownLatch在JDK中的说明
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
能让一个或多个线程进行等待直到一系列操作在其他线程被执行的同步辅助类。
既然提到至少是两个线程的故事,那首先创建一个线程池:
package com.example.javabase.countdownlatch;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.*;
/**
* @author red
*/
public class ExecutorPool {
private static ThreadFactory factory = new ThreadFactoryBuilder()
.setNameFormat("子线程-%d").build();
private static ExecutorService service = new ThreadPoolExecutor(10, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),factory);
/**
* 往线程池中添加任务
* @param r
*/
static void submit(Runnable r){
service.submit(r);
}
/**
* 等待线程池内的任务执行完毕后关闭
*/
static void shutdown(){
service.shutdown();
}
}
不使用CountDownLatch
在App0类中,定义了个方法 addChildThread,向线程池中添加新的线程,线程的运行时间是由参数决定的。
main方法中,分别向线程池中添加了两个线程,运行时间分别是2秒和5秒。并且在主线程中输出一串信息。
package com.example.javabase.countdownlatch;
/**
* @author red
*/
public class App0 {
public static void main(String[] args) throws InterruptedException {
addChildThread(2000);
addChildThread(5000);
System.out.println("线程"+Thread.currentThread().getName() +" 执行完成");
ExecutorPool.shutdown();
}
private static void addChildThread(Integer time){
ExecutorPool.submit(()->{
try {
Thread.sleep(time);
System.out.println(Thread.currentThread().getName()+" 执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
运行结果:
线程main 执行完成
子线程-0 执行完成
子线程-1 执行完成
使用1:等待各线程完成
假如有这样一个场景:某个线程需要等待其他一个或多个线程执行后才执行。
将上述代码稍作修改,让主线程必须等到线程池中的线程执行完成后才能执行,代码如下:
package com.example.javabase.countdownlatch;
import java.util.concurrent.CountDownLatch;
/**
* 等待各线程完成
* @author red
*/
public class App1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
addChildThread(latch,2000);
addChildThread(latch,3000);
addChildThread(latch,5000);
//在这儿停顿 等待latch countdown
latch.await();
System.out.println("线程"+Thread.currentThread().getName() +" 执行完成");
ExecutorPool.shutdown();
}
private static void addChildThread(CountDownLatch latch,Integer time){
ExecutorPool.submit(()->{
try {
Thread.sleep(time);
System.out.println(Thread.currentThread().getName()+" 执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
latch.countDown();
}
});
}
}
为了更便于观察,main方法中向线程池添加了3个线程,运行时间各不相同。每个线程执行结束后对CountDownLatch 进行countDown。
运行结果:
子线程-0 执行完成
子线程-1 执行完成
子线程-2 执行完成
线程main 执行完成
从结果可以看到,主线程并没有立即执行,而是等到计数为3的CountDownLatch减完之后才继续执行的。
此时主线程就像监考老师,每个线程池中的线程就像等待交卷的考生。每个学生答完交卷countDown,CountDownLatch减一。老师必须等到所有考生都交完卷,即CountDownLatch countDown到0,才能离开教室。
使用2:线程并行
package com.example.javabase.countdownlatch;
import java.util.concurrent.CountDownLatch;
/**
* 线程并行
* @author red
*/
public class App2 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
addChildThread(5,latch);
addChildThread(3,latch);
Thread.sleep(3000);
latch.countDown();
System.out.println("线程"+Thread.currentThread().getName() +" 执行完成");
ExecutorPool.shutdown();
}
private static void addChildThread(Integer num , CountDownLatch latch){
for (int i = 0; i<num; i++){
ExecutorPool.submit(()->{
try {
System.out.println(Thread.currentThread().getName()+" 开始等待");
latch.await();
System.out.println(Thread.currentThread().getName()+" 执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
在App2类中,addChildThread方法是批量向线程池中添加线程,而每个线程执行的操作时等待CountDownLatch至0。
main方法中,分两批次向线程池中共添加了8个线程,在主线程中对CountDownLatch进行countDown。
运行结果:
子线程-0 开始等待
子线程-1 开始等待
子线程-2 开始等待
子线程-4 开始等待
子线程-5 开始等待
子线程-3 开始等待
子线程-6 开始等待
子线程-7 开始等待
线程main 执行完成
子线程-1 执行完成
子线程-5 执行完成
子线程-7 执行完成
子线程-0 执行完成
子线程-3 执行完成
子线程-6 执行完成
子线程-2 执行完成
子线程-4 执行完成
此时线程池中的线程就像一名名田径运动员,在运动员走到起跑线后开始等待await,当倒计时器CountDownLatch countDown到0时,就像发令枪发令,让所有等待的运动员同时起跑。
网友评论