目录

  • 一、简介
  • 二、线程池任务场景
    • 场景一:提交5个任务,执行总耗时500ms
    • 场景二:提交10个任务,执行总耗时500ms
    • 场景三:提交11个任务,执行总耗时1000ms
    • 场景四:提交20个任务,执行总耗时1000ms
    • 场景五:提交30个任务,执行总耗时1500ms
    • 场景六:提交40个任务,执行总耗时2000ms
    • 场景七:提交41个任务,执行总耗时2000ms
    • 场景八:提交45个任务,执行总耗时1500ms
    • 场景九:提交50个任务,执行总耗时1500ms
    • 场景十:提交51个任务,执行总耗时1500ms
  • 三、总结

线程池原理详见:Java基础——深入理解Java线程池

一、简介

网上有很多关于 线程池原理 的讲解,理论说的多了你也不一定记得住,也不一定理解的了,下面我通过一个DEMO,加上多个任务场景,让你能够直观的理解线程池的工作流程。

二、线程池任务场景

线程池有几个关键参数:核心线程数、最大线程数、回收时间、队列等。

我们DEMO使用自定义线程池方式来讲解线程池的工作流程,代码如下:

package com.example.springbootdemo.util;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;public class ThreadPoolTest {// 任务数private static int taskCount = 50;// 实际完成任务数private static AtomicInteger taskCountExecuted;public static void main(String[] args) {init();}private static void init(){taskCountExecuted = new AtomicInteger(0);ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,        // 核心线程数20,    // 最大线程数5,        // 非核心线程回收超时时间TimeUnit.SECONDS,     // 超时时间单位new ArrayBlockingQueue<>(30)      // 任务队列);System.out.println("任务总数 = [" + taskCount + "]个");long start = System.currentTimeMillis();for(int i=0; i<taskCount; i++){Runnable runnable = new Runnable() {@Overridepublic void run() {try{Thread.sleep(500);System.out.println("已执行第 [" + taskCountExecuted.addAndGet(1) + "] 个任务");}catch (Exception ex){ex.printStackTrace();}}};try{// 默认拒绝策略会报错threadPoolExecutor.execute(runnable);}catch (Exception ex){ex.printStackTrace();taskCount = threadPoolExecutor.getActiveCount() + threadPoolExecutor.getQueue().size();}}long end = 0;while (threadPoolExecutor.getCompletedTaskCount() < taskCount){end = System.currentTimeMillis();}System.out.println("[" + taskCountExecuted + "]个任务执行总耗时 = [" + (end - start) + "]ms");threadPoolExecutor.shutdown();}
}

说明:我们DEMO中新建了个线程池,核心线程数10,最大线程数20,任务队列容量30。

执行每个任务场景只需修改 taskCount 值即可。

场景一:提交5个任务,执行总耗时500ms

taskCount = 5;

执行结果:

任务总数 = [5]个
已执行第 [2] 个任务
已执行第 [3] 个任务
已执行第 [4] 个任务
已执行第 [1] 个任务
已执行第 [5] 个任务
[5]个任务执行总耗时 = [506]ms

分析:核心线程数为10,也就是说有10个线程数长期处于活动状态,即来任务立马就能执行,任务数5 < 核心线程数10,所以,5个任务立马执行完成,且是多线程并行执行,所以任务执行总耗时 = 500ms

注:我们日志输出的是 505ms ,因为代码执行也是要花时间的。

场景二:提交10个任务,执行总耗时500ms

taskCount = 10;

执行结果:

任务总数 = [10]个
已执行第 [2] 个任务
已执行第 [8] 个任务
已执行第 [5] 个任务
...
已执行第 [3] 个任务
已执行第 [9] 个任务
已执行第 [10] 个任务
[10]个任务执行总耗时 = [507]ms

分析:任务数10 <= 核心线程数10,10个任务立马执行完成,所以任务执行总耗时 = 500ms。

场景三:提交11个任务,执行总耗时1000ms

taskCount = 11;

