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();
        }
    }
}
相关推荐
程序员老陆3 分钟前
通过std::unique_ptr初始化std::shared_ptr
开发语言·c++
成旭先生3 分钟前
AI 文本审核 API:一段中文文本判风险等级、命中标签与处置建议
java·前端·人工智能·api接口·内容风控·文本审核·ugc审核
~~~4558 分钟前
Java 数组详细笔记
java
Jinkxs10 分钟前
Zookeeper - Java API 实现节点的创建与删除开发
java·zookeeper·java-zookeeper
liangshanbo121514 分钟前
前端面试题:AI 对话中超长消息导致内存溢出,怎么解决?
java·开发语言·前端
极客先躯27 分钟前
高级java每日一道面试题-2026年01月20日-实战篇[Docker]-如何实现镜像的跨区域复制?
java·运维·docker·容器·架构图
for_ever_love__34 分钟前
Redis 持久化讲透:RDB、AOF 与混合持久化怎么选
java·数据库·redis·持久化·aof·区别·rdb
CoderYanger40 分钟前
A.每日一题:1614. 括号的最大嵌套深度
java·程序人生·算法·leetcode·面试·学习方法
传人41 分钟前
为什么 Xshell 中可以通过java -version查询到java 版本号码,Electerm 却查不到?
java·linux·服务器
小码哥0681 小时前
能碳管理系统源码揭秘:避坑指南与实战选型
java·spring·能源·能源管理·能源监测·能碳管理