前言
前面有写过关于一篇在多线程环境中如何确保所有子线程执行完毕的文章,当时写的是使用 CountDownLatch 计数器来实现。
今天来纠正下这部分的内容:使用计数器只能确保所有的线程都开始被执行了,无法确保线程执行完毕。
接下来我们来复现下这个问题。
实现
问题复现
这里我们复现一下使用 CountDownLatch 无法确保子线程执行结束的场景,我们使用前文中的一段代码(细微改动用于复现问题):
package org.example.test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author LANGJIALINGBOJUE
*/
public class Test5 {
public static void main(String[] args) throws InterruptedException {
// 开启线程池
ExecutorService executorService = Executors.newFixedThreadPool(8);
// 参数与多线程的任务数必须保持一致,否则 count 会提前减到0,导致多线程之外的任务提前执行
CountDownLatch countDownLatch = new CountDownLatch(3);
for (int i=0; i<3; i++){
int finalI = i;
executorService.execute(() -> {
countDownLatch.countDown();
try {
Thread.sleep(2000);
System.out.println("i:" + finalI);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executorService.shutdown();
countDownLatch.await();
System.out.println("Hello,World!");
}
}
输出结果:
Hello,World!
i:2
i:1
i:0
从输出结果中可以看出,Hello World! 并没有等子线程结束就输出了。
那前文中的结果是怎么回事呢?
这是因为代码逻辑过于简单,在执行到输出 Hello World! 的时候子线程就执行结束了,顺序执行下来的结果正好与预期结果相同。
问题解决
确保子线程执行结束,除了使用 submit() 方法外(这个方法在前面也写过),还可以使用 join()方法。这里我们记录下 join() 方法。
示例代码:join()方法实现
在线程池场景下,适合使用线程池的 submit() 方法,这可以参考本文。
由于 join() 方法不适合在多线程环境下使用,这里我们使用手动创建线程的方式。
一段示例代码:
package org.example.test;
/**
* @author LANGJIALINGBOJUE
*/
public class Test6 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("thread");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
thread.start();
thread.join();
System.out.println("main thread");
}
}
输出结果:
thread
main thread
从输出结果可以看出,在调用 join() 方法后,会等待子线程执行结束才会继续执行后续逻辑。
番外
大家可以使用上面这段代码自行验证下使用 CountDownLatch 的效果,它只能确保子线程执行了,但不会等待执行结束。当然我们也可以把 countDownLatch.countDown(); 逻辑放在子线程的最后,以此达到相同的效果。
这里我们不纠结可以达到什么效果,重在理解两者的区别。
总结
在多线程环境中,join() 方法可以等待子线程执行结束,而 CountDownLatch 计数器可以确保所有子线程开始执行,但不会等待执行结束。