执行结果:

任务总数 = [11]个
已执行第 [1] 个任务
已执行第 [3] 个任务
已执行第 [4] 个任务
...
已执行第 [8] 个任务
已执行第 [7] 个任务
已执行第 [11] 个任务
[11]个任务执行总耗时 = [1009]ms

分析:任务执行总耗时 = 1000ms,别惊讶,这里是很多人没有搞懂线程池运行机制的关键点,虽然任务只多个一个,但是地11个任务不是立马执行的,核心线程数为10,第11个任务会进入到任务队列,等核心线程有空出来后会从任务队列中取出任务再来执行,因此任务总耗时 = 1000ms。

场景四:提交20个任务,执行总耗时1000ms

taskCount = 20;

执行结果:

任务总数 = [20]个
已执行第 [5] 个任务
已执行第 [4] 个任务
已执行第 [2] 个任务
...
已执行第 [16] 个任务
已执行第 [19] 个任务
已执行第 [20] 个任务
[20]个任务执行总耗时 = [1010]ms

分析:任务执行总耗时 = 1000ms,此处与前一个场景一样,第11到第20共10个任务会进入到任务队列,等核心线程有空出来后会从任务队列中取出任务再来执行,因为有10个核心线程,前10个任务执行完成后,任务队列中的10个任务正好由空出的10个核心线程来执行,因此任务总耗时 = 1000ms。

场景五:提交30个任务,执行总耗时1500ms

taskCount = 30;

执行结果:

任务总数 = [30]个
已执行第 [2] 个任务
已执行第 [6] 个任务
已执行第 [3] 个任务
...
已执行第 [23] 个任务
已执行第 [25] 个任务
已执行第 [30] 个任务
[30]个任务执行总耗时 = [1514]ms

分析:任务执行总耗时 = 1500ms,此处与前一个场景一样,第11到第30共20个任务会进入到任务队列,等核心线程有空出来后会从任务队列中取出任务再来执行,因为有10个核心线程,前10个任务执行完成后,从任务队列中取出10个任务由空出的10个核心线程来执行,执行完后在取出10个任务来执行,因此任务总耗时 = 1500ms。

场景六:提交40个任务,执行总耗时2000ms

taskCount = 40;

执行结果:

任务总数 = [40]个
已执行第 [1] 个任务
已执行第 [2] 个任务
已执行第 [4] 个任务
...
已执行第 [32] 个任务
已执行第 [34] 个任务
已执行第 [40] 个任务
[40]个任务执行总耗时 = [2016]ms

分析:任务执行总耗时 = 2000ms,此处与前一个场景一样,这里不再过多解释。

场景七:提交41个任务,执行总耗时2000ms

taskCount = 41;

执行结果:

任务总数 = [41]个
已执行第 [1] 个任务
已执行第 [2] 个任务
已执行第 [3] 个任务
...
已执行第 [40] 个任务
已执行第 [37] 个任务
已执行第 [41] 个任务
[41]个任务执行总耗时 = [2016]ms

分析:任务执行总耗时 = 2000ms,这里重点来了,我们知道第11到第40共30个任务进入到了任务队列中(任务队列大小为30),第41个任务就创建了一个非核心线程(最大线程数20 - 核心线程数10 = 10个非核心线程)来执行,此时线程池中的活跃线程数为11,第一批任务执行完后,会从任务队列中取出11个任务来执行,那就是 11 + 11 + 11 + 8 = 500ms * 4 = 2000ms。

场景八:提交45个任务,执行总耗时1500ms

taskCount = 45;

执行结果:

任务总数 = [45]个
已执行第 [3] 个任务
已执行第 [4] 个任务
...
已执行第 [43] 个任务
已执行第 [42] 个任务
已执行第 [45] 个任务
[45]个任务执行总耗时 = [1516]ms

