草庐IT

判断线程池是否全部完成的 5 种方法,还有谁不会?

Java技术栈 2023-04-21 原文

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

概述

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

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

  • isTerminated() 判断方式,在执行 shutdown() ,关闭线程池后,判断是否所有任务已经完成。
  • ThreadPoolExecutorgetCompletedTaskCount() 方法,判断完成任务数和全部任务数是否相等。
  • 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,除非 shutdownshutdownNow 先被执行。

通俗点讲,就是在执行全部任务后,对线程池进行 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 的主要使用场景并不是用于判断任务执行状态。

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

有关判断线程池是否全部完成的 5 种方法,还有谁不会?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  5. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  6. Ruby 方法() 方法 - 2

    我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

  7. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  8. ruby - Highline 询问方法不会使用同一行 - 2

    设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

  9. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  10. ruby - 检查数组是否在增加 - 2

    这个问题在这里已经有了答案:Checktoseeifanarrayisalreadysorted?(8个答案)关闭9年前。我只是想知道是否有办法检查数组是否在增加?这是我的解决方案,但我正在寻找更漂亮的方法:n=-1@arr.flatten.each{|e|returnfalseife

随机推荐