浅记java线程池

Java 线程池


一、为什么要用线程池

复制代码
不用线程池:
  new Thread(runnable).start()  →  创建/销毁线程开销大
  1000 个任务 = 1000 次创建 + 1000 次销毁
  无法控制并发数 → 内存打爆 OOM

用线程池:
  线程复用,任务排队,资源可控
  1000 个任务 = 最多 corePoolSize 个线程

二、ThreadPoolExecutor 七大参数

java 复制代码
public ThreadPoolExecutor(
    int corePoolSize,              // 1. 核心线程数
    int maximumPoolSize,           // 2. 最大线程数
    long keepAliveTime,            // 3. 非核心线程空闲存活时间
    TimeUnit unit,                 // 4. 时间单位
    BlockingQueue<Runnable> workQueue,  // 5. 任务队列
    ThreadFactory threadFactory,   // 6. 线程工厂
    RejectedExecutionHandler handler    // 7. 拒绝策略
)

2.1 逐参数详解

corePoolSize --- 核心线程数
复制代码
含义:
  - 线程池始终保留的线程数,即使它们处于空闲状态
  - 默认情况下,核心线程不会被回收(除非 allowCoreThreadTimeOut(true))

设置原则:
  CPU 密集型:  N_CPU + 1
  IO 密集型:   N_CPU × 2
  混合型:      N_CPU × 2 ~ 4(压测决定)

不推荐:
  Runtime.getRuntime().availableProcessors() × N
  ↑ 在容器/虚拟化环境中这个值可能不准
maximumPoolSize --- 最大线程数
复制代码
含义:
  - 线程池允许创建的最大线程数
  - 只有在队列满了之后才会创建超过 corePoolSize 的线程

注意:
  maximumPoolSize 只在队列满了之后才发挥作用。
  如果用的是无界队列(如 LinkedBlockingQueue 不设容量),
  maximumPoolSize 参数根本不生效 ------ 任务永远先进队列。
keepAliveTime --- 空闲存活时间
复制代码
含义:
  - 当线程数 > corePoolSize 时,多余的空闲线程等待新任务的最长时间
  - 超时后,非核心线程被销毁

特殊配置:
  executor.allowCoreThreadTimeOut(true)
  → keepAliveTime 对核心线程也生效
  → 核心线程空闲超时也会被回收
workQueue --- 任务队列(关键!)
队列类型 特性 适用场景
SynchronousQueue 不存储任务,来一个任务必须立刻有线程处理 大量短任务,任务数 > 线程数就拒
LinkedBlockingQueue(无界) 无限排队,maximumPoolSize 不生效 任务平稳,不会暴增
LinkedBlockingQueue(有界) 有限排队,配合 maxPoolSize 使用 ⭐推荐,资源可控
ArrayBlockingQueue(有界) 数组实现,固定容量 任务数可预测
PriorityBlockingQueue 按优先级排序 有优先级的任务
DelayQueue 延迟执行 定时任务
threadFactory --- 线程工厂
java 复制代码
ThreadFactory namedFactory = new ThreadFactory() {
    private final AtomicInteger count = new AtomicInteger(1);

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setName("order-pool-" + count.getAndIncrement());  // 有名字好排查
        t.setDaemon(false);  // 非守护线程(默认)
        t.setUncaughtExceptionHandler((thread, e) -> {
            log.error("线程 {} 异常退出", thread.getName(), e);
        });
        return t;
    }
};

三、任务执行流程(必须背熟)

复制代码
execute(task) 被调用:
        │
        ▼
  ┌ 当前线程数 < corePoolSize?
  │    │
  │   是 → new 新线程执行任务(即使有空闲线程)
  │
  │   否 → 任务入 workQueue
  │           │
  │     ┌──入队成功 → 等待空闲线程消费
  │     │
  │     └──入队失败(队列满)
  │           │
  │     当前线程数 < maximumPoolSize?
  │           │
  │    是 → new 新线程执行任务
  │    否 → 执行拒绝策略

重要细节

