Java线程池

Java线程知识

总体设计

Java线程池

ThreadPoolExecutor实现的顶层接口是Executor,顶层接口Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。ExecutorService接口增加了一些能力:(1)扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;(2)提供了管控线程池的方法,比如停止线程池的运行。AbstractExecutorService则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。

线程池的核心类:ThreadPoolExecutor

Java 提供的线程池主要通过 java.util.concurrent.ThreadPoolExecutor 实现。

public ThreadPoolExecutor(
int corePoolSize, // 核心线程数(常驻)
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程的存活时间
TimeUnit unit, // 上面参数的单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂(创建线程的方式)
RejectedExecutionHandler handler // 拒绝策略
)

线程池参数详解

参数说明
corePoolSize核心线程数,线程池中始终保留的线程数量。
maximumPoolSize最大线程数。线程池能创建的最大线程数(包括核心线程)。
keepAliveTime非核心线程的空闲存活时间。超过这个时间会被销毁。
unit存活时间单位,如 TimeUnit.SECONDS。
workQueue任务缓存队列(如 ArrayBlockingQueue、LinkedBlockingQueue)。
threadFactory自定义线程创建方式,可设置线程名、守护线程等。
handler拒绝策略,线程池已满时的处理方式。

任务执行流程

Java线程池
  1. 若线程数 < corePoolSize → 创建新线程执行任务
  2. 若线程数 ≥ corePoolSize → 放入队列
  3. 若队列满且线程数 < maximumPoolSize → 创建新线程执行任务
  4. 若线程数 ≥ maximumPoolSize 且队列也满 → 触发拒绝策略

常见线程池拒绝策略(RejectedExecutionHandler

策略含义
AbortPolicy默认策略,抛出 RejectedExecutionException 异常。
CallerRunsPolicy谁提交谁执行(任务回退给主线程)。
DiscardPolicy直接丢弃任务,不抛异常。
DiscardOldestPolicy丢弃队列最旧的任务,尝试执行当前任务。

常用线程池队列(BlockingQueue

队列类型特点
ArrayBlockingQueue有界,数组实现,必须设置容量,适合固定大小任务量
LinkedBlockingQueue可设容量(默认无限),链表实现,适合任务突发
SynchronousQueue不缓存任务,直接传递给线程,适合高并发、立即处理任务

如何创建线程池?

推荐:使用 ThreadPoolExecutor,避免 Executors 工厂方法(不推荐)

ExecutorService threadPool = new ThreadPoolExecutor(
2, 5,
10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);

不推荐的 Executors(会导致OOM):

Executors.newFixedThreadPool(10); // 队列无界
Executors.newCachedThreadPool(); // 线程无限
Executors.newSingleThreadExecutor(); // 只有一个线程

线程池使用示例

public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(
2, 4,
10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);

for (int i = 0; i < 10; i++) {
final int taskId = i;
pool.execute(() -> {
System.out.println("任务 " + taskId + " 执行线程: " + Thread.currentThread().getName());
});
}

pool.shutdown(); // 关闭线程池
}
}

阿里巴巴线程池规范(强烈建议背熟)

使用 ThreadPoolExecutor 自定义线程池,禁止使用 Executors 提供的默认线程池,原因是:

  • FixedThreadPoolSingleThreadPool 使用的是无界队列,可能导致 OOM;
  • CachedThreadPool 使用的是无界线程数量,也可能 OOM;
  • 推荐设置合理 队列大小最大线程数

Executors

Executors 是 Java 提供的线程池工厂工具类,位于 java.util.concurrent.Executors 包中,它提供了一些快速创建线程池、定时任务线程池、单线程执行器的方法,使用简单,但也有一些坑(如无界队列、OOM 等问题)。

Executors 常用方法总览

