1 创建ThreadPoolExecutor

ThreadPollExecutor有四个构造函数,但本质上都是调用这一个构造函数。

    public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}

corePoolSize: 线程池核心线程数量

maximumPoolSize:线程池最大线程数量

keepAliveTime:线程空闲时间

unit:空闲时间单位

workQueue:工作队列,没有空闲线程时新加的任务会放入工作队列中排队等待。工作队列共有四种实现:

a.ArrayBlockingQueue: 创建固定大小的阻塞队列, 采用的是数组的结构方式
b.LinkedBlockingQueue: 创建固定大小的阻塞队列,如果为传入参数,则会创建Integer.MaxValue大小的队列
c.SynchronousQueue: 创建一个不存储元素的阻塞队列,每一个元素的插入都必须等待一个元素的移除操作,不然会一直阻塞。
d.PriorityBlockingQueue: 一个具有优先级的无限阻塞队列。

threadFactory:线程工厂,用于创建线程池中的线程,可以自己实现,默认工厂创建的线程名称为-poolNumber-thread-threadNumber,如:pool-1-thread-10

handler: 拒绝策略,当线程池线程达到最大后添加任务不能被执行时的处理策略。拒绝策略有4种实现,当然也可以自己实现(继承RejectExecutionHandle)。

a. AbortPolicy:默认拒绝策略,丢弃这个任务并抛出RejectedExecutionException异常
b. DiscardPolicy:直接丢弃,不做任何处理
c. DiscardOldestPolicy:将工作队列头部元素丢弃(最老的),然后重新提交任务
d. CallerRunsPolicy:主线程直接去执行这个任务,不用等待线程池

Executors工具类提供了几种ThreadpoolExecutor的创建方法:

1. FixedThreadPool

       FixedThreadPool的corePoolSize和maximumPoolSize都被设置为同一个参数nThreads

       keepAliveTime被设为0L意味着多余的空闲线程会被立即终止

       FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列容量为Integer.MAX_VALUE)。这意味着当线程池中的线程数达到corePoolSize时,新任务会被放入工作队列,因此线程池中的线程不会超过核心线程数。所以此时maximumPoolSize和keepAliveTime都是无效参数,并且只要线程池在运行中便不会拒绝任务。

    public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2. SingleThreadExecutor

       SingleThreadExecutor是一个使用单个worker线程的Executor,它的核心线程数和最大线程数都被设为1,相当于单线程的FixedThreadPool。

    public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
3. CachedThreadPool

       CachedThreadPool是一个会根据需要创建新线程的线程池,它的corePoolSize为0,意味着核心线程为空。

       keepAliveTime被设为60L意味着多余的空闲线程会在空闲60s后被终止。

       使用没有容量的SynchronousQueue作为工作队列,每次offer操作必须等待另一个take操作,反之亦然,SynchronousQueue就像一个门框(门框上不可以站人)。

       最大线程数为Integer.MAX_VALUE,意味着当主线程提交任务速度高于线程池中的线程处理速度时,会无限制的创建新线程。极端情况下,会因为创建过多的线程耗尽CPU和内存资源。

    public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

2 线程池工作原理

1. 当一个任务被提交时,线程池会判断核心线程数量是否达到最大,如果没有则会通过线程工厂创建一个新的线程来执行任务;如果达到最大则执行步骤2.
2. 尝试将任务放入工作队列,如果成功加入工作队列,当有工作线程空闲时会去工作队列中获取一个任务来执行;如果加入失败则执行步骤3.
3. 判断线程数量是否已达到最大线程数量,如果没有则会通过线程工厂创建一个新的线程来执行任务;如果已达到最大线程数,则执行拒绝策略

3 源码分析

3.1 核心静态变量及方法

    //高3位表示运行的状态,低29未表示线程池中运行的线程数量。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//Integer的大小为32,Integer.SIZE - 3 = 29
private static final int COUNT_BITS = Integer.SIZE - 3;
//1左移29位后减1,高3位全为0,低29位全为1
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits
//高3位:111 【-1的二进制为32位全为1(取反加1),左移29位后高3位为111】
private static final int RUNNING = -1 << COUNT_BITS;
//高3位:000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//高3位: 001
private static final int STOP = 1 << COUNT_BITS;
//高3位:010
private static final int TIDYING = 2 << COUNT_BITS;
//高3位:011
private static final int TERMINATED = 3 << COUNT_BITS; // Packing and unpacking ctl
// CAPACITY取反后高3位全为1,低29位全为0,与运算后c的低29位全为0,保留高3位获取runState
private static int runStateOf(int c) { return c & ~CAPACITY; }
// CAPACITY高3位全为0,低29位全为1,与运算后c的高3位全为0,保留低29位获取线程数量
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

