很多场景下,咱们须要期待线程池的所有工作都执行完,而后再进行下一步操作。对于线程 Thread 来说,很好实现,加一个 join 办法就解决了,然而对于线程池的判断就比拟麻烦了。

咱们本文提供 4 种判断线程池工作是否执行完的办法:

应用 isTerminated 办法判断。
应用 getCompletedTaskCount 办法判断。
应用 CountDownLatch 判断。
应用 CyclicBarrier 判断。
接下来咱们一个一个来看。

不判断的问题
如果不对线程池是否曾经执行完做判断,就会呈现以下问题,如下代码所示:

import java.util.Random;import java.util.concurrent.LinkedBlockingDeque;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class ThreadPoolCompleted {    public static void main(String[] args) {        // 创立线程池        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20,                0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));        // 增加工作        addTask(threadPool);                // 打印后果        System.out.println("线程池工作执行实现!");    }      /**     * 给线程池增加工作     */    private static void addTask(ThreadPoolExecutor threadPool) {        // 工作总数        final int taskCount = 5;        // 增加工作        for (int i = 0; i < taskCount; i++) {            final int finalI = i;            threadPool.submit(new Runnable() {                @Override                public void run() {                    try {                        // 随机休眠 0-4s                        int sleepTime = new Random().nextInt(5);                        TimeUnit.SECONDS.sleep(sleepTime);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(String.format("工作%d执行实现", finalI));                }            });        }    }}复制代码

以上程序的执行后果如下:

从上述执行后果能够看出,程序先打印了“线程池工作执行实现!”,而后还在陆续的执行线程池的工作,这种执行程序凌乱的后果,并不是咱们冀望的后果。咱们想要的后果是等所有工作都执行完之后,再打印“线程池工作执行实现!”的信息。

产生以上问题的起因是因为主线程 main,和线程池是并发执行的,所以当线程池还没执行完,main 线程的打印后果代码就曾经执行了。想要解决这个问题,就须要在打印后果之前,先判断线程池的工作是否曾经全副执行完,如果没有执行完就期待工作执行完再执行打印后果。

办法1:isTerminated
咱们能够利用线程池的终止状态(TERMINATED)来判断线程池的工作是否曾经全副执行完,但想要线程池的状态产生扭转,咱们就须要调用线程池的 shutdown 办法,不然线程池始终会处于 RUNNING 运行状态,那就没方法应用终止状态来判断工作是否曾经全副执行完了,它的实现代码如下:

import java.util.Random;import java.util.concurrent.LinkedBlockingDeque;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;/** * 线程池工作执行实现判断 */public class ThreadPoolCompleted {    public static void main(String[] args) {        // 1.创立线程池        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20,                0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));        // 2.增加工作        addTask(threadPool);        // 3.判断线程池是否执行完        isCompleted(threadPool); // 【外围调用办法】        // 4.线程池执行完        System.out.println();        System.out.println("线程池工作执行实现!");    }    /**     * 办法1:isTerminated 实现形式     * 判断线程池的所有工作是否执行完     */    private static void isCompleted(ThreadPoolExecutor threadPool) {        threadPool.shutdown();        while (!threadPool.isTerminated()) { // 如果没有执行完就始终循环        }    }    /**     * 给线程池增加工作     */    private static void addTask(ThreadPoolExecutor threadPool) {        // 工作总数        final int taskCount = 5;        // 增加工作        for (int i = 0; i < taskCount; i++) {            final int finalI = i;            threadPool.submit(new Runnable() {                @Override                public void run() {                    try {                        // 随机休眠 0-4s                        int sleepTime = new Random().nextInt(5);                        TimeUnit.SECONDS.sleep(sleepTime);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(String.format("工作%d执行实现", finalI));                }            });        }    }}复制代码

办法阐明:shutdown 办法是启动线程池有序敞开的办法,它在齐全敞开之前会执行完之前所有曾经提交的工作,并且不会再承受任何新工作。当线程池中的所有工作都执行完之后,线程池就进入了终止状态,调用 isTerminated 办法返回的后果就是 true 了。

以上程序的执行后果如下:

毛病剖析
须要敞开线程池。

扩大:线程池的所有状态
线程池总共蕴含以下 5 种状态:

RUNNING:运行状态。
SHUTDOWN:敞开状态。
STOP:阻断状态。
TIDYING:整顿状态。
TERMINATED:终止状态。

如果不调用线程池的敞开办法,那么线程池会始终处于 RUNNING 运行状态。

办法2:getCompletedTaskCount
咱们能够通过判断线程池中的打算执行工作数和已实现工作数,来判断线程池是否曾经全副执行完,如果打算执行工作数=已实现工作数,那么线程池的工作就全副执行完了,否则就未执行完,具体实现代码如下:

/** * 办法2:getCompletedTaskCount 实现形式 * 判断线程池的所有工作是否执行完 */private static void isCompletedByTaskCount(ThreadPoolExecutor threadPool) {    while (threadPool.getTaskCount() != threadPool.getCompletedTaskCount()) {    }}复制代码

以上程序执行后果如下:

办法阐明
getTaskCount():返回打算执行的工作总数。因为工作和线程的状态可能在计算过程中动态变化,因而返回的值只是一个近似值。
getCompletedTaskCount():返回实现执行工作的总数。因为工作和线程的状态可能在计算过程中动静地扭转,所以返回的值只是一个近似值,然而在间断的调用中并不会缩小。
优缺点剖析
此实现办法的长处是无需敞开线程池。 它的毛病是 getTaskCount() 和 getCompletedTaskCount() 返回的是一个近似值,因为线程池中的工作和线程的状态可能在计算过程中动态变化,所以它们两个返回的都是一个近似值。

办法3:CountDownLatch
CountDownLatch 能够了解为一个计数器,咱们创立了一个蕴含 N 个工作的计数器,每个工作执行完计数器 -1,直到计数器减为 0 时,阐明所有的工作都执行完了,就能够执行下一段业务的代码了,它的实现流程具体实现代码如下:

public static void main(String[] args) throws InterruptedException {    // 创立线程池    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20,        0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));    final int taskCount = 5;    // 工作总数    // 单次计数器    CountDownLatch countDownLatch = new CountDownLatch(taskCount); // ①    // 增加工作    for (int i = 0; i < taskCount; i++) {        final int finalI = i;        threadPool.submit(new Runnable() {            @Override            public void run() {                try {                    // 随机休眠 0-4s                    int sleepTime = new Random().nextInt(5);                    TimeUnit.SECONDS.sleep(sleepTime);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(String.format("工作%d执行实现", finalI));                // 线程执行完,计数器 -1                countDownLatch.countDown();  // ②            }        });    }    // 阻塞期待线程池工作执行完    countDownLatch.await();  // ③    // 线程池执行完    System.out.println();    System.out.println("线程池工作执行实现!");}复制代码

代码阐明:以上代码中标识为 ①、②、③ 的代码行是外围实现代码,其中: ① 是申明一个蕴含了 5 个工作的计数器; ② 是每个工作执行完之后计数器 -1; ③ 是阻塞期待计数器 CountDownLatch 减为 0,示意工作都执行完了,能够执行 await 办法前面的业务代码了。

以上程序的执行后果如下:

优缺点剖析
CountDownLatch 写法很优雅,且无需敞开线程池,但它的毛病是只能应用一次,CountDownLatch 创立之后不能被重复使用,也就是说 CountDownLatch 能够了解为只能应用一次的计数器。

办法4:CyclicBarrier
CyclicBarrier 和 CountDownLatch 相似,它能够了解为一个能够重复使用的循环计数器,CyclicBarrier 能够调用 reset 办法将本人重置到初始状态,CyclicBarrier 具体实现代码如下:

public static void main(String[] args) throws InterruptedException {    // 创立线程池    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20,        0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));    final int taskCount = 5;    // 工作总数    // 循环计数器 ①    CyclicBarrier cyclicBarrier = new CyclicBarrier(taskCount, new Runnable() {        @Override        public void run() {            // 线程池执行完            System.out.println();            System.out.println("线程池所有工作已执行完!");        }    });    // 增加工作    for (int i = 0; i < taskCount; i++) {        final int finalI = i;        threadPool.submit(new Runnable() {            @Override            public void run() {                try {                    // 随机休眠 0-4s                    int sleepTime = new Random().nextInt(5);                    TimeUnit.SECONDS.sleep(sleepTime);                    System.out.println(String.format("工作%d执行实现", finalI));                    // 线程执行完                    cyclicBarrier.await(); // ②                } catch (InterruptedException e) {                    e.printStackTrace();                } catch (BrokenBarrierException e) {                    e.printStackTrace();                }            }        });    }}复制代码

以上程序的执行后果如下:

办法阐明
CyclicBarrier 有 3 个重要的办法:

构造方法:构造方法能够传递两个参数,参数 1 是计数器的数量 parties,参数 2 是计数器为 0 时,也就是工作都执行完之后能够执行的事件(办法)。
await 办法:在 CyclicBarrier 上进行阻塞期待,当调用此办法时 CyclicBarrier 的外部计数器会 -1,直到产生以下情景之一:
在 CyclicBarrier 上期待的线程数量达到 parties,也就是计数器的申明数量时,则所有线程被开释,继续执行。
以后线程被中断,则抛出 InterruptedException 异样,并进行期待,继续执行。
其余期待的线程被中断,则以后线程抛出 BrokenBarrierException 异样,并进行期待,继续执行。
其余期待的线程超时,则以后线程抛出 BrokenBarrierException 异样,并进行期待,继续执行。
其余线程调用 CyclicBarrier.reset() 办法,则以后线程抛出 BrokenBarrierException 异样,并进行期待,继续执行。
reset 办法:使得CyclicBarrier回归初始状态,直观来看它做了两件事:
如果有正在期待的线程,则会抛出 BrokenBarrierException 异样,且这些线程进行期待,继续执行。
将是否破损标记位 broken 置为 false。
优缺点剖析
CyclicBarrier 从设计的复杂度到应用的复杂度都高于 CountDownLatch,相比于 CountDownLatch 来说它的长处是能够重复使用(只需调用 reset 就能复原到初始状态),毛病是应用难度较高。

总结
咱们本文提供 4 种判断线程池工作是否执行完的办法:

应用 isTerminated 办法判断:通过判断线程池的实现状态来实现,须要敞开线程池,个别状况下不倡议应用。
应用 getCompletedTaskCount 办法判断:通过打算执行总任务量和曾经实现总任务量,来判断线程池的工作是否曾经全副执行,如果相等则断定为全副执行实现。但因为线程个体和状态都会产生扭转,所以失去的是一个大抵的值,可能不精确。
应用 CountDownLatch 判断:相当于一个线程平安的单次计数器,应用比较简单,且不须要敞开线程池,是比拟罕用的判断办法。
应用 CyclicBarrier 判断:相当于一个线程平安的反复计数器,但应用较为简单,所以日常我的项目中应用的较少。
最初
如果你感觉此文对你有一丁点帮忙,点个赞。或者能够退出我的开发交换群:1025263163互相学习,咱们会有业余的技术答疑解惑

如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点star:http://github.crmeb.net/u/defu不胜感激 !

残缺源码下载地址:https://market.cloud.tencent....

PHP学习手册:https://doc.crmeb.com
技术交换论坛:https://q.crmeb.com