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();
        }
    }
}
相关推荐
sunshine22 girl19 小时前
Idea中如何搜索
java·ide·intellij-idea
猫吻鱼19 小时前
【AI 01】【Spring AI 基础使用】
java·spring·spring ai
红宝村村长19 小时前
windows笔记本双系统ubuntu设置
开发语言
梨涡泥窝19 小时前
基于SSM的校园二手交易平台的设计与实现
java·tomcat
许彰午20 小时前
44-useRowSet镜像实现
java·低代码·架构
bamboolm20 小时前
springboot+Ollama整合
java·ollama
菜鸟~noob23321 小时前
【电子战】第15篇:混合定位——TDOA/FDOA/AOA 联合【含matlab代码】
开发语言·matlab
我不会起名字32221 小时前
一天一道算法题(34):回溯法的经典例题(子集)
java·数据结构·python·算法·golang·深度优先·力扣
Wang's Blog1 天前
Java框架快速入门: Spring Security+OAuth2之JWT核心概念与实战
java·spring·log4j
Dreams_l1 天前
死信队列和延迟队列介绍
java·开发语言