复制代码
线程池创建线程的时机:

  第 1 ~ corePoolSize 个任务 → 立即创建线程
  第 corePoolSize+1 ~ 队列满 → 入队等待
  队列满之后的任务         → 创建新线程(最多到 maximumPoolSize)
  线程数到 maximumPoolSize 且队列满 → 拒绝

四、拒绝策略

复制代码
拒绝时机:
  ① 线程数 = maximumPoolSize 且 workQueue 满
  ② 线程池已 shutdown,还往里面提交任务

四种内置策略

策略 行为 适用
AbortPolicy(默认) RejectedExecutionException 必须感知到拒绝
CallerRunsPolicy 由提交任务的线程自己执行 不想丢任务,可以降速
DiscardPolicy 静默丢弃,不报错 任务允许丢失(如非关键日志)
DiscardOldestPolicy 丢弃队头最老的任务,重试提交 优先保证新任务
java 复制代码
// AbortPolicy --- 默认,抛异常
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    5, 10, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100),
    new ThreadPoolExecutor.AbortPolicy()
);

// CallerRunsPolicy --- 谁提交谁自己跑
// 好处:提交线程被占用来执行任务 → 提交速度自然降低
// 适合:不能丢任务的场景
new ThreadPoolExecutor.CallerRunsPolicy();

// DiscardPolicy --- 丢了就丢了,日志都不会打
new ThreadPoolExecutor.DiscardPolicy();

// DiscardOldestPolicy --- 把队头最老的丢了,塞进新任务
new ThreadPoolExecutor.DiscardOldestPolicy();

自定义拒绝策略

java 复制代码
// 场景:拒绝时记录日志 + 发告警 + 落库待后续重试
public class AlertRejectedHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        log.error("线程池已满!poolSize={}, queueSize={}, task={}",
            executor.getPoolSize(),
            executor.getQueue().size(),
            r.toString());

        // 发钉钉/飞书告警
        alertService.send("线程池拒绝任务");

        // 落库,后续定时任务拉取重试
        rejectedTaskRepository.save(r);
    }
}

五、预定义线程池 --- 为什么都不推荐

java 复制代码
// Executors 提供的便捷方法 --- 全都埋了坑

// ❌ newFixedThreadPool --- LinkedBlockingQueue 无界,OOM
Executors.newFixedThreadPool(10);

// ❌ newCachedThreadPool --- SynchronousQueue + Integer.MAX_VALUE 线程数
//    来一个任务没线程就创建,来 10W 个就创建 10W 个线程 → OOM
Executors.newCachedThreadPool();

// ❌ newSingleThreadExecutor --- 同样无界队列
Executors.newSingleThreadExecutor();

// ❌ newScheduledThreadPool --- 最大线程数 Integer.MAX_VALUE
Executors.newScheduledThreadPool(4);

// ✅ 正确做法:全部自己 new ThreadPoolExecutor,显式指定队列容量
new ThreadPoolExecutor(
    10, 20, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000),  // ← 必须有界
    namedFactory,
    new CallerRunsPolicy()
);

六、线程池状态机

复制代码
ThreadPoolExecutor 用一个 AtomicInteger ctl 同时表示:
  高 3 位:线程池状态
  低 29 位:工作线程数量

RUNNING(111)
  │  shutdown()
  ▼
SHUTDOWN(000)
  │  队列空 + 线程全部结束
  ▼
TIDYING(-1)
  │  terminated() 回调执行完
  ▼
TERMINATED(-3)

  RUNNING
  │  shutdownNow()
  ▼
STOP(001)
  │
  ▼
TIDYING → TERMINATED
状态 接受新任务 处理队列中任务 怎么进入
RUNNING new ThreadPoolExecutor
SHUTDOWN shutdown()
STOP shutdownNow()
TIDYING --- --- 过渡态
TERMINATED --- --- terminated() 执行完

七、线程池监控

java 复制代码
@Component
public class ThreadPoolMonitor {

    @Autowired
    private ThreadPoolExecutor orderPool;

