【Java】如何判断线程池任务执行完?

news2024/11/16 6:58:56

文章目录

  • 前言
  • 1.需求分析
  • 2.实现概述
  • 3.具体实现
    • 3.1 统计完成任务数
    • 3.2 FutureTask
    • 3.3 CountDownLatch和CyclicBarrier
  • 小结

前言

论是在项目开发中,还是在面试中过程中,总会被问到或使用到并发编程来完成项目中的某个功能。

例如某个复杂的查询,无法使用一个查询语句来完成此功能,此时我们就需要执行多个查询语句,然后再将各自查询的结果,组装之后返回给前端了,那么这种场景下,我们就必须使用线程池来进行并发查询了。

PS:磊哥做的最复杂的查询,总共关联了 21 张表,在和产品及需求方的沟通多次沟通下,才将查询的业务从 21 张表,降到了至少要查询 12 张表(非常难搞),那么这种场景下是无法使用一个查询语句来实现的,那么并发查询是必须要给安排上的。

1.需求分析

线程池的使用并不复杂,麻烦的是如何判断线程池中的任务已经全部执行完了?因为我们要等所有任务都执行完之后,才能进行数据的组装和返回,所以接下来,我们就来看如何判断线程中的任务是否已经全部执行完?

2.实现概述

判断线程池中的任务是否执行完的方法有很多,比如以下几个:

使用 getCompletedTaskCount() 统计已经执行完的任务,和 getTaskCount() 线程池的总任务进行对比,如果相等则说明线程池的任务执行完了,否则既未执行完。
使用 FutureTask 等待所有任务执行完,线程池的任务就执行完了。
使用 CountDownLatch 或 CyclicBarrier 等待所有线程都执行完之后,再执行后续流程。

具体实现代码如下。

3.具体实现

3.1 统计完成任务数

通过判断线程池中的计划执行任务数和已完成任务数,来判断线程池是否已经全部执行完,如果计划执行任务数=已完成任务数,那么线程池的任务就全部执行完了,否则就未执行完。
示例代码如下:

private static void isCompletedByTaskCount(ThreadPoolExecutor threadPool) {
    while (threadPool.getTaskCount() != threadPool.getCompletedTaskCount()) {
    }
}

以上程序执行结果如下:
在这里插入图片描述

方法说明

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

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

3.2 FutureTask

FutrueTask 的优势是任务判断精准,调用每个 FutrueTask 的 get 方法就是等待该任务执行完,如下代码所示:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

/**
 * 使用 FutrueTask 等待线程池执行完全部任务
 */
public class FutureTaskDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        // 创建任务
        FutureTask<Integer> task1 = new FutureTask<>(() -> {
            System.out.println("Task 1 start");
            Thread.sleep(2000);
            System.out.println("Task 1 end");
            return 1;
        });
        FutureTask<Integer> task2 = new FutureTask<>(() -> {
            System.out.println("Task 2 start");
            Thread.sleep(3000);
            System.out.println("Task 2 end");
            return 2;
        });
        FutureTask<Integer> task3 = new FutureTask<>(() -> {
            System.out.println("Task 3 start");
            Thread.sleep(1500);
            System.out.println("Task 3 end");
            return 3;
        });
        // 提交三个任务给线程池
        executor.submit(task1);
        executor.submit(task2);
        executor.submit(task3);

        // 等待所有任务执行完毕并获取结果
        int result1 = task1.get();
        int result2 = task2.get();
        int result3 = task3.get();
        System.out.println("Do main thread.");
    }
}

以上程序的执行结果如下:
在这里插入图片描述

3.3 CountDownLatch和CyclicBarrier

CountDownLatch 和 CyclicBarrier 类似,都是等待所有任务到达某个点之后,再进行后续的操作,如下图所示:
在这里插入图片描述

CountDownLatch 使用的示例代码如下:

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 创建之后不能被重复使用。
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 就能恢复到初始状态),缺点是使用难度较高。