方法名描述推荐程度
newFixedThreadPool(int nThreads)固定线程数线程池❌ 不推荐(使用无界队列)
newCachedThreadPool()缓存线程池,线程数不限制❌ 不推荐(线程数无界)
newSingleThreadExecutor()单线程线程池❌ 不推荐(无界队列)
newScheduledThreadPool(int corePoolSize)支持定时/周期执行的线程池✅ 合理使用
newSingleThreadScheduledExecutor()单线程定时线程池✅ 可用于简单定时任务
newWorkStealingPool() (Java 8+)ForkJoinPool 支持的多任务窃取线程池✅ 特定并行任务
callable(Runnable task, V result)把 Runnable 转为 Callable✅ 工具方法
unconfigurableExecutorService(…)包装不可配置的线程池✅ 安全场景用
privilegedThreadFactory()获取当前线程上下文权限的 ThreadFactory✅ 高安全场景
defaultThreadFactory()默认线程工厂(可用于手动 ThreadPoolExecutor)✅ 常用

重点方法说明

1. newFixedThreadPool(int nThreads)

ExecutorService pool = Executors.newFixedThreadPool(5);
  • 特点:线程数固定,超出部分任务排队执行
  • 使用:适合任务量固定、不会突发堆积的场景
  • ⚠️ 问题:使用的是无界队列,易造成内存堆积 ➜ ❌不推荐

2. newCachedThreadPool()

ExecutorService pool = Executors.newCachedThreadPool();
  • 特点:线程数量无限制(Integer.MAX_VALUE)
  • 使用:适合大量、短周期的轻量任务
  • ⚠️ 问题:线程无限增长,易 OOM ➜ ❌不推荐

3. newSingleThreadExecutor()

ExecutorService pool = Executors.newSingleThreadExecutor();
  • 特点:只有一个线程,串行执行任务
  • 使用:任务必须顺序执行场景
  • ⚠️ 问题:队列无界,线程挂了会自动创建新线程,可能隐藏问题 ➜ ❌不推荐用于生产高并发

4. newScheduledThreadPool(int corePoolSize)

ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
  • 特点:支持定时、周期性任务,基于 ScheduledThreadPoolExecutor
  • 使用:定时任务(如日志归档、缓存刷新)
  • ✅ 推荐使用,但建议配合 ThreadFactory + 拒绝策略显式构造更安全

5. newWorkStealingPool()

ExecutorService pool = Executors.newWorkStealingPool(); // Java 8+

✅ 高性能,但不适合高并发请求处理,且无法精细控制线程池参数

特点:基于 ForkJoinPool,自动按 CPU 核心数创建工作线程

使用:适合并行分治任务(如大数据分片处理)

推荐做法(线程池最佳实践)

阿里巴巴 Java 开发手册强烈建议不要使用 Executors,而是手动构造:

ThreadPoolExecutor pool = new ThreadPoolExecutor(
10, 20,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200),
Executors.defaultThreadFactory(), // 可替换为自定义命名工厂
new ThreadPoolExecutor.CallerRunsPolicy()
);

拓展方法

方法说明
Executors.callable(Runnable task)把 Runnable 包装成 Callable(返回 null)
Executors.defaultThreadFactory()返回默认线程工厂,线程名如:pool-1-thread-1
Executors.privilegedThreadFactory()创建带当前 AccessControlContext 的线程
Executors.unconfigurableExecutorService()包装线程池,不允许调用配置方法(如 shutdownNow)

总结:Executors 方法使用建议

方法是否推荐说明
newFixedThreadPool无界队列风险
newCachedThreadPool无界线程数风险
newSingleThreadExecutor无界队列、线程替换隐藏问题
newScheduledThreadPool合理控制线程数
newWorkStealingPool高吞吐并行计算
自定义 ThreadPoolExecutor✅强烈推荐可精细控制每个参数,安全、灵活

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor 是 Java 中专门用于执行定时任务周期任务的线程池,它比 Timer 更强大、更安全,是企业级定时任务的首选方案。

ScheduledThreadPoolExecutor 是什么?

ScheduledThreadPoolExecutor 是一个线程池,支持以下几种定时任务调度:

调度类型描述
延迟执行延迟固定时间后执行一次任务
周期执行(固定速率)每隔固定时间开始执行(忽略任务执行时长)
周期执行(固定延迟)每次任务执行完后,延迟固定时间再开始下一次