    @Scheduled(fixedDelay = 5000)  // 每 5 秒采集一次
    public void monitor() {
        int coreSize    = orderPool.getCorePoolSize();
        int maxSize     = orderPool.getMaximumPoolSize();
        int activeCount = orderPool.getActiveCount();
        int poolSize    = orderPool.getPoolSize();
        int queueSize   = orderPool.getQueue().size();
        long completed  = orderPool.getCompletedTaskCount();

        // 告警规则
        if (activeCount >= maxSize) {
            log.warn("线程池已满!active={}, max={}", activeCount, maxSize);
        }

        if (queueSize > 500) {
            log.warn("队列堆积严重!queueSize={}", queueSize);
        }

        // 推送到 Prometheus / Grafana / 飞书
        metrics.push("thread_pool_active", activeCount);
        metrics.push("thread_pool_queue", queueSize);
    }
}

关键监控指标

复制代码
1. activeCount / maximumPoolSize  → 线程池负载率,> 80% 告警
2. queueSize                      → 队列积压,> 容量 80% 告警
3. rejectedCount                  → 拒绝次数,有任何拒绝立刻告警
4. completedTaskCount             → 总完成量,看吞吐趋势
5. poolSize                       → 当前线程数,观察是否需要扩容

八、线程池配置实战

8.1 Spring Boot 配置

java 复制代码
@Configuration
public class ThreadPoolConfig {

    /**
     * 订单处理线程池 --- IO 密集型
     */
    @Bean("orderExecutor")
    public ThreadPoolExecutor orderExecutor() {
        return new ThreadPoolExecutor(
            10,                                   // 核心 10
            20,                                   // 最大 20
            120, TimeUnit.SECONDS,                // 空闲 2 分钟回收
            new LinkedBlockingQueue<>(500),       // 队列 500
            new NamedThreadFactory("order"),       // 线程命名
            new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝时降速
        );
    }

    /**
     * 报表计算线程池 --- CPU 密集型
     */
    @Bean("reportExecutor")
    public ThreadPoolExecutor reportExecutor() {
        return new ThreadPoolExecutor(
            4,                                     // CPU 核心数
            8,
            60, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(100),
            new NamedThreadFactory("report"),
            new ThreadPoolExecutor.AbortPolicy()   // 报表不能丢
        );
    }
}

8.2 动态调整

java 复制代码
// 运行时动态调整(配 Apollo / Nacos 配置中心实时生效)

@Component
public class DynamicThreadPool {

    @Resource(name = "orderExecutor")
    private ThreadPoolExecutor orderPool;

    @ApolloConfigChangeListener
    public void onChange(ConfigChangeEvent event) {
        if (event.isChanged("order.pool.core")) {
            int newCore = event.getInt("order.pool.core", 10);
            orderPool.setCorePoolSize(newCore);
            log.info("核心线程数调整: {} → {}", orderPool.getCorePoolSize(), newCore);
        }
        if (event.isChanged("order.pool.max")) {
            orderPool.setMaximumPoolSize(event.getInt("order.pool.max", 20));
        }
    }
}

8.3 优雅关闭

java 复制代码
@Component
public class GracefulShutdown implements DisposableBean {

    @Resource(name = "orderExecutor")
    private ThreadPoolExecutor orderPool;

    @Override
    public void destroy() {
        log.info("开始关闭线程池...");

        // 第一步:停止接受新任务
        orderPool.shutdown();

        try {
            // 第二步:等待已提交任务完成(最多等 60 秒)
            if (!orderPool.awaitTermination(60, TimeUnit.SECONDS)) {
                // 第三步:超时了就强制中断
                List<Runnable> undone = orderPool.shutdownNow();
                log.warn("强制关闭,未完成任务数: {}", undone.size());

                // 第四步:再等 10 秒
                if (!orderPool.awaitTermination(10, TimeUnit.SECONDS)) {
                    log.error("线程池拒绝关闭!");
                }
            }
        } catch (InterruptedException e) {
            orderPool.shutdownNow();
            Thread.currentThread().interrupt();
        }

        log.info("线程池已关闭");
    }
}

8.4 在异步任务中传递上下文

java 复制代码
// 场景:异步任务需要用到当前请求的 tenant_id、user_id
// 问题:异步线程的 ThreadLocal 是空的

