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();
        }
    }
}
相关推荐
日月云棠1 小时前
Spring 源码深度剖析:DispatcherServlet 的自动化注册机制
java·后端
Jazz_z2 小时前
通过 Java 实现 Word 转 TXT
java·c#·word
秋田君2 小时前
Qt_QVariant
开发语言·qt
江华森2 小时前
Python 实现高德地图找房(三):地图可视化与高德 JS API
开发语言·javascript·python
SamDeepThinking2 小时前
为什么我会主动给一个年轻程序员加薪、晋升?
java·后端·程序员
weixin_471383032 小时前
args,...args与Parameters<T>
开发语言·前端
ikun_文2 小时前
Java面向对象编程OOP核心知识
java
宠友信息3 小时前
MySQL 租户数据隔离在即时通讯源码后端中的落地方式
java·数据库·spring boot·redis·websocket·mysql
AI人工智能+电脑小能手3 小时前
【大白话说Java面试题 第163题】【06_Spring篇】第23题:说说SpringBoot 自动配置原理?
java·自动配置·springboot·spi·条件注解