3.2 入口方法(execute)

  1. 如果工作线程未达到核心线程数,直接创建一个工作线程来执行任务,创建失败则继续执行下一步。
  2. 如果线程池正在运行中并且将任务加入工作队列成功,则进行安全检查,虽然加入工作队列成功,但是保不齐其他调用者或者线程此时会修改线程池状态,那么此时我们就需要将任务再进行必要的移除,这是考虑复杂情况的一种安全机制的保障。
  3. 如果加入队列失败,工作线程未达到最大线程数,则创建一个新的线程来执行任务,工作线程已达到最大线程数则执行拒绝策略
    public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//高3位表示运行的状态,低29未表示线程池中运行的线程数量。
int c = ctl.get();
//根据低29位获取线程数
if (workerCountOf(c) < corePoolSize) {
//如果工作线程数量小于核心线程数,尝试创建新的worker线程来执行任务
if (addWorker(command, true))
return;
//如果失败可能是在addWorker时ctl发生改变(核心线程数达到最大),所以重新获取值
c = ctl.get();
}
//如果线程池正在运行中并且将任务加入工作队列成功
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//如果此刻线程池已不在运行并且任务从队列移除成功,执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
//如果此刻线程池还在运行,但是工作线程数量为0,则增加一个空的工作线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果加入队列失败,工作线程未达到最大线程数,则创建一个新的线程来执行任务
else if (!addWorker(command, false))
//工作线程已达到最大线程数,执行拒绝策略
reject(command);
}

