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

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. 6.17考试总结(NOIP模拟8)[星际旅行·砍树·超级树·求和]

    6.17考试总结(NOIP模拟8) 背景 考得不咋样,有一个非常遗憾的地方:最后一题少取膜了,\(100pts->40pts\),改了这么多年的错还是头一回看见以下的情景... T1星际旅行 前 ...

  2. DBA入门相关知识介绍

    DBA(database administrator):数据库管理员                           DBMS(database management system):数据库管理系 ...

  3. 一、RabbitMQ 概念详解和应用

    消息队列和同步请求的区别 无论RabbitMQ还是Kafka,本质上都是提供了基于message或事件驱动异步处理业务的能力,相比于http和rpc的直接调用,它有着不可替代的优势: 1. 解耦,解耦 ...

  4. Windows10上基于Visual Studio Code安装Golang开发环境

    GoLang简介 Go编程语言是一个开源项目,它使程序员更具生产力. Go语言具有很强的表达能力,它简洁.清晰而高效.得益于其并发机制,用它编写的程序能够非常有效地利用多核与联网的计算机,其新颖的类型 ...

  5. 11、linux的目录结构

    11.1.查看磁盘的id: blkid 11.2.linux目录类似一个倒挂的树: / 是所有目录的顶点,目录磁盘和分区是没有关联的,因此/下不同的目录会对应不同的磁盘的不同的分区: linux中硬盘 ...

  6. 9、ssh服务详解及优化

    ssh是"secure shell protocol"的简写,在进行数据传输之前,ssh先对练级数据包通过加密技术进行加密处理, 加密后再进行数据的传输,从而确保了数据的安全: s ...

  7. mapboxgl 互联网地图纠偏插件(二)

    前段时间写的mapboxgl 互联网地图纠偏插件(一)存在地图旋转时瓦片错位的问题. 这次没有再跟 mapboxgl 的变换矩阵较劲,而是另辟蹊径使用 mapboxgl 的自定义图层,重新写了一套加载 ...

  8. Pygame 入门基本指南

    最近正在利用 Python 制作一个小游戏,但对于 Pygame 不熟悉,故在学习的过程记录相关知识点 Pygame 中文文档下载:Here Pygame第1-1课:入门 什么是Pygame? Pyg ...

  9. hdu 3306 Another kind of Fibonacci 矩阵快速幂

    参考了某大佬的 我们可以根据(s[n-2], a[n-1]^2, a[n-1]*a[n-2], a[n-2]^2) * A = (s[n-1], a[n]^2, a[n]*a[n-1], a[n-1] ...

  10. external-attacher源码分析(2)-核心处理逻辑分析

    kubernetes ceph-csi分析目录导航 基于tag v2.1.1 https://github.com/kubernetes-csi/external-attacher/releases/ ...