它是 Executors.newScheduledThreadPool(int corePoolSize) 的底层实现类,继承自 ThreadPoolExecutor

Java线程池

创建线程池的方式

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

等价于:

ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(
2,
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);

常用方法介绍

1. schedule(Runnable command, long delay, TimeUnit unit)

延迟固定时间后执行一次

scheduler.schedule(() -> {
System.out.println("延迟 3 秒后执行一次任务");
}, 3, TimeUnit.SECONDS);

2. scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

周期执行(固定速率):每隔 period 时间触发一次任务,不管任务执行多久

scheduler.scheduleAtFixedRate(() -> {
System.out.println("每 5 秒执行一次,固定速率");
}, 1, 5, TimeUnit.SECONDS);

✅ 适合:定时轮询任务(比如定时监控、健康检查)

⚠️ 注意:如果任务执行超过周期,会出现任务并发执行重叠


3. scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

周期执行(固定延迟):每次任务执行完之后,等 delay 时间再执行下一次

scheduler.scheduleWithFixedDelay(() -> {
System.out.println("每次执行完后再等 5 秒执行");
}, 1, 5, TimeUnit.SECONDS);

✅ 适合:任务耗时不固定、希望每次之间有固定“间隔”


含义和区别:

方法重复?延迟/周期点执行时机并发执行
schedule(Runnable, delay)否(一次性)延迟 delay 后执行一次不管任务执行多长时间,到达延迟点就执行一次不会重复,也不存在并发问题
scheduleAtFixedRate(Runnable, initialDelay, period)是(固定速率)以调度开始时间为基准,每隔 period 安排一次上一次开始时间 + period 到点就触发不会并发执行(内部串行),但如果执行慢于 period,会“立即”补跑
scheduleWithFixedDelay(Runnable, initialDelay, delay)是(固定延迟)以上一次结束时间为基准,结束后再延迟 delay上一次完成时间 + delay 到点才触发同样不并发执行

与 Timer 的区别(为什么用 ScheduledThreadPoolExecutor)

比较点TimerScheduledThreadPoolExecutor
错误隔离❌ 一个任务出异常会终止所有任务✅ 一个任务异常不影响其他任务
并发支持❌ 单线程✅ 多线程
精度一般更精确
线程管理❌ 不支持✅ 支持线程回收、拒绝策略等

✅ 所以:推荐使用 ScheduledThreadPoolExecutor 替代 Timer


推荐封装方式(更安全、更灵活)

ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(
4,
new BasicThreadFactory.Builder()
.namingPattern("scheduled-task-%d")
.daemon(true)
.build(),
new ThreadPoolExecutor.AbortPolicy()
);
  • BasicThreadFactory 是来自 commons-lang3 的工具类,可设置线程名、是否守护线程
  • 可结合监控系统收集执行时间、失败次数等

注意事项

问题建议
任务执行时间过长建议使用 scheduleWithFixedDelay 避免任务重叠
出现异常未捕获用 try-catch 包裹任务逻辑,避免线程退出
执行慢造成堆积可使用 ScheduledExecutorService.shutdownNow() 停止并清理
定时任务过多拆分多个线程池,按业务划分执行优先级

实战示例:每晚 2 点定时执行

public class DailyTask {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

Runnable task = () -> {
System.out.println("执行每日任务:" + LocalDateTime.now());
};

long initialDelay = computeInitialDelay(); // 计算当前时间到 2 点的间隔
long period = 24 * 60 * 60; // 每 24 小时执行一次

scheduler.scheduleAtFixedRate(task, initialDelay, period, TimeUnit.SECONDS);
}

private static long computeInitialDelay() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime nextRun = now.withHour(2).withMinute(0).withSecond(0);
if (now.compareTo(nextRun) > 0) {
nextRun = nextRun.plusDays(1);
}
return Duration.between(now, nextRun).getSeconds();
}
}

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
Java文章

Java线程知识

2025-6-19 21:10:15

文章

只需 1 美元薅 ChatGPT Team 教程,5 个 ChatGPT Team

2025-6-30 11:15:02

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索