分析:任务执行总耗时 = 1500ms,此处与前一个场景一样,我们知道第11到第40共30个任务进入到了任务队列中(任务队列大小为30),第41到第45共5个任务就创建了5个非核心线程(最大线程数20 - 核心线程数10 = 10个非核心线程)来执行,此时线程池中的活跃线程数为15,第一批任务执行完后,会从任务队列中取出15个任务来执行,那就是 15 + 15 + 15 = 500ms * 3 = 1500ms。

场景九:提交50个任务,执行总耗时1500ms

taskCount = 50;

执行结果:

任务总数 = [50]个
已执行第 [1] 个任务
已执行第 [9] 个任务
已执行第 [12] 个任务
...
已执行第 [45] 个任务
已执行第 [44] 个任务
已执行第 [50] 个任务
[50]个任务执行总耗时 = [1515]ms

分析:任务执行总耗时 = 1500ms,此处与前一个场景一样,我们知道第11到第40共30个任务进入到了任务队列中(任务队列大小为30),第41到第50共10个任务就创建了10个非核心线程(最大线程数20 - 核心线程数10 = 10个非核心线程)来执行,此时线程池中的活跃线程数为20,第一批任务执行完后,会从任务队列中取出20个任务来执行,那就是 20 + 20 + 10 = 500ms * 3 = 1500ms。

场景十:提交51个任务,执行总耗时1500ms

taskCount = 10;

执行结果:

任务总数 = [51]个
java.util.concurrent.RejectedExecutionException: Task com.example.springbootdemo.util.ThreadPoolTest$1@682a0b20 rejected from java.util.concurrent.ThreadPoolExecutor@3d075dc0[Running, pool size = 20, active threads = 20, queued tasks = 30, completed tasks = 0]at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)at com.example.springbootdemo.util.ThreadPoolTest.init(ThreadPoolTest.java:48)at com.example.springbootdemo.util.ThreadPoolTest.main(ThreadPoolTest.java:16)
已执行第 [3] 个任务
已执行第 [5] 个任务
...
已执行第 [49] 个任务
已执行第 [48] 个任务
已执行第 [50] 个任务
[50]个任务执行总耗时 = [1514]ms

分析:任务执行总耗时 = 1500ms,此处与前一个场景一样,由于线程池同时最大能接收50个任务(最大线程数20 + 任务队列大小30 = 50),所以第51个任务被拒绝了(线程池使用默认拒绝策略AbortPolicy),抛出了异常,DEMO中使用了try-catch捕获到了。

三、总结

  • 任务数 <= 核心线程数,线程池中工作线程数 = 任务数

  • 核心线程数 < 任务数 <= (核心线程数 + 队列容量)时,线程池中工作线程数 = 核心线程数

  • 核心线程数 + 队列容量) < 任务数 <= (最大线程数 + 队列容量)时,线程池中工作线程数 = (任务数 - 队列容量);

