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

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. Pipeline模式与Factory+Provider模式的应用

    前言 我正在写FastGithub这个小麻雀项目,里面主要涉及了Pipeline模式和Factory+Provider模式,这两种设计模式,让这个项目在"ip扫描"和"i ...

  2. 深入理解Java中的反射机制和使用原理!详细解析invoke方法的执行和使用

    反射的概念 反射: Refelection,反射是Java的特征之一,允许运行中的Java程序获取自身信息,并可以操作类或者对象的内部属性 通过反射,可以在运行时获得程序或者程序中的每一个类型的成员活 ...

  3. Vue3全局APi解析-源码学习

    本文章共5314字,预计阅读时间5-15分钟. 前言 不知不觉Vue-next的版本已经来到了3.1.2,最近对照着源码学习Vue3的全局Api,边学习边整理了下来,希望可以和大家一起进步. 我们以官 ...

  4. 使用远程Docker进行集成测试

    目录 需求背景 使用docker进行环境搭建 以中心化的docker server改进集成测试 Docker Server远程链接配置 Testcontainers 框架 Testcontainers ...

  5. rsync 基本使用

    基本参数 # rsync -P test.tar.gz ./ test.tar.gz 395,706,368 48% 377.34MB/s 0:00:01 Or # rsync -avPh test. ...

  6. 17 DOS攻击防范

    #!/bin/bash source /etc/profile HOST_IP="172.16.1.122" DROP_IP_FILE="/tmp/drop_ip.log ...

  7. [心得体会]mysql复习

    1. 进入企业需要注意的事情 (1) 查看测试服和本地的mysql版本是否一致(2) 确认sql_mode是否和线上版本一致 show VARIABLES LIKE 'sql_mode'; (3) m ...

  8. 6-x1 read命令:从键盘读取数据

    1.read的用法 read从 STDIN 读取一行数据并将其赋给一个变量,如果没有进行重定向,默认就是从键盘读取用户输入的数据:如果进行了重定向,那么可以从文件中读取数据. read 命令的用法为: ...

  9. XCTF-boomshakalaka-3

    这题讲道理出的挺脑洞的,apk直接拖入jeb打开,找到主活动 这里有两个方法,一个是onCreate()方法,还有一个是onCreateView()方法 onCreate()方法调用了a这个对象的d方 ...

  10. vim程序编辑器---常用操作整理

    vim程序编辑器---常用操作整理 移动光标方法 o 在光标行的下一行,进入编辑模式 $ 移动到光标这行,最末尾的地方 G(大写) 移动到文件最末行 :set  nu 文件显示行数 :set  non ...