3.3 增加工作线程(addWorker)

       首先通过CAS操作增加工作线程数量,增加成功后实例化一个工作线程用来执行任务,然后将工作线程加入works,因为works实际上是一个非线程安全的容器(HashSet),所以通过可重入锁(ReentrantLock)来保证了线程安全问题,防止多个任务同时提交导致works计算不准确,如果添加成功则将该工作线程启动。

    private boolean addWorker(Runnable firstTask, boolean core) {
//continue retry; 可以使retry:后面的代码块重新执行
//break retry; 可以使retry:后面的代码块终止执行,不管有多少层循环嵌套
retry:
for (;;) {
//高3位表示运行的状态,低29未表示线程池中运行的线程数量。
int c = ctl.get();
//获取线程池的运行状态
int rs = runStateOf(c); // Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false; for (;;) {
//获取工作线程数量
int wc = workerCountOf(c);
//如果工作线程数超过限制,则返回添加失败
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//通过CAS机制增加工作线程数,成功则直接跳出循环
if (compareAndIncrementWorkerCount(c))
break retry;
//如果CAS操作失败,则说明其他调用者或者线程修改了线程池数据,需要重新读取
c = ctl.get(); // Re-read ctl
//如果当前线程池状态已经发生了改变,则从最外层循环开始重新执行
if (runStateOf(c) != rs)
continue retry;
// 否则继续当前循环
}
}
//工作线程启动状态
boolean workerStarted = false;
//工作线程添加状态
boolean workerAdded = false;
//工作线程引用
Worker w = null;
try {
//实例化一个工作线程用于执行任务
w = new Worker(firstTask);
//取工作线程中的线程
final Thread t = w.thread;
if (t != null) {
//获取可重入锁
final ReentrantLock mainLock = this.mainLock;
//加锁,防止多个线程同时提交任务导致works计算不准确,因为works实际上使用HashSet存储的,非线程安全的
mainLock.lock();
try {
// 获取线程池运行状态
int rs = runStateOf(ctl.get()); //如果线程池处于运行状态
if (rs < SHUTDOWN ||
//或者终止状态并且任务为空
(rs == SHUTDOWN && firstTask == null)) {
//如果该线程已运行,则抛出IllegalThreadStateException异常
if (t.isAlive())
throw new IllegalThreadStateException();
//将新建的工作线程加入works
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
//调整工作线程添加状态为true
workerAdded = true;
}
} finally {
//释放锁
mainLock.unlock();
}
//如果工作线程已添加,则启动该线程,并将工作线程启动状态改为true
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//如果工作线程未启动,执行添加失败逻辑
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}

学习线程池源码--ThreadPoolExecutor的更多相关文章

  1. 学习线程池源码--ScheduledThreadPoolExecutor

    1. 创建ScheduledThreadPoolExecutor        ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,实现了Schedule ...

  2. java多线程——线程池源码分析(一)

    本文首发于cdream的个人博客,点击获得更好的阅读体验! 欢迎转载,转载请注明出处. 通常应用多线程技术时,我们并不会直接创建一个线程,因为系统启动一个新线程的成本是比较高的,涉及与操作系统的交互, ...

  3. java多线程----线程池源码分析

    http://www.cnblogs.com/skywang12345/p/3509954.html 线程池示例 在分析线程池之前,先看一个简单的线程池示例. 1 import java.util.c ...

  4. 线程池之ThreadPoolExecutor线程池源码分析笔记

    1.线程池的作用 一方面当执行大量异步任务时候线程池能够提供较好的性能,在不使用线程池的时候,每当需要执行异步任务时候是直接 new 一线程进行运行,而线程的创建和销毁是需要开销的.使用线程池时候,线 ...

  5. Java线程池源码及原理

    目录 1 说明 1.1类继承图 2 线程池的状态 3 源码分析 3.1完整的线程池构造方法 3.2 ctl 3.3 任务的执行 3.3.1 execute(Runnable command) 3.3. ...

  6. 手撕ThreadPoolExecutor线程池源码

    这篇文章对ThreadPoolExecutor创建的线程池如何操作线程的生命周期通过源码的方式进行详细解析.通过对execute方法.addWorker方法.Worker类.runWorker方法.g ...

  7. Java多线程学习之线程池源码详解

    0.使用线程池的必要性 在生产环境中,如果为每个任务分配一个线程,会造成许多问题: 线程生命周期的开销非常高.线程的创建和销毁都要付出代价.比如,线程的创建需要时间,延迟处理请求.如果请求的到达率非常 ...

  8. 并发系列(一)——线程池源码(ThreadPoolExecutor类)简析

    前言 本文主要是结合源码去线程池执行任务的过程,基于JDK 11,整个过程基本与JDK 8相同. 个人水平有限,文中若有表达有误的,欢迎大伙留言指出,谢谢了! 一.线程池简介 1.1 使用线程池的优点 ...

  9. Java并发编程中线程池源码分析及使用

    当Java处理高并发的时候,线程数量特别的多的时候,而且每个线程都是执行很短的时间就结束了,频繁创建线程和销毁线程需要占用很多系统的资源和时间,会降低系统的工作效率. 参考http://www.cnb ...

随机推荐

  1. 为什么Java中的String是设计成不可变的?(Why String is immutable in java)

    There are many reasons due to the string class has been made immutable in Java. These reasons in vie ...

  2. System.Web.Mvc.JsonResult.cs

    ylbtech-System.Web.Mvc.JsonResult.cs 1.程序集 System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicK ...

  3. @Value取值为NULL的解决方案

    在spring mvc架构中,如果希望在程序中直接使用properties中定义的配置值,通常使用一下方式来获取: @Value("${tag}") private String ...

  4. selenium简单应用

    文章引用自:https://wenku.baidu.com/view/d5c296c75727a5e9846a6182.html 例子:

  5. css 超出两行省略号,超出一行省略号

    参考:https://www.cnblogs.com/yangguojin/p/10301981.html 超出一行省略: p{ white-space:nowrap; overflow:hidden ...

  6. Cat- Linux必学的60个命令

    1.作用 cat(“concatenate”的缩写)命令用于连接并显示指定的一个和多个文件的有关信息,它的使用权限是所有用户. 2.格式 cat [options] 文件1 文件2…… 3.[opti ...

  7. linux-c getopt()参数处理函数

    转自:https://www.cnblogs.com/qingergege/p/5914218.html 最近在弄Linux C编程,本科的时候没好好学啊,希望学弟学妹们引以为鉴. 好了,虽然啰嗦了点 ...

  8. pptp,l2tp获取登录用户信息用pppd参数即可

    这个问题困扰了我很久,终于在pppd的man文档里,发现了踪迹.在man中的SCRIPTS下有一系列的参数,其中PEERNAME就是登陆的用户名,并且在/etc/ppp/ip-up和/etc/ppp/ ...

  9. Mahout In Action-第一章:初识Mahout

    1. 初识Mahout 本章涵盖以下内容: Apache Mahout是什么? 现实中推荐系统引擎.聚类.分类概述 配置mahout 读者可能从本书的标题中猜测到,本书是一本讲解如何将mahout应用 ...

  10. [Swoole系列入门教程 6] TCP协议和粘包