还不懂Java线程池实现原理,看这一篇文章就够了
moboyou 2025-06-05 16:50 40 浏览
线程池无论是工作还是面试都是必备的技能,但是很多人对于线程池的实现原理却一知半解,并不了解线程池内部的工作原理,今天一灯就带大家一块剖析线程池底层实现原理。
1. 为什么要使用线程池
使用线程池通常由以下两个原因:
- 频繁创建销毁线程需要消耗系统资源,使用线程池可以复用线程。
- 使用线程池可以更容易管理线程,线程池可以动态管理线程个数、具有阻塞队列、定时周期执行任务、环境隔离等。
2. 线程池的使用
/**
* @author 一灯架构
* @apiNote 线程池示例
**/
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1. 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3,
3,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 2. 往线程池中提交3个任务
for (int i = 0; i < 3; i++) {
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 关注公众号:一灯架构");
});
}
// 3. 关闭线程池
threadPoolExecutor.shutdown();
}
}输出结果:
pool-1-thread-2 关注公众号:一灯架构
pool-1-thread-1 关注公众号:一灯架构
pool-1-thread-3 关注公众号:一灯架构线程池的使用非常简单:
- 调用new ThreadPoolExecutor()构造方法,指定核心参数,创建线程池。
- 调用execute()方法提交Runnable任务
- 使用结束后,调用shutdown()方法,关闭线程池。
再看一下线程池构造方法中核心参数的作用。
3. 线程池核心参数
线程池共有七大核心参数:
参数名称 | 参数含义 |
int corePoolSize | 核心线程数 |
int maximumPoolSize | 最大线程数 |
long keepAliveTime | 线程存活时间 |
TimeUnit unit | 时间单位 |
BlockingQueue<Runnable> workQueue | 阻塞队列 |
ThreadFactory threadFactory | 线程创建工厂 |
RejectedExecutionHandler handler | 拒绝策略 |
- corePoolSize 核心线程数
当往线程池中提交任务,会创建线程去处理任务,直到线程数达到corePoolSize,才会往阻塞队列中添加任务。默认情况下,空闲的核心线程并不会被回收,除非配置了allowCoreThreadTimeOut=true。
- maximumPoolSize 最大线程数
当线程池中的线程数达到corePoolSize,阻塞队列又满了之后,才会继续创建线程,直到达到maximumPoolSize,另外空闲的非核心线程会被回收。
- keepAliveTime 线程存活时间
非核心线程的空闲时间达到了keepAliveTime,将会被回收。
- TimeUnit 时间单位
线程存活时间的单位,默认是TimeUnit.MILLISECONDS(毫秒),可选择的有:
TimeUnit.NANOSECONDS(纳秒)
TimeUnit.MICROSECONDS(微秒)
TimeUnit.MILLISECONDS(毫秒)
TimeUnit.SECONDS(秒)
TimeUnit.MINUTES(分钟)
TimeUnit.HOURS(小时)
TimeUnit.DAYS(天)
- workQueue 阻塞队列
当线程池中的线程数达到corePoolSize,再提交的任务就会放到阻塞队列的等待,默认使用的是LinkedBlockingQueue,可选择的有:
LinkedBlockingQueue(基于链表实现的阻塞队列)
ArrayBlockingQueue(基于数组实现的阻塞队列)
SynchronousQueue(只有一个元素的阻塞队列)
PriorityBlockingQueue(实现了优先级的阻塞队列)
DelayQueue(实现了延迟功能的阻塞队列)
- threadFactory 线程创建工厂
用来创建线程的工厂,默认的是
Executors.defaultThreadFactory(),可选择的还有
Executors.privilegedThreadFactory()实现了线程优先级。当然也可以自定义线程创建工厂,创建线程的时候最好指定线程名称,便于排查问题。
- RejectedExecutionHandler 拒绝策略
当线程池中的线程数达到maximumPoolSize,阻塞队列也满了之后,再往线程池中提交任务,就会触发执行拒绝策略,默认的是AbortPolicy(直接终止,抛出异常),可选择的有:
AbortPolicy(直接终止,抛出异常)
DiscardPolicy(默默丢弃,不抛出异常)
DiscardOldestPolicy(丢弃队列中最旧的任务,执行当前任务)
CallerRunsPolicy(返回给调用者执行)
4. 线程池工作原理
线程池的工作原理,简单理解如下:
- 当往线程池中提交任务的时候,会先判断线程池中线程数是否是核心线程数,如果小于,会创建核心线程并执行任务。
- 如果线程数大于核心线程数,会判断阻塞队列是否已满,如果没有满,会把任务添加到阻塞队列中等待调度执行。
- 如果阻塞队列已满,会判断线程数是否小于最大线程数,如果小于,会继续创建最大线程数并执行任务。
- 如果线程数大于最大线程数,会执行拒绝策略,然后结束。
5. 线程池源码剖析
5.1 线程池的属性
public class ThreadPoolExecutor extends AbstractExecutorService {
// 线程池的控制状态,Integer长度是32位,前3位用来存储线程池状态,后29位用来存储线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 线程个数所占的位数
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池的最大容量,2^29-1,约5亿个线程
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 独占锁,用来控制多线程下的并发操作
private final ReentrantLock mainLock = new ReentrantLock();
// 工作线程的集合
private final HashSet<Worker> workers = new HashSet<>();
// 等待条件,用来响应中断
private final Condition termination = mainLock.newCondition();
// 是否允许回收核心线程
private volatile boolean allowCoreThreadTimeOut;
// 线程数的历史峰值
private int largestPoolSize;
/**
* 以下是线程池的七大核心参数
*/
private volatile int corePoolSize;
private volatile int maximumPoolSize;
private volatile long keepAliveTime;
private final BlockingQueue<Runnable> workQueue;
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
}线程池的控制状态ctl用来存储线程池状态和线程个数,前3位用来存储线程池状态,后29位用来存储线程数量。
设计者多聪明,用一个变量存储了两块内容。
5.2 线程池状态
线程池共有5种状态:
状态名称 | 状态值 | 状态含义 | 状态作用 |
RUNNING | 111 | 运行中 | 线程池创建后默认状态,接收新任务,并处理阻塞队列中的任务。 |
SHUTDOWN | 000 | 已关闭 | 调用shutdown方法后处于该状态,不再接收新任务,处理阻塞队列中任务。 |
STOP | 001 | 已停止 | 调用shutdownNow方法后处于该状态,不再新任务,并中断所有线程,丢弃阻塞队列中所有任务。 |
TIDYING | 010 | 处理中 | 所有任务已完成,所有工作线程都已回收,等待调用terminated方法。 |
TERMINATED | 011 | 已终止 | 调用terminated方法后处于该状态,线程池的最终状态。 |
5.3 execute源码
看一下往线程池中提交任务的源码,这是线程池的核心逻辑:
// 往线程池中提交任务
public void execute(Runnable command) {
// 1. 判断提交的任务是否为null
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 2. 判断线程数是否小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// 3. 把任务包装成worker,添加到worker集合中
if (addWorker(command, true))
return;
c = ctl.get();
}
// 4. 判断如果线程数不小于corePoolSize,并且可以添加到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
// 5. 重新检查线程池状态,如果线程池不是运行状态,就移除刚才添加的任务,并执行拒绝策略
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
// 6. 判断如果线程数是0,就创建非核心线程(任务是null,会从阻塞队列中拉取任务)
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 7. 如果添加阻塞队列失败,就创建一个Worker
else if (!addWorker(command, false))
// 8. 如果创建Worker失败说明已经达到最大线程数了,则执行拒绝策略
reject(command);
}execute方法的逻辑也很简单,最终就是调用addWorker方法,把任务添加到worker集合中,再看一下addWorker方法的源码:
// 添加worker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (; ; ) {
int c = ctl.get();
int rs = runStateOf(c);
// 1. 检查是否允许提交任务
if (rs >= SHUTDOWN &&
!(rs == SHUTDOWN &&
firstTask == null &&
!workQueue.isEmpty()))
return false;
// 2. 使用死循环保证添加线程成功
for (; ; ) {
int wc = workerCountOf(c);
// 3. 校验线程数是否超过容量限制
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 4. 使用CAS修改线程数
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
// 5. 如果线程池状态变了,则从头再来
if (runStateOf(c) != rs)
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 6. 把任务和新线程包装成一个worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 7. 加锁,控制并发
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 8. 再次校验线程池状态是否异常
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 9. 如果线程已经启动,就抛出异常
if (t.isAlive())
throw new IllegalThreadStateException();
// 10. 添加到worker集合中
workers.add(w);
int s = workers.size();
// 11. 记录线程数历史峰值
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 12. 启动线程
t.start();
workerStarted = true;
}
}
} finally {
if (!workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}方法虽然很长,但是逻辑很清晰。就是把任务和线程包装成worker,添加到worker集合,并启动线程。
5.4 worker源码
再看一下worker类的结构:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable {
// 工作线程
final Thread thread;
// 任务
Runnable firstTask;
// 创建worker,并创建一个新线程(用来执行任务)
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
}5.5 runWorker源码
再看一下run方法的源码:
// 线程执行入口
public void run() {
runWorker(this);
}
// 线程运行核心方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
// 1. 如果当前worker中任务是null,就从阻塞队列中获取任务
while (task != null || (task = getTask()) != null) {
// 加锁,保证thread不被其他线程中断(除非线程池被中断)
w.lock();
// 2. 校验线程池状态,是否需要中断当前线程
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 3. 执行run方法
task.run();
} catch (RuntimeException x) {
thrown = x;
throw x;
} catch (Error x) {
thrown = x;
throw x;
} catch (Throwable x) {
thrown = x;
throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
// 解锁
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 4. 从worker集合删除当前worker
processWorkerExit(w, completedAbruptly);
}
}runWorker方法逻辑也很简单,就是不断从阻塞队列中拉取任务并执行。
再看一下从阻塞队列中拉取任务的逻辑:
// 从阻塞队列中拉取任务
private Runnable getTask() {
boolean timedOut = false;
for (; ; ) {
int c = ctl.get();
int rs = runStateOf(c);
// 1. 如果线程池已经停了,或者阻塞队列是空,就回收当前线程
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 2. 再次判断是否需要回收线程
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 3. 从阻塞队列中拉取任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}6. 总结
今天带大家一块详细剖析了Java线程池的实现原理,是不是非常简单?
几百行的方法虽然看着复杂,令人头疼,只要由浅入深的梳理清理业务逻辑,源码读起来也是小菜一碟。
我是「一灯架构」,如果本文对你有帮助,欢迎各位小伙伴点赞、评论和关注,感谢各位老铁,我们下期见
- 上一篇:探讨C语言系统编程中线程的原理以及实现
- 下一篇:一分钟快速部署Django应用
相关推荐
- Excel技巧:SHEETSNA函数一键提取所有工作表名称批量生产目录
-
首先介绍一下此函数:SHEETSNAME函数用于获取工作表的名称,有三个可选参数。语法:=SHEETSNAME([参照区域],[结果方向],[工作表范围])(参照区域,可选。给出参照,只返回参照单元格...
- Excel HOUR函数:“小时”提取器_excel+hour函数提取器怎么用
-
一、函数概述HOUR函数是Excel中用于提取时间值小时部分的日期时间函数,返回0(12:00AM)到23(11:00PM)之间的整数。该函数在时间数据分析、考勤统计、日程安排等场景中应用广泛。语...
- Filter+Search信息管理不再难|多条件|模糊查找|Excel函数应用
-
原创版权所有介绍一个信息管理系统,要求可以实现:多条件、模糊查找,手动输入的内容能去空格。先看效果,如下图动画演示这样的一个效果要怎样实现呢?本文所用函数有Filter和Search。先用filter...
- FILTER函数介绍及经典用法12:FILTER+切片器的应用
-
EXCEL函数技巧:FILTER经典用法12。FILTER+切片器制作筛选按钮。FILTER的函数的经典用法12是用FILTER的函数和切片器制作一个筛选按钮。像左边的原始数据,右边想要制作一...
- office办公应用网站推荐_office办公软件大全
-
以下是针对Office办公应用(Word/Excel/PPT等)的免费学习网站推荐,涵盖官方教程、综合平台及垂直领域资源,适合不同学习需求:一、官方权威资源1.微软Office官方培训...
- WPS/Excel职场办公最常用的60个函数大全(含卡片),效率翻倍!
-
办公最常用的60个函数大全:从入门到精通,效率翻倍!在职场中,WPS/Excel几乎是每个人都离不开的工具,而函数则是其灵魂。掌握常用的函数,不仅能大幅提升工作效率,还能让你在数据处理、报表分析、自动...
- 收藏|查找神器Xlookup全集|一篇就够|Excel函数|图解教程
-
原创版权所有全程图解,方便阅读,内容比较多,请先收藏!Xlookup是Vlookup的升级函数,解决了Vlookup的所有缺点,可以完全取代Vlookup,学完本文后你将可以应对所有的查找难题,内容...
- 批量查询快递总耗时?用Excel这个公式,自动计算揽收到签收天数
-
批量查询快递总耗时?用Excel这个公式,自动计算揽收到签收天数在电商运营、物流对账等工作中,经常需要统计快递“揽收到签收”的耗时——比如判断某快递公司是否符合“3天内送达”的服务承...
- Excel函数公式教程(490个实例详解)
-
Excel函数公式教程(490个实例详解)管理层的财务人员为什么那么厉害?就是因为他们精通excel技能!财务人员在日常工作中,经常会用到Excel财务函数公式,比如财务报表分析、工资核算、库存管理等...
- Excel(WPS表格)Tocol函数应用技巧案例解读,建议收藏备用!
-
工作中,经常需要从多个单元格区域中提取唯一值,如体育赛事报名信息中提取唯一的参赛者信息等,此时如果复制粘贴然后去重,效率就会很低。如果能合理利用Tocol函数,将会极大地提高工作效率。一、功能及语法结...
- Excel中的SCAN函数公式,把计算过程理清,你就会了
-
Excel新版本里面,除了出现非常好用的xlookup,Filter公式之外,还更新一批自定义函数,可以像写代码一样写公式其中SCAN函数公式,也非常强大,它是一个循环函数,今天来了解这个函数公式的计...
- Excel(WPS表格)中多列去重就用Tocol+Unique组合函数,简单高效
-
在数据的分析和处理中,“去重”一直是绕不开的话题,如果单列去重,可以使用Unique函数完成,如果多列去重,如下图:从数据信息中可以看到,每位参赛者参加了多项运动,如果想知道去重后的参赛者有多少人,该...
- Excel(WPS表格)函数Groupby,聚合统计,快速提高效率!
-
在前期的内容中,我们讲了很多的统计函数,如Sum系列、Average系列、Count系列、Rank系列等等……但如果用一个函数实现类似数据透视表的功能,就必须用Groupby函数,按指定字段进行聚合汇...
- Excel新版本,IFS函数公式,太强大了!
-
我们举一个工作实例,现在需要计算业务员的奖励数据,右边是公司的奖励标准:在新版本的函数公式出来之前,我们需要使用IF函数公式来解决1、IF函数公式IF函数公式由三个参数组成,IF(判断条件,对的时候返...
- Excel不用函数公式数据透视表,1秒完成多列项目汇总统计
-
如何将这里的多组数据进行汇总统计?每组数据当中一列是不同菜品,另一列就是该菜品的销售数量。如何进行汇总统计得到所有的菜品销售数量的求和、技术、平均、最大、最小值等数据?不用函数公式和数据透视表,一秒就...
- 一周热门
- 最近发表
-
- Excel技巧:SHEETSNA函数一键提取所有工作表名称批量生产目录
- Excel HOUR函数:“小时”提取器_excel+hour函数提取器怎么用
- Filter+Search信息管理不再难|多条件|模糊查找|Excel函数应用
- FILTER函数介绍及经典用法12:FILTER+切片器的应用
- office办公应用网站推荐_office办公软件大全
- WPS/Excel职场办公最常用的60个函数大全(含卡片),效率翻倍!
- 收藏|查找神器Xlookup全集|一篇就够|Excel函数|图解教程
- 批量查询快递总耗时?用Excel这个公式,自动计算揽收到签收天数
- Excel函数公式教程(490个实例详解)
- Excel(WPS表格)Tocol函数应用技巧案例解读,建议收藏备用!
- 标签列表
-
- 外键约束 oracle (36)
- oracle的row number (32)
- 唯一索引 oracle (34)
- oracle in 表变量 (28)
- oracle导出dmp导出 (28)
- 多线程的创建方式 (29)
- 多线程 python (30)
- java多线程并发处理 (32)
- 宏程序代码一览表 (35)
- c++需要学多久 (25)
- css class选择器用法 (25)
- css样式引入 (30)
- css教程文字移动 (33)
- php简单源码 (36)
- php个人中心源码 (25)
- php小说爬取源码 (23)
- 云电脑app源码 (22)
- html画折线图 (24)
- docker好玩的应用 (28)
- linux有没有pe工具 (34)
- 可以上传视频的网站源码 (25)
- 随机函数如何生成小数点数字 (31)
- 随机函数excel公式总和不变30个数据随机 (33)
- 所有excel函数公式大全讲解 (22)
- 有动图演示excel函数公式大全讲解 (32)
