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();
        }
    }
}
相关推荐
sbjdhjd4 小时前
从污点追踪断点到 PHP-FPM 请求队列:HIDS PHP WebShell 样本复盘
开发语言·网络安全·数据挖掘·渗透测试·开源·php·shell
一条泥憨鱼4 小时前
【RabbitMQ】高级篇
java·分布式·rabbitmq·php·ruby
京师20万禁军教头4 小时前
50-常用类-Arrays
java
寰梦4 小时前
Spring Boot + MinIO 大文件上传完整实战:MD5 秒传、分片上传、断点续传与引用一致性
java·spring boot·文件上传·minio
SEO_juper5 小时前
Go 站点地图生成器:从首页爬全站,一键生成 sitemap.xml
xml·开发语言·人工智能·golang·node.js·seo·外贸独立站
懂点Tech12 小时前
ctfshow misc28:GIF 高度修改解题与脚本实现
开发语言·windows·python
云和数据.ChenGuang13 小时前
Python 重写(Override)深度解析:从 MRO 到 super() 的完整机制
java·开发语言·人工智能·python·langchain·1024程序员节
摇滚侠13 小时前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 12
java·数据库·笔记
美狐美颜SDK开放平台13 小时前
视频美颜SDK试用DEMO阶段应该测试什么?直播APP接入实操指南
开发语言·人工智能·深度学习·音视频·美颜sdk·直播美颜sdk·第三方美颜sdk
dtq042414 小时前
C++小案例 - 日志实战
开发语言·c++