Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池,所以我们就要认识并弄懂线程池,以便于更好的为我们业务场景服务。

一、线程池的好处

在开发过程中,合理地使用线程池大致有3个好处

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,
还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用
线程池,必须对其实现原理了如指掌

二、线程池工作流程

1)当提交一个新任务到线程池时,线程池判断corePoolSize线程池是否都在执行任务,如果有空闲线程,则从核心线程池中取一个线程来执行任务,直到当前线程数等于corePoolSize;

2)如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;

3)如果阻塞队列满了,那就创建新的线程执行当前任务,直到线程池中的线程数达到maxPoolSize,这时再有任务来,由饱和策略来处理提交的任务

三、线程池参数

下面是ThreadPoolExecutor类的构造方法传参数

pblic ThreadPoolExecutor(int corePoolSize, #核心线程数
                              int maximumPoolSize,   #最大线程数
                              long keepAliveTime, #达到最大线程数数时候,线程池的工作线程空闲后,保持存活的时间
                              TimeUnit unit,     #keepAliveTime单位
                              BlockingQueue<Runnable> workQueue #阻塞队列
                              RejectedExecutionHandler handler  #饱和策略

) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }


new ThreadPoolExecutor(6 ,12, 5L, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(10),new ThreadPoolExecutor.CallerRunsPolicy());

比如corePoolSize为6, maximumPoolSize为,12  keepAliveTime为5秒,队列长度为10;提交任务数达到核心线程数6时候,新来的任务就会被放入LinkedBlockingQueue阻塞队列,当队列任务数达到10个时候,就会创建新线程执行任务,直到达到maximumPoolSize数量12;如果还有新来的任务,由策略来处理提交的任务;如果没有,线程池空闲时候,超过5秒,创建的maximumPoolSize,就会被销毁。

四、阻塞队列

阻塞队列BlockingQueue接口,从jdk1.5开始,有四个实现类,jdk8亦是如此

ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。

LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue,静态工厂方法Executors.newFixedThreadPool()使用了这个队列。

SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个列。

PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

五、四种饱和策略

RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常,查看源码,从jdk5开始RejectedExecutionHandler接口有四个实现类,jdk8亦是如此。

AbortPolicy:不处理,直接抛出异常。
CallerRunsPolicy:若线程池还没关闭,调用当前所在线程来运行任务,r.run()执行。
DiscardOldestPolicy:LRU策略,丢弃队列里最近最久不使用的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉,不抛出异常。

六、向线程池提交任务

可以使用两个方法向线程池提交任务,分别为execute()和submit()方法

1、execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功

2、submit()方法用于提交需要返回值的任务。

线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit),在指定的时间内会等待任务执行,超时则抛出超时异常,等待时候会阻塞当前线程

七、关闭线程池

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),它们的原理是遍历线
程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务
都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true

因此,判断线程池所有线程是否执行完成,可以这样写

while(true){//死循环if(threadPool.isTerminated()) {//执行自己的操作break;//true停止}Thread.sleep(500);//休眠500继续循环
}

shutdown,只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线
程,等待执行任务的线程完成。

shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,

并返回等待执行任务的列表

八、线程池状态

线程池有五种运行状态

1、RUNNING