一个DEMO让你彻底理解线程池相关推荐

  1. 一个demo让你彻底理解线程池工作流程

    网上关于线程池的八股文太多了我不多说,说了你也记不住,记住了也理解不了,理解了也不会用- 想了很久,终于想出一个demo,加上十个场景,让你能逐步理解线程池真正的工作流程 相信我,认真看完这篇文章,你 ...

  2. Java核心(二)深入理解线程池ThreadPool

    本文你将获得以下信息: 线程池源码解读 线程池执行流程分析 带返回值的线程池实现 延迟线程池实现 为了方便读者理解,本文会由浅入深,先从线程池的使用开始再延伸到源码解读和源码分析等高级内容,读者可根据 ...

  3. Java提高班(二)深入理解线程池ThreadPool

    本文你将获得以下信息: 线程池源码解读 线程池执行流程分析 带返回值的线程池实现 延迟线程池实现 为了方便读者理解,本文会由浅入深,先从线程池的使用开始再延伸到源码解读和源码分析等高级内容,读者可根据 ...

  4. 通过ThreadPoolExecutor与ForkJoinPool比较,分别对比其execute ,submit 等方法提交线程池任务的区别,来深入理解线程池及并发编程

    前言 以前使用线程池,对execute . submit 等方法提交线程池任务的区别比较模糊,现在通过ThreadPoolExecutor与ForkJoinPool比较,分别对比其execute ,s ...

  5. 深入理解线程池(ThreadPoolExecutor)——细致入微的讲源码。

    在上一篇博文<图解线程池原理>中,大体上介绍了线程池的工作原理. 这一篇从源码层面,细致剖析,文章会很长. 如果上篇文章内容没吸收,先看上篇,先易后难嘛. 本文源码是 java 1.8 版 ...

  6. java如何创建一个两个数的队列_java线程池 如何构建一个线程立即到拉到MAX数量跑业务,线程到MAX了,额外的队列可以存储任务的线程池...

    背景:JDK的线程池的运作原理 : JDK的线程池的构造函数有7个参数,分别是corePoolSize.maximumPoolSize.keepAliveTime.unit.workQueue.thr ...

  7. 通过transmittable-thread-local源码理解线程池线程本地变量传递的原理

    前提 最近一两个月花了很大的功夫做UCloud服务和中间件迁移到阿里云的工作,没什么空闲时间撸文.想起很早之前写过ThreadLocal的源码分析相关文章,里面提到了ThreadLocal存在一个不能 ...

  8. 深入理解线程池 ThreadPoolExecutor

    前言 网上关于线程池详解的文章很多,陆续也看了不少,从刚开始的怎么使用到原理解析,但是一直想自己写一篇,记录一下自己的学习历程,之前看源码一直是一知半解,不得要领,最近静下心来仔细看,发现以前看不懂的 ...

  9. 如何优雅的使用和理解线程池

    前言 平时接触过多线程开发的童鞋应该都或多或少了解过线程池,之前发布的<阿里巴巴 Java 手册>里也有一条: 可见线程池的重要性. 简单来说使用线程池有以下几个目的: 线程是稀缺资源,不 ...

最新文章

  1. Linux内核情景分析之异常访问,用户堆栈的扩展
  2. 腾讯35亿美元抄底收购搜狗,产品张小龙和技术王小川双剑合璧
  3. php中使用mysql_fetch_array输出数组至页面中展示
  4. html代码转换成为纯文本
  5. Robot Perception for Indoor Navigation《室内导航中的机器人感知》
  6. DotNetNuke各项业务外包
  7. OpenStack架构企业IT应用的敏捷实践
  8. poj 2346(DP)
  9. 计算机视觉学习--计算机视觉的古老秘密系列视频
  10. ESP8266开发笔记
  11. 『设计模式』适配器模式(Adapter)
  12. Redis:15---键迁移(move、dump、restore、migrate)
  13. Git学习的最佳教程
  14. GLSL/C++ 实现滤镜效果
  15. poi 读取excel
  16. 优秀的程序员是这样的
  17. 使用链接时间代码生成
  18. DELPHI 编程(一) __快速认识Delphi
  19. java语言的基本介绍及相关特性
  20. 自己组装电脑配置清单 2021年组装电脑配置清单推荐

热门文章

  1. python中回车怎么表示_如何在python中使用读取行仅拆分回车符?
  2. python tkinter listbox_Python3 tkinter基础 Listbox for+insert 将list中元素导入listbox中
  3. 【转】肺小结节就诊指南:4种CT的区别及如何选择?
  4. 【转】ABP源码分析三十一:ABP.AutoMapper
  5. 【转】0.SharePoint服务器端对象模型 之 序言
  6. 一步步编写操作系统 15 CPU与外设通信——IO接口,下
  7. 您需要来自pc的权限才能_微信电脑版还是鸡肋吗?微信PC版3.0内测体验
  8. 【Python学习】 - 如何将Pandas包中的groupby分组类型转换成DataFrame(一步就足够了)
  9. 【PTA天梯赛CCCC -2017决赛L2-3】图着色问题 (25 分)(图染色)
  10. hostapd 进程启动不了_项目管理|项目启动会实操要点,项目经理掌权的关键