news 2026/4/12 3:51:51

手写 Java 线程池:从状态转换到拒绝策略的极致实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写 Java 线程池:从状态转换到拒绝策略的极致实现

🚀 引言:为什么要手写线程池?

在日常开发中,我们习惯了Executors.newFixedThreadPool()或者直接new ThreadPoolExecutor()。但你是否思考过:

  • 为什么线程池能让线程“长生不老”而不被销毁?
  • 一个int变量是如何神奇地同时表示“线程数量”和“运行状态”的?
  • 当任务洪峰来临时,线程池内部的微秒级调度逻辑是怎样的?

今天,我们摒弃那些繁琐的参数定义,直接动刀,从零实现一个具备核心调度、任务队列、拒绝策略功能的工业级线程池。


一、 核心设计:线程池的“灵魂”状态机

线程池不是简单的线程集合,而是一个复杂的状态机。在 JDK 中,作者 Doug Lea 巧妙地使用了一个AtomicInteger(32位)来存储两个信息:

  1. 高 3 位:表示线程池运行状态(RUNNING, SHUTDOWN, STOP…)。
  2. 低 29 位:表示当前有效线程数。

1.1 状态转换逻辑流

创建并初始化

调用 shutdown()

调用 shutdownNow()

任务清空

队列 & 线程池全空

线程池全空

terminated() 执行完毕

RUNNING

SHUTDOWN

STOP

TIDYING

TERMINATED


二、 第一阶段:构建核心骨架与 Worker 模型

线程池的核心在于Worker(工作者)。它本质上是一个不断从BlockingQueue中获取任务并执行的循环体。

2.1 任务承载:Worker 内部类实现

privatefinalclassWorkerimplementsRunnable{Threadthread;RunnablefirstTask;Worker(RunnablefirstTask){this.firstTask=firstTask;this.thread=newThread(this);// 真正执行的线程}@Overridepublicvoidrun(){runWorker(this);}}

2.2 核心循环:让线程“复用”的秘密

finalvoidrunWorker(Workerw){Runnabletask=w.firstTask;w.firstTask=null;try{// 【核心代码】如果任务不为空,或者从队列中能取到任务,就一直循环while(task!=null||(task=getTask())!=null){try{task.run();// 执行真正的业务逻辑}finally{task=null;}}}finally{processWorkerExit(w);// 线程退出后的清理}}

三、 第二阶段:调度逻辑——execute方法的精妙推演

当我们调用execute(runnable)时,线程池会经历三个关键判断:

  1. 核心线程数(corePoolSize):没满?直接创线程执行。
  2. 阻塞队列(workQueue):满了?尝试放进队列。
  3. 最大线程数(maximumPoolSize):队列也满了?尝试开启非核心线程。
  4. 拒绝策略(Handler):全满了?触发拒绝。

3.1 调度流程图(Mermaid 渲染)

成功

失败

提交任务 execute

当前线程数 < corePoolSize?

addWorker: 创建核心线程

workQueue.offer: 尝试入队

等待 Worker 调度

当前线程数 < maximumPoolSize?

addWorker: 创建非核心线程

执行拒绝策略 RejectedExecutionHandler


四、 第三阶段:极致实现——自定义拒绝策略

当系统负载达到极限,如何体面地拒绝任务?我们需要提供一个接口:

publicinterfaceRejectedExecutionHandler{voidrejectedExecution(Runnabler,MyThreadPoolExecutorexecutor);}// 模拟 JDK 的 DiscardOldestPolicy(丢弃最老任务策略)publicclassDiscardOldestPolicyimplementsRejectedExecutionHandler{publicvoidrejectedExecution(Runnabler,MyThreadPoolExecutorexecutor){executor.getQueue().poll();// 弹出队首executor.execute(r);// 重新尝试提交}}

五、 隐形陷阱:手写线程池时的 3 个坑

5.1 变量可见性与原子性

在增加线程计数时,必须使用AtomicIntegercompareAndSet(CAS),否则在高并发下,线程数会超出你的限制,直接撑爆内存。

5.2 线程工厂(ThreadFactory)的重要性

不要在代码里硬编码new Thread()。手写时,一定要支持传入ThreadFactory,以便为线程命名。没有名字的线程池,排查 OOM 时就是噩梦。

5.3 锁的颗粒度

在维护HashSet<Worker>(保存存活线程的容器)时,必须加全局锁(如ReentrantLock),这决定了线程池的线程安全性。


六、 总结:从手写到架构思考

通过手写,你会发现ThreadPoolExecutor的伟大之处在于它对资源的极致克制。它利用CAS + 阻塞队列,在无锁化和稳定性之间取得了近乎完美的平衡。

架构师建议:生产环境严禁使用Executors的快捷方法,一定要手动配置参数,并根据业务是CPU 密集型还是IO 密集型来计算核心线程数。


七、 互动引导

如果你在面试中遇到这道题:

“如果线程池的队列满了,但核心线程都在忙,新来的任务会发生什么?”

你能精准地回答出 addWorker 的执行时机吗?

欢迎在评论区留下你的代码实现片段。点赞过百,我将开源这套手写的“极简版 ThreadPoolExecutor”完整工程代码(包含单元测试)!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/9 23:05:21

学生近视低龄化引关注,教育照明护眼灯成防控关键

从近些年来开始&#xff0c;我国学生所面临的近视状况愈发日益显著地获取社会瞩目。依据《国民视觉健康》调研所形成的数据&#xff0c;在年龄超过5岁的人群范围里&#xff0c;近视所占比例已经达到了35%至39%左右这个区间&#xff0c;并且呈现出年龄朝着低龄化方向变化的态势。…

作者头像 李华
网站建设 2026/4/10 19:58:42

基于BS技术的订单管理系统开题报告(1)

目录 研究背景与意义系统目标与功能技术选型创新点分析预期成果研究方法 项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作 研究背景与意义 订单管理系统是电子商务和企业资源规划的核心模块&#xff0c;基…

作者头像 李华
网站建设 2026/4/11 20:03:27

基于Java的超市积分系统和会员分类_开题报告(1)

目录 系统背景与意义系统功能模块技术选型关键实现示例预期成果 项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作 以下是基于Java的超市积分系统和会员分类的开题报告内容整理&#xff1a; 系统背景与意义…

作者头像 李华
网站建设 2026/4/3 3:17:58

基于Uniapp+ssm的社区疫情防控系统毕业论文

目录 系统概述技术架构核心功能模块创新点分析论文结构建议代码示例&#xff08;SSM部分&#xff09;应用价值 项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作 系统概述 基于UniappSSM的社区疫情防控系统…

作者头像 李华
网站建设 2026/4/10 12:47:47

Python 地理数据处理——GeoTIFF 读取与分析实战

一、从 TIF 到 GeoTIFF——当图像拥有了坐标 假设你用手机拍摄了一张风景照&#xff0c;或者在电脑上保存了一张高清的地图图片。在计算机眼里&#xff0c;这些普通的图片&#xff08;通常是 JPG、PNG 或普通的 TIF 格式&#xff09;其实只是由无数个像素排列而成的矩阵&#x…

作者头像 李华