(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。 
(2) 状态切换:线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,且线程池中的任务数为0

2、 SHUTDOWN

(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。 
(2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

3、STOP

(1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。 
(2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4、TIDYING

(1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。 
(2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

5、 TERMINATED

(1) 状态说明:线程池彻底终止,就变成TERMINATED状态。 
(2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

源码中可以看到

private static final int COUNT_BITS = Integer.SIZE - 3;//32-3=29

// runState is stored in the high-order bits  运行状态存储在高位

//左移动29位,即-1的29次方,转换为二进制11100000000000000000000000000000
   private static final int RUNNING    = -1 << COUNT_BITS;

//即0的29次方,转换为二进制00000000000000000000000000000000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;

//即1的29次方,转换为二进制00100000000000000000000000000000
    private static final int STOP       =  1 << COUNT_BITS;

//即2的29次方,转换为二进制01000000000000000000000000000000
    private static final int TIDYING    =  2 << COUNT_BITS;

//即3的29次方,转换为二进制01100000000000000000000000000000
    private static final int TERMINATED =  3 << COUNT_BITS;

合并线程池状态与工作线程数量:ctlOf 

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

//表示线程池最大容量,1的29次方减去1,即536870911,二进制表示00011111111111111111111111111111
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

//rs线程池状态,wc工作线程数量,两者按位或,得到ctl ,高3位表示线程池状态,低29位表示工作线程数量
    private static int ctlOf(int rs, int wc) { return rs | wc; }

线程池状态:runStateOf

private static int runStateOf(int c)     { return c & ~CAPACITY; }

描述:参数c=ctl.get(),get到合并值ctl,c & ~CAPACITY与运算,得到高3位就是线程状态

释义:CAPACITY   = (1 << COUNT_BITS) - 1的值536870911,"~"表示取反

536870911转换二进制 00011111111111111111111111111111,取反后 11100000000000000000000000000000

参数c转为二进制后,其高3位与111按位与,得到的高3位是参数c的高3位

CAPACITY取反的低29位为零,所以参数c与之低29位按位与,得到的低29位是CAPACITY取反的低29位,全为0

示例:

xxx0000000000000000000000001100  & 11100000000000000000000000000000
按位与后xxx000000000000000000000000000 即取参数c的高3位,CAPACITY的低29位全为0

所为参数c,即ctl,高3位表示线程池状态

工作线程数量:workerCountOf 

private static int workerCountOf(int c)  { return c & CAPACITY; }

描述:参数c=ctl.get(),get到合并值ctl ,c & CAPACITY运算,得到低29位,即线程池线程数量

释义:CAPACITY   = (1 << COUNT_BITS) - 1的值536870911

536870911转换二进制 00011111111111111111111111111111

参数c转为二进制后,其高3位与000按位与后,得到的高3位为CAPACITY 的高3位,即000

CAPACIT的低29位都为1,所以参数c低29位与之按位与,得到的低29位总是参数c的低29位

示例:

010xxxxxxxxxxxxxxxxxxxxxxxxxxxx  & 00011111111111111111111111111111
按位与后000xxxxxxxxxxxxxxxxxxxxxxxxxxxx 即取CAPACITY得高3位000,参数c的低29位

所为参数c,即ctl,低29位表示线程数量

九、调整线程池大小

 ThreadPoolExecutor提供了setter方法,因此创建线程池之后,也可以动态调整线程池大小

十、监控线程池

可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性,获取线程池任务状况

taskCount:线程池需要执行的任务数量。
completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是
否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销
毁,所以这个大小只增不减。
getActiveCount:获取活动的线程数。

也可以通过扩展线程池进行监控。可以通过继承线程池来自定义线程池,重写线程池的beforeExecute、afterExecute和terminated方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。

例如,监控任务的平均执行时间、最大执行时间和最小执行时间等,这几个方法在线程池里是空方法

参考:《Java并发编程的艺术》

参考:http://blog.51cto.com/10594544/2176728

Java线程池工作原理相关推荐

  1. 【源码阅读计划】浅析 Java 线程池工作原理及核心源码

    [源码阅读计划]浅析 Java 线程池工作原理及核心源码 为什么要用线程池? 线程池的设计 线程池如何维护自身状态? 线程池如何管理任务? execute函数执行过程(分配) getTask 函数(获 ...

  2. java线程池工作原理和实现原理

    为什么要使用线程池? 1.使用线程池可以复用池中的线程,不需要每次都创建新线程,减少创建和销毁线程的开销: 2.同时,线程池具有队列缓冲策略.拒绝机制和动态管理线程个数,特定的线程池还具有定时执行.周 ...

  3. java 池化_溯本求源: JAVA线程池工作原理

    1. 前言 线程池是JAVA开发中最常使用的池化技术之一,可以减少线程资源的重复创建与销毁造成的开销. 2. 灵魂拷问:怎么做到线程重复利用? 很多同学会联想到连接池,理所当然的说:需要的时候从池中取 ...

  4. JAVA 线程池工作原理 图解

    详细图见:https://www.processon.com/view/5ee48dc7f346fb1ae55fd42d

  5. Java 线程池的原理与实现

    最近在学习线程池.内存控制等关于提高程序运行性能方面的编程技术,在网上看到有一哥们写得不错,故和大家一起分享. [分享]Java 线程池的原理与实现 这几天主要是狂看源程序,在弥补了一些以前知识空白的 ...

  6. 【有料】Java线程池实现原理及其在美团业务中的实践

    随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供的线程池:ThreadPoolExecutor类,帮助开发人员 ...

  7. 25张图展示线程池工作原理和实现原理,建议认真阅读,对你有帮助

    上篇<这样的API网关查询接口优化,我是被迫的>文章末尾,有朋友留言提到文中的场景是IO密集型操作,不是CPU密集操作,不需要使用线程池,我猜这位朋友可能想表达的是IO密集且阻塞时间久的不 ...

  8. 线程池工作原理和实现原理

    为什么要使用线程池 平时讨论多线程处理,大佬们必定会说使用线程池,那为什么要使用线程池?其实,这个问题可以反过来思考一下,不使用线程池会怎么样?当需要多线程并发执行任务时,只能不断的通过new Thr ...

  9. Java 线程池(ThreadPoolExecutor)原理分析与使用 – 码农网

    线程池的详解 Java 线程池(ThreadPoolExecutor)原理分析与使用 – 码农网 http://www.codeceo.com/article/java-threadpool-exec ...

  10. Java线程池实现原理及其在美团业务中的实践

    来自:美团技术团队 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供的线程池ThreadPoolExecuto ...

最新文章

  1. pyspatialite
  2. 《数据结构》知识点Day_03
  3. linux如何查看所有的用户(user)、用户组(group)、密码(password/passwd)
  4. java数组根据下标插入数据
  5. [vue] vue项目有使用过npm run build --report吗?
  6. 数学题 HDOJ——2086 简单归纳
  7. 顺丰同城:香港IPO发行价定为16.42港元
  8. 打印pdf文件 vfp_将Excel转换成PDF的工具有哪些?
  9. SurfaceView 之满屏的代码雨效果
  10. 【Python学习笔记】《和孩子一起学编程》第2章 记住内存和变量
  11. Keil5 显示汉字时字体不生效,设置国标时,字体设置无效。
  12. slickedit编写linux内核驱动,slickedit 2016 linux下载
  13. java p12证书,如何使用已安装的.p12 证书在 Mac OS X 上签名 jar?
  14. matlab添加坐标,Matlab绘图添加直角坐标轴
  15. Delphi 人民币大小写转换
  16. 1003【顺序结构】A+B 问题
  17. 手机拍的照片计算机内存不足怎么办,手机内存不够用,照片应该怎么处理才能够少占用内存?...
  18. IDEA使用database时,连接MySQL后schemas不显示数据库名的情况
  19. java计算机毕业设计汽车客运站票务管理系统源代码+数据库+系统+lw文档
  20. 基于点灯科技平台的智能开关设计

热门文章

  1. 《打造Facebook》 读书报告
  2. 华为外包员工是什么样的群体?
  3. 大学生活这样过,等着 Offer 飞来找 | 程序员有话说
  4. 腾讯云IM集成问题汇总
  5. 2022年全球市场HTCC陶瓷封装总体规模、主要生产商、主要地区、产品和应用细分研究报告
  6. 太极图形html5代码,canvas绘制太极图的实现示例
  7. 三角形中的欧拉公式证明过程
  8. 创始人之间应该如何量化分配股权?
  9. 网站被挂马的解决方案
  10. 【云周刊】第121期:图管够!灌篮高手、女儿国…阿里日,这帮程序员太会玩了!