学习线程池源码--ThreadPoolExecutor
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)
- 如果工作线程未达到核心线程数,直接创建一个工作线程来执行任务,创建失败则继续执行下一步。
- 如果线程池正在运行中并且将任务加入工作队列成功,则进行安全检查,虽然加入工作队列成功,但是保不齐其他调用者或者线程此时会修改线程池状态,那么此时我们就需要将任务再进行必要的移除,这是考虑复杂情况的一种安全机制的保障。
- 如果加入队列失败,工作线程未达到最大线程数,则创建一个新的线程来执行任务,工作线程已达到最大线程数则执行拒绝策略
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的更多相关文章
- 学习线程池源码--ScheduledThreadPoolExecutor
1. 创建ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,实现了Schedule ...
- java多线程——线程池源码分析(一)
本文首发于cdream的个人博客,点击获得更好的阅读体验! 欢迎转载,转载请注明出处. 通常应用多线程技术时,我们并不会直接创建一个线程,因为系统启动一个新线程的成本是比较高的,涉及与操作系统的交互, ...
- java多线程----线程池源码分析
http://www.cnblogs.com/skywang12345/p/3509954.html 线程池示例 在分析线程池之前,先看一个简单的线程池示例. 1 import java.util.c ...
- 线程池之ThreadPoolExecutor线程池源码分析笔记
1.线程池的作用 一方面当执行大量异步任务时候线程池能够提供较好的性能,在不使用线程池的时候,每当需要执行异步任务时候是直接 new 一线程进行运行,而线程的创建和销毁是需要开销的.使用线程池时候,线 ...
- Java线程池源码及原理
目录 1 说明 1.1类继承图 2 线程池的状态 3 源码分析 3.1完整的线程池构造方法 3.2 ctl 3.3 任务的执行 3.3.1 execute(Runnable command) 3.3. ...
- 手撕ThreadPoolExecutor线程池源码
这篇文章对ThreadPoolExecutor创建的线程池如何操作线程的生命周期通过源码的方式进行详细解析.通过对execute方法.addWorker方法.Worker类.runWorker方法.g ...
- Java多线程学习之线程池源码详解
0.使用线程池的必要性 在生产环境中,如果为每个任务分配一个线程,会造成许多问题: 线程生命周期的开销非常高.线程的创建和销毁都要付出代价.比如,线程的创建需要时间,延迟处理请求.如果请求的到达率非常 ...
- 并发系列(一)——线程池源码(ThreadPoolExecutor类)简析
前言 本文主要是结合源码去线程池执行任务的过程,基于JDK 11,整个过程基本与JDK 8相同. 个人水平有限,文中若有表达有误的,欢迎大伙留言指出,谢谢了! 一.线程池简介 1.1 使用线程池的优点 ...
- Java并发编程中线程池源码分析及使用
当Java处理高并发的时候,线程数量特别的多的时候,而且每个线程都是执行很短的时间就结束了,频繁创建线程和销毁线程需要占用很多系统的资源和时间,会降低系统的工作效率. 参考http://www.cnb ...
随机推荐
- 阿里重磅开源首款自研科学计算引擎Mars,揭秘超大规模科学计算
日前,阿里巴巴正式对外发布了分布式科学计算引擎 Mars 的开源代码地址,开发者们可以在pypi上自主下载安装,或在Github上获取源代码并参与开发. 此前,早在2018年9月的杭州云栖大会上,阿里 ...
- 高斯消元+期望dp——light1151
高斯消元弄了半天没弄对.. #include<bits/stdc++.h> using namespace std; #define maxn 205 #define eps 1e-8 d ...
- html 引入公共的头部和底部
- IDEA本地SBT项目上传到SVN
需求 将本地创建的一个项目上到SVN 网上很多从SVN下载到idea,提交.更新.删除等操作. 但是少有从本地上传一个项目到svn管理的案例 本文参考https://blog.csdn.net/cao ...
- tab切换 -- vue
效果: html: // 按钮<div class="orderTab clearfix" @click="toggle()"> <div c ...
- Python学习day05 - Python基础(3) 格式化输出和基本运算符
figure:last-child { margin-bottom: 0.5rem; } #write ol, #write ul { position: relative; } img { max- ...
- svn插件的所有链接
http://subclipse.tigris.org/servlets/ProjectDocumentList?folderID=2240
- 对话框处理Enter,Esc键相应问题
在类视图里面选择你要实现的类,右键属性,在属性里面找到函数PreTranslateMessage,然后添加PreranslateMessage的消息函数,在PreTranslateMessage的消息 ...
- BZOJ4383/LuoGuP3588 Pustynia/PUS 线段树建图优化
我会告诉你我看了很久很久才把题目看懂吗???怀疑智商了 原来他给的l,r还有k个数字都是下标... 比如给了一个样例 l, r, k, x1,x2,x3...xk,代表的是一个数组num[l]~num ...
- mysql导出某张表的部分数据
.使用into outfile '保存到操作系统的外部文件路径' mysql -uroot -p123456 -hhostname -P3306 select column_name_list fro ...