小结

在实现判断线程池任务是否执行完成的方案中,通过统计线程池执行完任务的方式(实现方法 1),以及实现方法 3(CountDownLatch 或CyclicBarrier)等统计,都是“不记名”的,只关注数量,不关注(具体)对象,所以这些方式都有可能受到外界代码的影响,因此使用 FutureTask 等待具体任务执行完的方式是最推荐的判断方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/838598.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

etcd

文章目录 etcd单机安装设置键值对watch操作读取键过往版本的值压缩修订版本lease租约&#xff08;过期机制&#xff09;授予租约撤销租约keepAlive续约获取租约信息 事务基于etcd实现分布式锁原生实现官方 concurrency 包实现 服务注册与发现Go 操作 Etcd 参考 etcd etcd 是一…

【Java】批量生成条码

批量生成PDF条码 效果图&#xff1a; //调用下方接口注意编码格式if(CollectionUtil.isNotEmpty(productExList)){String exportFileName URLEncoder.encode("商品条码", "UTF-8") DateUtil.format(new Date(), "yyyyMMddHHmmss");response.…

git 公钥密钥 生成与查看

1.什么是公钥 很多服务器都是需要认证的&#xff0c;ssh认证是其中的一种。在客户端生成公钥&#xff0c;把生成的公钥添加到服务器&#xff0c;你以后连接服务器就不用每次都输入用户名和密码了。 很多git服务器都是用ssh认证方式&#xff0c;你需要把你生成的公钥发送给代码仓…

【每日一题】21. 合并两个有序链表

【每日一题】21. 合并两个有序链表 21. 合并两个有序链表题目描述解题思路 21. 合并两个有序链表 题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4…

中间人攻击

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、启动 Kali Linux准备 Apache 服务器使用Ettercap插件进行欺骗使用 Wireshark 捕获 Https 流量总结 前言 您可能已经熟悉中间人攻击&#xff1a;攻击者通过…

测试平台——项目模块模型类设计

这里写目录标题 一、项目应用1、项目包含接口&#xff1a;2、创建子应用3、项目模块设计a、模型类设计b、序列化器类设计c、视图类设计 4、接口模块设计a、模型类设计b、序列化器类设计c、视图类设计 5、环境模块设计6、DRF中的通用过滤6.1、设置过滤器后端 一、项目应用 1、项…

js省市区下拉框联动——前端笔记

问题&#xff1a; 我们常常要用到下拉框联动的功能&#xff0c;比如最常用的是选择地址的 省 市 区 的联动。思路&#xff1a; 先填充第一个下拉框&#xff0c;然后写一个第一个下拉框的change事件来加载第二个下拉框&#xff0c;再写第二个下拉框的change事件来加载第三个下…

面试总结-c++

1该吹牛逼吹牛逼。在自己能说出个所以然的情况下&#xff0c;该吹就吹&#xff0c;不吹没工作&#xff0c;吹了有希望。 比如 c组长&#xff0c;确有其事&#xff0c;但是挺唬人。说自己在北京定居也是侧面吹牛逼&#xff0c;证明自己的能力。还有媳妇在研究所。 2.对自己做过…

uniapp:图片验证码检验问题处理

图形验证码功能实现 uniapp&#xff1a;解决图形验证码问题及利用arraybuffer二进制转base64格式图片&#xff08;后端传的图片数据形式&#xff1a;x00\x10JFIF\x00\x01\x02\x00…&#xff09;_❆VE❆的博客-CSDN博客 UI稿&#xff1a; 需求&#xff1a;向后端请求验证码图片&…

Flask 是什么?Flask框架详解及实践指南

Flask 是一个轻量级的 Python Web 框架&#xff0c;它被广泛用于构建 Web 应用程序和 API。Flask 简单易用&#xff0c;具有灵活性和可扩展性&#xff0c;是许多开发者喜欢用其构建项目的原因。本文将介绍 Flask 是什么以及如何使用它来构建 Web 应用程序&#xff0c;同时提供一…

