精华内容
下载资源
问答
  • 判断线程池是否执行完
    千次阅读
    2022-06-05 20:05:58

    1、使用 isTerminated 方法判断
    2、使用 getCompletedTaskCount方法判断
    3、使用CountDownLatch判断
    4、使用 CyclicBarrier判断

    一、isTerminated方法判断

    我们可以利用线程池的终止状态(TERMINATED)来判断线程池的任务是否已经全部执行完,但想要线程池的状态发生改变,我们就需要调用线程池的shutdown方法,不然线程池一直会处于RUNNING 运行状态

    缺点:需要先手动关闭线程池调用shutdown等方法,然后再通过isTerminated方法来判断。

    private static void isCompleted(ThreadPoolExecutor threadPool) {
        threadPool.shutdown();//1、必须先手动关闭线程池
        while (!threadPool.isTerminated()) {
        	//2、如果没有执行完就一直循环
        }
    }
    

    二、getCompletedTaskCount

    我们可以通过判断线程池中的计划执行任务数和已完成任务数,来判断线程池是否已经全部执行完,如果计划执行任务数=已完成任务数,那么线程池的任务就全部执行完了,否则就未执行完

    //getCompletedTaskCount 实现方式判断线程池的所有任务是否执行完
    private static void isCompletedByTaskCount(ThreadPoolExecutor threadPool) {
        while (threadPool.getTaskCount() != threadPool.getCompletedTaskCount()) {
    
        }
    }
    

    方法说明

    1、getTaskCount():返回计划执行的任务总数。由于任务和线程的状态可能在计算过程中动态变化,因此返回的值只是一个近似值。
    2、getCompletedTaskCount():返回完成执行任务的总数。因为任务和线程的状态可能在计算过程中动态地改变,所以返回的值只是一个近似值,但是在连续的调用中并不会减少。

    优缺点分析

    此实现方法的优点是无需关闭线程池。 它的缺点是getTaskCount() 和getCompletedTaskCount(),返回的是一个近似值,因为线程池中的任务和线程的状态可能在计算过程中动态变化,所以它们两个返回的都是一个近似值。

    三、CountDownLatch

    CountDownLatch 可以理解为一个计数器,我们创建了一个包含 N 个任务的计数器,每个任务执行完计数器 -1,直到计数器减为 0 时,说明所有的任务都执行完了

    核心代码:

    //在主线程定义线程计数器的总数
    1CountDownLatch countDownLatch = new CountDownLatch(taskCount);
    2、countDownLatch.countDown();//在子线程完成减一的操作
    3、countDownLatch.await();//主线程阻塞等待,当线程计数器值为0时,表示线程执行完毕
    

    优缺点分析

    CountDownLatch 写法很优雅,且无需关闭线程池,但它的缺点是只能使用一次,CountDownLatch创建之后不能被重复使用,也就是说 CountDownLatch 可以理解为只能使用一次的计数器。

    四、拓展CountDownLatch类的API

    1、构造方法

    //参数count为计数值
    public CountDownLatch(int count) {};  
    
    //1、调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
    public void await() throws InterruptedException { };   
    //2、和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
    public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
    //3、将count值减1
    public void countDown() { };  
    

    五、线程池相关的API

    ThreadPoolExecutor 中的 shutdown() 、shutdownNow() 、awaitTermination()的用法和区别

    1、shutdown()

    正常关闭,将线程池状态置为SHUTDOWN,线程池并不会立即停止

    1. 停止接收外部submit的任务
    2. 内部正在跑的任务和队列里等待的任务,会执行完
    3. 等到第二步完成后,才真正停止

    1、shutdownNow
    强行关闭,将线程池状态置为STOP。企图立即停止,事实上不一定:

    1. 跟shutdown()一样,先停止接收外部提交的任务
    2. 忽略队列里等待的任务
    3. 尝试将正在跑的任务interrupt中断
    4. 返回未执行的任务列表

    shutdownNow()它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,调用interrupt()方法并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息,interrupt()只能响应可中断的阻塞,对于不可中断的阻塞,我们需要找到线程阻塞的原因并特殊处理。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。

    3、awaitTermination()

    作用: 当前线程阻塞,直到等所有已提交的任务执行完 ,这里包括正在跑的和队列中等待的,或者等超时时间到或者线程被中断,抛出InterruptedException,然后返回true,shutdown()可以和awaitTermination()方法一起使用

    4、isShutDown

    当调用shutdown()或shutdownNow()方法后返回为true。

    5、isTerminated

    1、当调用shutdown()方法后,并且所有提交的任务完成后返回为true;
    2、当调用shutdownNow()方法后,成功停止后返回为true;
    3、如果线程池任务正常完成,都为false

    本文转载至:https://blog.csdn.net/qq_39221436/article/details/123863354

    更多相关内容
  • 我们本文提供 4 种判断线程池任务是否执行完的方法: 使用 isTerminated 方法判断。 使用 getCompletedTaskCount 方法判断。 使用 CountDownLatch 判断。 使用 CyclicBarrier 判断。 接下来我们一个一个来看。 不...

    很多场景下,我们需要等待线程池的所有任务都执行完,然后再进行下一步操作。对于线程 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.com/products/33276

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

    展开全文
  • 主要介绍了Java判断线程池线程是否执行完毕,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 我们本文提供 4 种判断线程池任务是否执行完的方法: 使用 isTerminated 方法判断。 使用 getCompletedTaskCount 方法判断。 使用 CountDownLatch 判断。 使用 CyclicBarrier 判断。 接下来我们一个一个来看。 ...

    很多场景下,我们需要等待线程池的所有任务都执行完,然后再进行下一步操作。对于线程 Thread 来说,很好实现,加一个 join 方法就解决了,然而对于线程池的判断就比较麻烦了。

    我们本文提供 4 种判断线程池任务是否执行完的方法:

    1. 使用 isTerminated 方法判断。
    2. 使用 getCompletedTaskCount 方法判断。
    3. 使用 CountDownLatch 判断。
    4. 使用 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. 构造方法:构造方法可以传递两个参数,参数 1 是计数器的数量 parties,参数 2 是计数器为 0 时,也就是任务都执行完之后可以执行的事件(方法)。
    2. await 方法:在 CyclicBarrier 上进行阻塞等待,当调用此方法时 CyclicBarrier 的内部计数器会 -1,直到发生以下情形之一:
      1. 在 CyclicBarrier 上等待的线程数量达到 parties,也就是计数器的声明数量时,则所有线程被释放,继续执行。
      2. 当前线程被中断,则抛出 InterruptedException 异常,并停止等待,继续执行。
      3. 其他等待的线程被中断,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。
      4. 其他等待的线程超时,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。
      5. 其他线程调用 CyclicBarrier.reset() 方法,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。
    3. reset 方法:使得CyclicBarrier回归初始状态,直观来看它做了两件事:
      1. 如果有正在等待的线程,则会抛出 BrokenBarrierException 异常,且这些线程停止等待,继续执行。
      2. 将是否破损标志位 broken 置为 false。

    优缺点分析

    CyclicBarrier 从设计的复杂度到使用的复杂度都高于 CountDownLatch,相比于 CountDownLatch 来说它的优点是可以重复使用(只需调用 reset 就能恢复到初始状态),缺点是使用难度较高。

    总结

    我们本文提供 4 种判断线程池任务是否执行完的方法:

    1. 使用 isTerminated 方法判断:通过判断线程池的完成状态来实现,需要关闭线程池,一般情况下不建议使用。
    2. 使用 getCompletedTaskCount 方法判断:通过计划执行总任务量和已经完成总任务量,来判断线程池的任务是否已经全部执行,如果相等则判定为全部执行完成。但因为线程个体和状态都会发生改变,所以得到的是一个大致的值,可能不准确。
    3. 使用 CountDownLatch 判断:相当于一个线程安全的单次计数器,使用比较简单,且不需要关闭线程池,是比较常用的判断方法
    4. 使用 CyclicBarrier 判断:相当于一个线程安全的重复计数器,但使用较为复杂,所以日常项目中使用的较少。
    展开全文
  • 主要介绍了Java 判断线程池所有任务是否执行完毕的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • 点击关注公众号,利用碎片时间学习概述最近写小玩具的时候用到了CountDownLatch计数器,然后顺便想了想判断线程池全部结束有多少种方法。在网上搜了下,可能有些没找到,但是我找到的有(所有方法都是在...

    点击关注公众号,利用碎片时间学习

    概述

    最近写小玩具的时候用到了 CountDownLatch 计数器,然后顺便想了想判断线程池全部结束有多少种方法。

    在网上搜了下,可能有些没找到,但是我找到的有(所有方法都是在 ThreadPoolExecutor 线程池方法下测试的):

    • isTerminated() 判断方式,在执行 shutdown() ,关闭线程池后,判断是否所有任务已经完成。

    • ThreadPoolExecutor 的 getCompletedTaskCount() 方法,判断完成任务数和全部任务数是否相等。

    • CountDownLatch 计数器,使用闭锁计数来判断是否全部完成。

    • 手动维护一个公共计数 ,原理和闭锁类似,就是更加灵活。

    • 使用 submit 向线程池提交任务,Future 判断任务执行状态。

    好嘞,现在开始一个一个介绍优缺点和简要原理;

    先创建一个 static 线程池,后面好几个例子就不一一创建了,全部用这个就行了:

    /**
     * 创建一个最大线程数是20的线程池
     */
    public static ThreadPoolExecutor pool = new ThreadPoolExecutor(
         10, 20, 0L,
         TimeUnit.MILLISECONDS,
         new LinkedBlockingQueue<>());

    然后再准备一个通用的睡眠方法:

    /**
     * 线程执行方法,随机等待0到10秒
     */
    private static void sleepMtehod(int index){
        try {
            long sleepTime = new Double(Math.random() * 10000).longValue();
            Thread.sleep(sleepTime);
            System.out.println("当前线程执行结束: " + index);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    这个方法就是为了测试的时候区分线程执行完毕的下顺序而已。

    好嘞,准备完毕,现在开始。

    isTerminated 方式

    首先贴上测试代码:

    private static void shutdownTest() throws Exception {
        for (int i = 0; i < 30; i++) {
            int index = i;
            pool.execute(() -> sleepMtehod(index));
        }
        pool.shutdown();
        while (!pool.isTerminated()){
            Thread.sleep(1000);
            System.out.println("还没停止。。。");
        }
        System.out.println("全部执行完毕");
    }

    这一种方式就是在主线程中进行循环判断,全部任务是否已经完成。

    这里有两个主要方法:

    • shutdown() :启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。如果已经关闭,调用没有额外的作用。

    • isTerminated() :如果所有任务在关闭后完成,则返回true。请注意, isTerminated 从不是 true,除非 shutdown 或 shutdownNow 先被执行。

    通俗点讲,就是在执行全部任务后,对线程池进行 shutdown() 有序关闭,然后循环判断 isTerminated() ,线程池是否全部完成。

    • 优点 :操作简单,代码更加简单。

    • 缺点 :需要关闭线程池。一般我在代码中都是将线程池注入到 Spring 容器,然后各个组件中统一用同一个,当然不能关闭。

    类似方法扩展:

    • shutdownNow() :尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。从此方法返回时,这些任务将从任务队列中删除。通过 Thread.interrupt() 取消任务。

    • isShutdown() :如果线程池已关闭,则返回 true 。

    • isTerminating() :如果在 shutdown() 或 shutdownNow() 之后终止 ,但尚未完全终止,则返回true。

    • waitTermination(long timeout, TimeUnit unit) :当前线程阻塞,直到等所有已提交的任务(包括正在跑的和队列中等待的)执行完,或者等超时时间到,或者线程被中断抛出异常;全部执行完返回true,超时返回false。也可以用这个方法代替 isTerminated() 进行判断 。

    getCompletedTaskCount

    还是一样,贴上代码:

    private static void taskCountTest() throws Exception {
            for (int i = 0; i < 30; i++) {
                int index = i;
                pool.execute(() -> sleepMtehod(index));
            }
            //当线程池完成的线程数等于线程池中的总线程数
            while (!(pool.getTaskCount() == pool.getCompletedTaskCount())) {
                System.out.println("任务总数:" + pool.getTaskCount() + "; 已经完成任务数:" + pool.getCompletedTaskCount());
                Thread.sleep(1000);
                System.out.println("还没停止。。。");
            }
            System.out.println("全部执行完毕");
        }

    还是一样在主线程循环判断,主要就两个方法:

    • getTaskCount() :返回计划执行的任务总数。由于任务和线程的状态可能在计算过程中动态变化,因此返回的值只是一个近似值。

    • getCompletedTaskCount() :返回完成执行的任务的大致总数。因为任务和线程的状态可能在计算过程中动态地改变,所以返回的值只是一个近似值,但是在连续的调用中并不会减少。

    这个好理解,总任务数等于已完成任务数,就表示全部执行完毕。

    • 优点 :完全使用了 ThreadPoolExecutor 提供的方法,并且不必关闭线程池,避免了创建和销毁带来的损耗。

    • 缺点 :上面的解释也看到了,使用这种判断存在很大的限制条件;必须确定,在循环判断过程中,没有新的任务产生。差不多意思就是,这个线程池只能在这条线程中使用。

    其他 :

    最后扯两句,因为我用 main 方法运行的,跑完后 main 没有结束,是因为非守护线程如果不终止,程序是不会结束的。而线程池 Worker 线程里写了一个死循环,而且被设置成了非守护线程。

    CountDownLatch 计数器

    这种方法是我比较常用的方法,先看代码:

    private static void countDownLatchTest() throws Exception {
         //计数器,判断线程是否执行结束
         CountDownLatch taskLatch = new CountDownLatch(30);
         for (int i = 0; i < 30; i++) {
             int index = i;
             pool.execute(() -> {
                 sleepMtehod(index);
                 taskLatch.countDown();
                 System.out.println("当前计数器数量:" + taskLatch.getCount());
             });
         }
         //当前线程阻塞,等待计数器置为0
         taskLatch.await();
         System.out.println("全部执行完毕");
     }

    这种方法,呃,应该是看起来比较高级的,我也不知道别的大佬怎么写的,反正我就用这个。

    这个方法需要介绍下这个工具类 CountDownLatch 。先把这种方式的优缺点写了,后面再详细介绍这个类。

    • 优点 :代码优雅,不需要对线程池进行操作,将线程池作为 Bean 的情况下有很好的使用场景。

    • 缺点 :需要提前知道线程数量;性能确实,呃呃呃呃呃,差了点。哦对了,还需要在线程代码块内加上异常判断,否则在 countDown 之前发生异常而没有处理,就会导致主线程永远阻塞在 await。

    CountDownLatch 概述

    CountDownLatch 是 JDK 提供的一个同步工具,它可以让一个或多个线程等待,一直等到其他线程中执行完成一组操作。

    常用的方法有 countDown 方法和 await 方法,CountDownLatch 在初始化时,需要指定用给定一个整数作为计数器。

    当调用 countDown 方法时,计数器会被减1;当调用 await 方法时,如果计数器大于0时,线程会被阻塞,一直到计数器被 countDown 方法减到0时,线程才会继续执行。

    计数器是无法重置的,当计数器被减到0时,调用 await 方法都会直接返回。

    维护一个公共计数

    这种方式其实和 CountDownLatch 原理类似。

    先维护一个静态变量

    private static int taskNum = 0;

    然后在线程任务结束时,进行静态变量操作:

    private static void staticCountTest() throws Exception {
         Lock lock = new ReentrantLock();
         for (int i = 0; i < 30; i++) {
             int index = i;
             pool.execute(() -> {
                 sleepMtehod(index);
                 lock.lock();
                 taskNum++;
                 lock.unlock();
             });
         }
         while(taskNum < 30) {
             Thread.sleep(1000);
             System.out.println("还没停止。。。当前完成任务数:" + taskNum);
         }
         System.out.println("全部执行完毕");
     }

    其实就是加锁计数,循环判断。

    • 优点 :手动维护方式更加灵活,对于一些特殊场景可以手动处理。

    • 缺点 :和 CountDownLatch 相比,一样需要知道线程数目,但是代码实现比较麻烦,相对于灵活这一个优势,貌似投入产出并不对等。

    Future 判断任务执行状态

    Future 是用来装载线程结果的,不过,用这个来进行判断写代码总感觉怪怪的。

    因为 Future 只能装载一条线程的返回结果,多条线程总不能用 List 在接收 Future 。

    这里就开一个线程做个演示:

    private static void futureTest() throws Exception {
        Future<?> future = pool.submit(() -> sleepMtehod(1));
        while (!future.isDone()){
            Thread.sleep(500);
            System.out.println("还没停止。。。");
        }
        System.out.println("全部执行完毕");
    }

    这种方式就不写优缺点了,因为 Future 的主要使用场景并不是用于判断任务执行状态。

    来源:https://blog.csdn.net/m0_46144826

    推荐:

    最全的java面试题库

    c8eeab43a32b5ce2910efb634aea7cfe.png

    PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

    展开全文
  • 正是由于这个睡眠,所以当所有线程池中的线程都执行完后,有可能延迟200ms才执行"结束了"语句。这个参数越小延迟越小,结果越准确。 下面是子线程,子线程只是简单的将数字i打印出来; [java] view plaincopy ...
  • shutdownvoid shutdown()启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。抛出: RuntimePermission ("modifyThread")),或者安全管理器的 checkAccess方法拒绝访问。...
  • 在使用多线程的时候有时候我们会使用 java.util.concurrent.Executors的线程池,当多个线程异步执行的时候,我们往往不好判断是否线程池中所有的子线程都已经执行完毕,但有时候这种判断却很有用,例如我有个方法的...
  • 在使用多线程的时候有时候我们会使用 java.util.concurrent.Executors的线程池,当多个线程异步执行的时候,我们往往不好判断是否线程池中所有的子线程都已经执行完毕,但有时候这种判断却很有用,例如我有个方法的...
  • 判断线程池的线程是否全部执行结束 boolean flag = false; List<Callable<Boolean>> tasks = new ArrayList<Callable<Boolean>>(); for (int i = 0; i < threadNum; i++) { task = new ...
  • 解决问题,实现判断线程池中的线程是否执行完成4-1.FutureTask4-2.实现代码(使用isDone)4-3.实现代码(使用get) 1.先写结果 使用FutureTask类即可实现判断线程池中的线程的状态,提供的方法是isDone(),get()。 ...
  • 判断线程池中所有线程是否执行完毕 1.根据线程池中的总线程数目等于完成的线程数目 package com.luna.thread; import java.util.Random; import java.util.concurrent.LinkedBlockingQueue; import java.util....
  • 多线程-三种方法判断线程池中任务有没全部执行完
  • Android—判断线程池是否执行结束

    千次阅读 2015-09-01 11:18:15
    ExecutorService exe = Executors.newFixedThreadPool(30);  /* lowerorgdatalist.clear();  if(lowerorglist.size()>0){  for (int i = 0; i   m = lowerorglist.get(i);  exe.e
  • 判断线程池是否有任务正在执行

    千次阅读 2019-11-23 18:43:40
    在使用线程池提交异步任务时,当需要做多用户互斥操作时,用户A在操作关键数据时,用户B也在操作,系统检测到A下发的数据任务还在执行则禁止用户B的下发的任务,可以通过判断线程池是否存在正在执行的任务来操作,...
  • 本篇文章小编给大家分享一下Java判断线程池所有任务是否执行完毕代码操作示例,文章代码介绍的很详细,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看。代码如下:import java.util....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 117,844
精华内容 47,137
关键字:

判断线程池是否执行完