Java 线程池

1. 引言

线程池 是用来管理和复用线程的。如果不使用线程池,可能会造成以下问题:

  • 每次都得创建新线程,然后重复销毁,浪费性能
  • 会无限制地创建线程,造成内存压力
  • 线程生命周期难以把控

因此,合理使用线程池是 Java 并发编程的基础之一。

2. 快速创建线程池

使用 Executors 的静态方法可以快速获得线程池实例:

方法 线程池类型 特点 适用场景
newFixedThreadPool(int n) 固定大小 核心线程数 = 最大线程数 = n,队列无界 负载稳定的长期任务
newCachedThreadPool() 缓存型 核心线程数 = 0,最大线程数 = 无限,队列为 SynchronousQueue 短期、突发的异步任务
newSingleThreadExecutor() 单线程 只有一个工作线程,队列无界 需要顺序执行的任务(如日志写入)
newScheduledThreadPool(int n) 定时任务 支持延迟和周期性执行 定时任务(如心跳检测)

示例:

java 复制代码
ExecutorService fixedPool = Executors.newFixedThreadPool(5);

3. 手动创建线程池(ThreadPoolExecutor)

手动创建线程池具有更高的可操作性和灵活性,推荐在生产环境中使用:

java 复制代码
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    2,                              // 核心线程数(corePoolSize)
    5,                              // 最大线程数(maximumPoolSize)
    60L,                            // 空闲线程存活时间(keepAliveTime)
    TimeUnit.SECONDS,               // 时间单位
    new LinkedBlockingQueue<>(100), // 任务队列(有界队列,容量 100)
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略(队列满时由提交线程执行)
);

它们都实现了统一的接口 ExecutorService

4. 核心参数深度解析

4.1 核心线程数(corePoolSize)

计算依据:核心线程数应充分利用 CPU 资源。

  • CPU 密集型任务:核心线程数 = CPU 核数
  • IO 密集型任务:核心线程数 = CPU 核数 × 2
  • 混合型任务 :根据等待时间占比公式计算
    核心线程数 = (总响应时间 / CPU 计算时间) × CPU 核数
    例如:总耗时 500ms 中 CPU 计算时间为 50ms,IO 等待为 450ms
    核心线程数 = (500/50) × 4 核 = 40
  • 保守策略:直接设为 CPU 核数(如 4)以避免资源过载

4.2 最大线程数(maximumPoolSize)

这是线程池可以同时运行的最大线程数量。当工作队列满了,且当前运行的线程数少于最大线程数时,线程池会创建新的线程来处理任务。最大线程数包含核心线程数。

  • 公式推导
    最大线程数 = ((线程等待时间 + CPU 时间) / CPU 时间) × CPU 核数
    代入数据:((450 + 50) / 50) × 4 = 40
  • 安全余量:考虑突发流量,建议设为 50
  • 经验值:通常为核心线程数的 5--10 倍(混合型任务)

4.3 任务队列

实现类 特点 常用场景
ArrayBlockingQueue 有界队列,基于数组,必须指定容量 任务量可控,需限制内存使用
LinkedBlockingQueue 有界/无界队列,基于链表,容量默认为 Integer.MAX_VALUE 任务量大,生产速度不确定
SynchronousQueue 不存储元素,每个插入操作必须等待一个取出操作 传递性任务,要求任务立即被处理

队列大小经验公式:核心线程每秒能处理的任务数 × 2

4.4 拒绝策略(RejectedExecutionHandler)

当任务队列满且工作线程数达到最大值时,新提交的任务会被拒绝。Java 提供四种内置策略:

策略 行为 风险
AbortPolicy(默认) 抛出 RejectedExecutionException 可能导致任务丢失,需捕获异常
CallerRunsPolicy 由提交任务的线程自己执行 阻塞提交者,但保证任务不丢失
DiscardPolicy 直接丢弃新任务,无任何提示 可能丢失数据(慎用)
DiscardOldestPolicy 丢弃队列中最老的任务,然后尝试提交新任务 可能丢失未处理的历史任务

5. 线程池常用方法

5.1 提交任务(最常用)

方法 返回值 说明
execute(Runnable) void 提交无返回值的任务
submit(Runnable) Future<?> 提交无返回值任务,可通过 Future 获取执行状态
submit(Callable<T>) Future<T> 提交有返回值的任务(最常用)
invokeAll(Collection<Callable>) List<Future<T>> 批量提交,等待所有任务完成
invokeAny(Collection<Callable>) T 批量提交,任意一个完成即返回(其他取消)

5.2 销毁与关闭

方法 行为 是否等待任务完成
shutdown() 拒绝新任务,已提交的任务继续执行 等待完成
shutdownNow() 尝试中断正在执行的任务,返回未执行的任务列表 强制中断
awaitTermination(timeout, unit) 阻塞等待,直到线程池完全终止或超时 配合 shutdown() 使用

5.3 状态查询

方法 返回值 说明
isShutdown() boolean 是否已调用 shutdown()shutdownNow()
isTerminated() boolean 是否已彻底终止(所有任务完成)
awaitTermination(timeout, unit) boolean 阻塞等待,直到终止或超时

6. 最佳实践:优雅使用线程池

java 复制代码
public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 1. 创建线程池(手动创建,不要用 Executors)
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
            4, 8, 60, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(200),
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
        
        // 2. 提交有返回值的任务
        Future<String> future = pool.submit(() -> {
            Thread.sleep(2000);
            return "任务结果";
        });
        
        try {
            String result = future.get(3, TimeUnit.SECONDS);
            System.out.println(result);
        } catch (TimeoutException e) {
            System.out.println("任务超时");
            future.cancel(true); // 取消任务
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        // 3. 优雅关闭
        pool.shutdown();
        try {
            if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
                pool.shutdownNow();
            }
        } catch (InterruptedException e) {
            pool.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
相关推荐
warpdrivelabs2 小时前
Codex 开源 harness 全面了解
开发语言·人工智能
2401_894915532 小时前
GEO 优化源码全解析:从搜索引擎到 AI 引擎的底层改写逻辑
java·服务器·前端·数据库·人工智能·分布式·搜索引擎
Java牛马6 小时前
SpringBoot Starter 依赖相关总结
java·spring·springboot·starter·自动装配·依赖
丰锋ff6 小时前
基于 Qt 的智能门禁系统(二)
开发语言·qt
吴声子夜歌6 小时前
Java面试——Spring Cloud原理及应用(二)
java·spring cloud·面试
fpcc6 小时前
跟我学C++中级篇——编译期的条件选择
开发语言·c++
SomeB1oody6 小时前
【RustyML入门】6.3. 并行归约
开发语言·后端·机器学习·rust·教程
东小西7 小时前
【SAA实战】第 1 篇:ReactAgent 入门——先撸个"会调工具的助手"跑起来
java·人工智能·spring
mqiqe7 小时前
AgentScope Java 2.0 协议集成全景解析:A2A、AG-UI、Agent Protocol 三大开放协议实战指南
java·开发语言·ui
lzhdim8 小时前
12、JavaScript常见的内存泄露问题 - JavaScript学习系列文章
开发语言·前端·javascript·学习·ecmascript