首先看下类的继承关系,不多介绍:

public interface Executor {void execute(Runnable);}
public interface ExecutorService extends Executor {...}
public abstract class AbstractExecutorService implements ExecutorService {...}
public class ThreadPoolExecutor extends AbstractExecutorService {...}

线程池构造器七大参数:

核心线程数,最大线程数,生存时间,时间单位,任务队列,线程工厂,拒绝策略

public ThreadPoolExecutor(int corePoolSize,		//核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //生存时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //任务队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler) //拒绝策略

先对线程池有个大概的概念:线程池,有若干个运行中的线程(工作者,Worker),负责从任务队列(workQueue)中取任务(Task)出来,并执行它。

private final BlockingQueue<Runnable> workQueue;
private final HashSet<Worker> workers = new HashSet<Worker>();

这里再大概介绍一下Worker类:

Worker类内部有两个关键引用:线程Thread t、待执行任务Runnable firstTask

并且其自身就是Runnable,其run()方法调用自身的runWorker()方法,稍后再来介绍runWorker()干了啥。

回到线程池的使用:一般都是调用submit()或者execute()submit()只是把传入的Runnable包装成FutureTask来保存执行结果,本质也是调用execute()方法。

因此我们主要分析execute()方法:

结合代码和注释,可以得出其执行流程:public void execute(Runnable command)

  1. 不够核心线程数的时候,起新线程(addWorker())
  2. 核心线程满的时候把command放进workQueue队列
  3. 核心线程和队列都满,不够最大线程数的时候,起新线程
  4. 否则执行拒绝策略

其中最关键的当然是创建新线程执行任务的过程,addWorker()方法:

大概描述一下addWorker()的执行步骤:

  1. 双重CAS把工作线程数加一
  2. new一个Worker w,并放入workers(HashSet)。
  3. 放入成功则执行w.t.start()(即会调用w.run()

其中,最初传入的command作为wfirstTaskw.t是用线程工厂创建一个新线程,把w自己作为Runnable传入。

w.run()方法直接执行runWorker()方法:

描述一下大概执行过程:

  1. 把 task 取出来:task = w.firstTask; w.firstTask = null;
  2. 首先执行 task ,然后循环从阻塞队列 workQueue 中获取一个 task 来执行
  3. 获取不到任务时,结束运行。结束之前执行一些后续处理。

此外,有几个小问题值得一提:

  • 非核心线程与核心线程的区别:

并没有这种区别。从源码可以看到,addWorker()方法的参数boolean core并不会用于创建不同类型的Worker。只在新建Worker之前判断“核心线程是否已满”:core=true时,判断工作线程数是否大于corePoolSize,是则返回false而不新建Workercore=false时,判断工作线程数是否大于maximumPoolSize,是则返回false而不新建Worker

  • 那怎么使得核心线程不被销毁而非核心线程被销毁呢?

可以看到,如果当前的工作线程数大于核心线程数,则从任务队列中取任务的方法则从阻塞的take()方法换为超时等待keepAliveTime时长的poll()。当非核心线程闲置(任务队列没有任务)的时候,等待一会从getTask()方法返回null,于是线程结束。

其中allowCoreThreadTimeOut属性指示keepAliveTime是否也会作用于核心线程。

并且,线程结束之前有“后续处理”:

可以看到,如果当前的工作线程数小于核心线程数,则新建一个没有task的线程(等待任务队列中的任务到来)。


最后提一下线程工厂和拒绝策略:

  • Executors提供的默认线程工厂DefaultThreadFactory其实内部也是new Thread的方式来新建线程,分配pool-i-thread-j这样的线程名称。当然最好自己实现线程工厂来分配有意义的线程名,方便查错。

  • ThreadPoolExecutor提供四种拒绝策略。当然,最好是根据需求自己实现拒绝策略。

    • AbortPolicy:抛出异常
    • DiscardPolicy:扔掉任务,不抛异常
    • DiscardOldestPolicy:扔掉排队时间最久的任务
    • CallerRunsPolicy:调用者负责处理任务

浅析线程池 ThreadPoolExecutor 源码的更多相关文章

  1. 【Java并发编程】21、线程池ThreadPoolExecutor源码解析

    一.前言 JUC这部分还有线程池这一块没有分析,需要抓紧时间分析,下面开始ThreadPoolExecutor,其是线程池的基础,分析完了这个类会简化之后的分析,线程池可以解决两个不同问题:由于减少了 ...

  2. Java并发之线程池ThreadPoolExecutor源码分析学习

    线程池学习 以下所有内容以及源码分析都是基于JDK1.8的,请知悉. 我写博客就真的比较没有顺序了,这可能跟我的学习方式有关,我自己也觉得这样挺不好的,但是没办法说服自己去改变,所以也只能这样想到什么 ...

  3. Python线程池ThreadPoolExecutor源码分析

    在学习concurrent库时遇到了一些问题,后来搞清楚了,这里记录一下 先看个例子: import time from concurrent.futures import ThreadPoolExe ...

  4. Java核心复习——线程池ThreadPoolExecutor源码分析

    一.线程池的介绍 线程池一种性能优化的重要手段.优化点在于创建线程和销毁线程会带来资源和时间上的消耗,而且线程池可以对线程进行管理,则可以减少这种损耗. 使用线程池的好处如下: 降低资源的消耗 提高响 ...

  5. 线程池ThreadPoolExecutor源码解读研究(JDK1.8)

    一.什么是线程池 为什么要使用线程池?在多线程并发开发中,线程的数量较多,且每个线程执行一定的时间后就结束了,下一个线程任务到来还需要重新创建线程,这样线程数量特别庞大的时候,频繁的创建线程和销毁线程 ...

  6. java内置线程池ThreadPoolExecutor源码学习记录

    背景 公司业务性能优化,使用java自带的Executors.newFixedThreadPool()方法生成线程池.但是其内部定义的LinkedBlockingQueue容量是Integer.MAX ...

  7. 线程池ThreadPoolExecutor源码分析

    在阿里编程规约中关于线程池强制了两点,如下: [强制]线程资源必须通过线程池提供,不允许在应用中自行显式创建线程.说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源 ...

  8. Java并发包源码学习系列:线程池ThreadPoolExecutor源码解析

    目录 ThreadPoolExecutor概述 线程池解决的优点 线程池处理流程 创建线程池 重要常量及字段 线程池的五种状态及转换 ThreadPoolExecutor构造参数及参数意义 Work类 ...

  9. Java并发包源码学习系列:线程池ScheduledThreadPoolExecutor源码解析

    目录 ScheduledThreadPoolExecutor概述 类图结构 ScheduledExecutorService ScheduledFutureTask FutureTask schedu ...

随机推荐

  1. PTA题目集总结

    PTA题目集1-3总结 一:前言 我认为题目集一的有八个题目,题量可能稍微有点多,但是题型较为简单,基本为入门题:题集二有五道题,题量适度,难度也适中:题集三虽然只有三道题,但是难度却骤然提升,前两题 ...

  2. Python3中列表、字典、元组、集合的看法

    文首,我先强调一下我是一个弱鸡码农,这个随笔是在我学习完Python3中的元组.字典.列表,集合这四种常见数据的数据类型的一些感想,如果有什么不对的地方欢迎大家予以指正.谢谢大家啦 回归正题:这篇随笔 ...

  3. ubuntu初识

    简单的Linux系统理解 Ubuntu ctrl+alt 可以令虚拟机释放鼠标,回到主系统中. 如果ubuntu有问题,如何重装? -------直接删除目录.虚拟机内的操作不会影响到宿主机. 1.挂 ...

  4. 浅谈.Net Core中使用Autofac替换自带的DI容器

    为什么叫 浅谈 呢?就是字面上的意思,讲得比较浅,又不是不能用(这样是不对的)!!! Aufofac大家都不陌生了,说是.Net生态下最优秀的IOC框架那是一点都过分.用的人多了,使用教程也十分丰富, ...

  5. 安卓控件RecycleView的简单使用

    RecycleView的使用 目录 RecycleView的使用 技术概述 技术详述 遇到问题和解决 总结 参考文献 技术概述 RecycleView是谷歌官方对ListView的改进(并不是替代), ...

  6. 我是如何用 ThreadLocal 虐面试官的?

    我是陈皮,一个在互联网 Coding 的 ITer,微信搜索「陈皮的JavaLib」第一时间阅读最新文章,回复[资料],即可获得我精心整理的技术资料,电子书籍,一线大厂面试资料和优秀简历模板. Thr ...

  7. 十八、.net core(.NET 6)搭建ElasticSearch(ES)系列之使用Logstash通过Rabbitmq接收Serilog日志到ES

    使用Logstash通过Rabbitmq接收Serilog日志到ES 首先,要部署logstash 为了与前面的ElasticSearch版本保持一致,此处Logstash下载的版本也是7.13.1, ...

  8. 重新整理 .net core 实践篇————polly失败重试[三十四]

    前言 简单整理一下polly 重试. 正文 在开发程序中一般都有一个重试帮助类,那么polly同样有这个功能. polly 组件包: polly 功能包 polly.Extensions.Http 专 ...

  9. Kubernetes之无头服务(headless)

    己经看到如何使用服务来提供稳定的ip地址,从而允许客户端连接到支持服务的每个pod (或其他端点).到服务的每个连接都被转发到一个随机选择的pod上.但是如果客户端需要链接到所有的pod呢?如果后端的 ...

  10. <题解>世界树

    世界树<题解> 首先我们拿到这个题之后,能想到的一定是虚树,如果想不到的话,还是重新学一遍去吧 所以我们应该怎么做呢 虚树的板子不需要我再讲一遍了吧 所以对于这个题来说,怎么根据虚树上的节 ...