《Java-SE-第二十八章》之CAS

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页&#xff1a;KC老衲爱尼姑的博客主页 博主的github&#xff0c;平常所写代码皆在于此 共勉&#xff1a;talk is cheap, show me the code 作者是爪哇岛的新手&#xff0c;水平很有限&…

Cocos Creator的 Cannot read property ‘applyForce‘ of undefined报错

序&#xff1a; 1、博主是看了这个教程操作的时候出的bug>游戏开发 | 17节课学会如何用Cocos Creator制作3D跑酷游戏 | P9 代码控制对象移动_哔哩哔哩_bilibili 2、其实问题不是出在代码上&#xff0c;但是发现物体就是不平移 3、node全栈的资料》node全栈框架 正文…

【文件操作:解锁高效读写与管理技巧】

本章重点 为什么使用文件 什么是文件 文件的打开和关闭 文件的顺序读写 文件的随机读写 文本文件和二进制文件 文件读取结束的判定 文件缓冲区 1. 为什么使用文件 C语言中的变量和数据通常只在程序运行时存在于内存中&#xff0c;一旦程序结束&#xff0c;这些数据就…

LeetCode933. 最近的请求次数

题干 写一个 RecentCounter 类来计算特定时间范围内最近的请求。 请你实现 RecentCounter 类&#xff1a; RecentCounter() 初始化计数器&#xff0c;请求数为 0 。int ping(int t) 在时间 t 添加一个新请求&#xff0c;其中 t 表示以毫秒为单位的某个时间&#xff0c;并返回…

三、PWM呼吸灯

1. 什么是呼吸灯 如下图中的蓝色LED灯,不再是亮灭交替,而是慢慢亮慢慢灭,这就是呼吸灯 生活中常见 2. 怎样实现? 答:用PWM

Android中级——RemoteView

RemoteView RemoteView的应用NotificationWidgetPendingIntent RemoteViews内部机制模拟RemoteViews RemoteView的应用 Notification 如下开启一个系统的通知栏&#xff0c;点击后跳转到某网页 public class MainActivity extends AppCompatActivity {private static final …

项目一:基于stm32的阿里云智慧消防监控系统

若该文为原创文章&#xff0c;转载请注明原文出处。 Hi&#xff0c;大家好&#xff0c;我是忆枫&#xff0c;今天向大家介绍一个单片机项目。 一、简介 智慧消防监控系统&#xff0c;是用于检测火灾&#xff0c;温度&#xff0c;烟雾的监控系统。以 stm32单片机为核心外加 MQ…

一次redis缓存不均衡优化经验

背景 高并发接口&#xff0c;引入redis作为缓存之后&#xff0c;运行一段时间发现redis各个节点在高峰时段的访问量严重不均衡&#xff0c;有的节点访问量7000次/s&#xff0c;有的节点访问量500次/s 此种现象虽然暂时不影响系统使用&#xff0c;但是始终是个安全隐患&#x…

ARM进阶:内存屏障(DMB/DSB/ISB)的20个使用例子详解

在上一节内存屏障指令之DMB、DSB和ISB详解中&#xff0c;介绍了一下内存屏障的三个指令的作用并举了一些例子&#xff0c;对于内存屏障指令的使用时机&#xff0c;与处理器架构(比如Cortex-M和Cortex-A)和处理器的系统实现(同样的架构&#xff0c;有不同的实现&#xff0c;如ST…

大厂必面:你们系统qps多少,怎么部署的?假设每天有几千万请求,该如何部署?

前言 在40岁老架构师 尼恩的读者社区(50)中&#xff0c;很多小伙伴要拿高薪&#xff0c;这就要面大厂、面架构&#xff0c;拿高薪。 在高级开发面试、大厂面试、架构师的面试过程中&#xff0c;常常会遇到下面的问题&#xff1a; 你们系统qps多少&#xff1f;怎么部署的&…