// 解决:用装饰器包装任务
public class ContextDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        // 从主线程捕获上下文
        String tenantId = TenantContext.getTenantId();
        Long userId = UserContext.getUserId();
        String traceId = MDC.get("traceId");

        return () -> {
            try {
                // 设置到异步线程
                TenantContext.setTenantId(tenantId);
                UserContext.setUserId(userId);
                MDC.put("traceId", traceId);
                runnable.run();
            } finally {
                // 清理
                TenantContext.clear();
                UserContext.clear();
                MDC.clear();
            }
        };
    }
}

// 配置中设置
executor.setTaskDecorator(new ContextDecorator());

九、ThreadPoolExecutor 与 ForkJoinPool

ThreadPoolExecutor ForkJoinPool
设计目标 独立任务并发执行 可拆分的递归任务
核心机制 阻塞队列 + 线程池 工作窃取(Work Stealing)
适用 Web 请求、RPC 调用 并行计算、递归算法
Java 默认 --- parallelStream() / CompletableFuture 默认
java 复制代码
// ForkJoinPool 的工作窃取原理:
//
// Thread-1 队列: [T1, T2, T3, T4]  ← 任务多
// Thread-2 队列: [T5]              ← 快做完了
// Thread-2 从 Thread-1 的队尾偷一个 → 负载均衡
//
// 这就是为什么 CompletableFuture.supplyAsync() 默认比 newFixedThreadPool 快

十、常见问题排查

复制代码
问题 1:任务不执行,也不报错
  → 检查:队列是不是无界?任务全在队列里等,线程数没超过 corePoolSize
  → 解决:换成有界队列

问题 2:线程数一直涨到 maxPoolSize
  → 检查:队列是不是太小?核心线程来不及消费
  → 解决:增大队列容量 或 增大 corePoolSize

问题 3:OOM: unable to create new native thread
  → 检查:是不是用了无界队列 + maximumPoolSize 太大?
  → 解决:减小 maximumPoolSize,改用有界队列

问题 4:线程池 shutdown 后线程不退出
  → 检查:队列里的任务是不是阻塞在 IO 上不响应中断?
  → 解决:任务的 IO 操作设置超时时间

问题 5:任务被吞掉,日志没有任何记录
  → 检查:submit() 返回的 Future 是不是没调 get()?
  → 解决:用 execute() 替代 submit(),或调 Future.get()

十一、总结

复制代码
线程池配置口诀:

  核 心 数 --- CPU 密集 +1,IO 密集 ×2
  队 列 长 --- 必须设上限,防止 OOM
  拒 绝 策 --- 关键业务 Abort,非关键 CallerRuns
  命 名 线 程 --- 出问题好排查
  监 控 告 警 --- 线程数、队列长度、拒绝次数 三指标
  优 雅 关 闭 --- shutdown → awaitTermination → shutdownNow
相关推荐
一路向北North1 小时前
Spring Security OAuth2.0(23):分布式系统授权-转发明文给微服务
java·spring·微服务
zmzb01031 小时前
C++课后习题训练记录Day157
开发语言·c++
dunge20261 小时前
2026年7月最新ChatGPT Plus / Pro 与 Codex:当 AI Agent 最新5.6版本来袭,必须理解事务、幂等与补偿
开发语言·人工智能·python
爱吃牛肉的大老虎3 小时前
rust基础之环境搭建
java·开发语言·rust
openKylin3 小时前
与全球技术演进同频,openKylin 3.0从C迈向Rust
c语言·开发语言·rust·开源·开放原子·openkylin
tellmewhoisi4 小时前
多版本共用redis的token有效期校验(过期重新登录)
java·redis·缓存
疯狂打码的少年4 小时前
【软件工程】结构化设计:模块独立性与耦合内聚
java·开发语言·笔记·软件工程
乐观的Terry4 小时前
3、数据库设计与领域实体
java·数据库·spring boot·spring cloud·ai编程
程高兴4 小时前
PMSM基于在线转动惯量辩识的滑模负载转矩观测器MATLAB-SIMULINK仿真模型
开发语言·matlab