Java 多线程与线程池完整笔记
侧边栏壁纸
  • 累计撰写 36 篇文章
  • 累计收到 1 条评论

Java 多线程与线程池完整笔记

ASN__
2026-04-09 / 0 评论 / 4 阅读 / 正在检测是否收录...

Java 多线程与线程池完整笔记


一、线程池基础

1.1 什么是线程池

线程池是一种多线程处理模式,它在系统启动时预先创建一定数量的线程放入池中。这些线程在没有任务时处于等待状态,当有任务提交时,池中某个空闲线程会被唤醒执行任务。任务执行完成后,线程并不会被销毁,而是返回池中继续等待下一个任务。

┌─────────────────────────────────────────────────┐
│                  线程池架构                       │
├─────────────────────────────────────────────────┤
│  ┌─────┐  ┌─────┐  ┌─────┐  ┌─────┐            │
│  │线程1│  │线程2│  │线程3│  │线程N│   ← 常驻线程 │
│  └──┬──┘  └──┬──┘  └──┬──┘  └──┬──┘            │
│     │         │         │         │              │
│     └─────────┴────┬────┴─────────┘              │
│                    ▼                             │
│            ┌───────────────┐                     │
│            │   任务队列     │  ← 缓冲待处理任务    │
│            └───────────────┘                     │
└─────────────────────────────────────────────────┘

1.2 为什么需要线程池

优势说明
资源复用避免频繁创建和销毁线程的开销,线程对象可重复使用
响应速度任务到达时无需等待线程创建,立即执行
可管理性统一分配、调优和监控线程,防止资源耗尽
流量控制通过队列和最大线程数限制并发量,保护系统

二、ThreadPoolExecutor 核心参数详解

2.1 七个核心参数

public ThreadPoolExecutor(
    int corePoolSize,              // 1. 核心线程数
    int maximumPoolSize,           // 2. 最大线程数
    long keepAliveTime,            // 3. 空闲线程存活时间
    TimeUnit unit,                 // 4. 时间单位
    BlockingQueue<Runnable> workQueue,  // 5. 工作队列
    ThreadFactory threadFactory,   // 6. 线程工厂
    RejectedExecutionHandler handler    // 7. 拒绝策略
)
参数含义比喻
corePoolSize核心线程数,即使空闲也保留在池中的线程数量餐厅正式员工,永不辞退
maximumPoolSize最大线程数 = 核心线程 + 临时线程正式员工 + 临时工的上限
keepAliveTime临时线程空闲超过该时间则被回收临时工多久没活干就辞退
unitkeepAliveTime 的时间单位秒、毫秒等
workQueue存放待执行任务的阻塞队列餐厅等位区
threadFactory创建线程的工厂,可自定义线程名、优先级等员工招聘标准
handler队列满且线程达最大值时的拒绝策略客满时的处理方式

2.2 常用工作队列类型

队列类型特点适用场景
SynchronousQueue不存储任务,直接交给线程执行CachedThreadPool
LinkedBlockingQueue无界队列(默认 Integer.MAX_VALUE)FixedThreadPool
ArrayBlockingQueue有界队列,需指定容量需限制队列长度的场景
PriorityBlockingQueue优先级队列任务有优先级要求

三、线程池执行流程(五步核心逻辑)

// 伪代码表示执行逻辑
public void execute(Runnable task) {
    // 步骤1:当前线程数 < corePoolSize
    if (workerCount < corePoolSize) {
        addWorker(task, true);  // 新建核心线程执行
        return;
    }
    
    // 步骤2:核心线程已满,尝试放入队列
    if (workQueue.offer(task)) {
        // 放入成功,等待空闲线程取走执行
        return;
    }
    
    // 步骤3:队列已满,尝试创建临时线程
    if (workerCount < maximumPoolSize) {
        addWorker(task, false); // 新建临时线程执行
        return;
    }
    
    // 步骤4:线程数已达最大值,执行拒绝策略
    reject(task);
}

流程图:

提交任务
    │
    ▼
当前线程数 < corePoolSize? ──Yes──▶ 创建核心线程执行
    │
   No
    │
    ▼
任务能否入队? ──Yes──▶ 入队等待执行
    │
   No(队列已满)
    │
    ▼
当前线程数 < maximumPoolSize? ──Yes──▶ 创建临时线程执行
    │
   No
    │
    ▼
执行拒绝策略

四、四种拒绝策略

策略类行为比喻使用建议
AbortPolicy抛出 RejectedExecutionException直接赶走客人并吵架默认策略,开发常用,快速失败
CallerRunsPolicy由提交任务的线程自己执行老板亲自服务客人可减缓提交速度,重要任务兜底
DiscardPolicy静默丢弃新任务悄悄赶走客人不重要的任务,如日志上报
DiscardOldestPolicy丢弃队列中最老的任务赶走排队最久的人优先处理新任务
// 自定义拒绝策略示例
new ThreadPoolExecutor.AbortPolicy()            // 抛异常
new ThreadPoolExecutor.CallerRunsPolicy()       // 调用者执行
new ThreadPoolExecutor.DiscardPolicy()          // 直接丢弃
new ThreadPoolExecutor.DiscardOldestPolicy()    // 丢弃最旧的

五、Executors 四种快捷线程池

⚠️ 开发规范:强制禁止使用 Executors 创建线程池,必须通过 ThreadPoolExecutor 构造函数显式指定参数。

原因:

  • FixedThreadPool 和 SingleThreadPool 使用无界队列,可能 OOM
  • CachedThreadPool 和 ScheduledThreadPool 允许创建无限数量线程,可能 OOM
工厂方法核心特点适用场景风险
newFixedThreadPool(n)固定线程数,无界队列重负载服务器队列无限堆积
newCachedThreadPool()0核心,无限临时线程,同步队列大量短期异步任务线程数爆炸
newSingleThreadExecutor()单线程,无界队列任务需顺序执行队列无限堆积
newScheduledThreadPool(n)支持定时/周期任务定时调度线程数爆炸
// ❌ 错误示范
ExecutorService pool = Executors.newFixedThreadPool(10);

// ✅ 正确示范
ExecutorService pool = new ThreadPoolExecutor(
    10, 20, 60L, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(1000),
    new ThreadFactoryBuilder().setNameFormat("my-pool-%d").build(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

六、线程数设置公式

6.1 CPU 密集型任务

定义:大量时间用于计算,很少等待 I/O
线程数 = CPU 核心数 + 1

加 1 是为了在某个线程因缺页中断等原因暂停时,有备用线程顶上,最大化 CPU 利用率。

6.2 I/O 密集型任务

定义:大量时间等待 I/O 操作(网络、磁盘、数据库)
线程数 = CPU 核心数 × 2

更精确的公式:

线程数 = CPU 核心数 × (1 + 平均等待时间 / 平均计算时间)

6.3 混合型任务

将任务拆分为 CPU 密集和 I/O 密集两部分,分别用不同线程池处理。


七、线程池生命周期与优雅关闭

7.1 五种状态

状态说明接收新任务执行队列任务
RUNNING正常运行
SHUTDOWN关闭中(调用 shutdown())
STOP强制停止(调用 shutdownNow())
TIDYING过渡状态,所有任务终止
TERMINATED完全终止

7.2 优雅关闭代码模板

public void shutdownGracefully(ExecutorService pool) {
    pool.shutdown();  // ① 停止接收新任务
    try {
        // ② 等待现有任务完成,最多等待 60 秒
        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
            pool.shutdownNow();  // ③ 超时则强制关闭
            // ④ 再等待 5 秒,确认是否终止
            if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
                System.err.println("线程池未能正常终止");
            }
        }
    } catch (InterruptedException e) {
        pool.shutdownNow();  // ⑤ 当前线程被中断,也强制关闭
        Thread.currentThread().interrupt();
    }
}

八、ScheduledThreadPoolExecutor 定时任务线程池

8.1 概述

ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor,专门用于执行定时和周期性任务,是传统 Timer 的替代品。

对比项TimerScheduledThreadPoolExecutor
线程模型单线程多线程池
异常处理任务抛异常则 Timer 线程终止单任务异常不影响其他任务
任务调度基于绝对时间基于相对时间,更精准
适用场景简单定时生产级定时调度

8.2 三种核心 API

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);

// 1. 延迟执行一次
ScheduledFuture<?> future = scheduler.schedule(
    () -> System.out.println("延迟执行"),
    5, TimeUnit.SECONDS
);

// 2. 固定频率执行(不管上次是否完成)
scheduler.scheduleAtFixedRate(
    () -> System.out.println("固定频率"),
    1,      // 首次延迟 1 秒
    3,      // 每 3 秒执行一次
    TimeUnit.SECONDS
);

// 3. 固定延迟执行(等上次完成后再延迟)
scheduler.scheduleWithFixedDelay(
    () -> System.out.println("固定延迟"),
    1,      // 首次延迟 1 秒
    3,      // 上次完成后等待 3 秒
    TimeUnit.SECONDS
);

8.3 scheduleAtFixedRate vs scheduleWithFixedDelay

方法调度方式效果
scheduleAtFixedRate固定频率(周期固定)不管任务执行多久,严格按周期触发,可能任务堆叠
scheduleWithFixedDelay固定延迟(间隔固定)等上次执行完,再等固定时间才触发,周期 = 执行时间 + 延迟
scheduleAtFixedRate (周期 = 3秒)
任务执行2秒 → 等待1秒 → 下一轮
任务执行4秒 → 立即触发下一轮(无等待)

scheduleWithFixedDelay (延迟 = 3秒)
任务执行2秒 → 等待3秒 → 下一轮(间隔5秒)
任务执行4秒 → 等待3秒 → 下一轮(间隔7秒)

九、线程池性能监控与调优

9.1 关键监控指标

指标含义告警阈值排查方向
活跃线程数当前正在工作的线程数> 最大线程数 80%线程数不足或任务耗时过长
队列长度等待执行的任务数> 队列容量 70%消费速度跟不上生产速度
任务完成数已完成任务累计增长停滞线程池可能卡死或阻塞
拒绝任务数被拒绝的任务累计> 0(正常应为 0)线程数和队列容量需调大
线程池大小当前池中线程总数长期接近 maximumPoolSize调大 maximumPoolSize
任务等待时间任务从提交到执行的时间> 业务可接受阈值处理能力不足

9.2 监控实现方案

@Component
public class ThreadPoolMonitor {
    
    private final ThreadPoolExecutor executor;
    
    @Scheduled(fixedDelay = 30000)  // 每30秒采集一次
    public void monitor() {
        int activeCount = executor.getActiveCount();
        long completedCount = executor.getCompletedTaskCount();
        int queueSize = executor.getQueue().size();
        int poolSize = executor.getPoolSize();
        long rejectCount = // 自定义记录
        
        log.info("线程池监控 - 活跃:{}, 队列:{}, 池大小:{}, 已完成:{}, 拒绝:{}",
            activeCount, queueSize, poolSize, completedCount, rejectCount);
            
        // 告警判断
        if (queueSize > queueCapacity * 0.7) {
            alert("队列使用率超过70%");
        }
    }
}

9.3 调优策略

现象可能原因解决方案
队列持续增长消费速度 < 生产速度增加线程数,或优化任务执行效率
活跃线程数持续满任务耗时过长拆分任务、异步化、增加最大线程数
拒绝次数频繁线程池容量不足增大队列容量和最大线程数
CPU 使用率低但队列满线程阻塞于 I/O增加线程数(I/O 密集型公式)
OOM 内存溢出无界队列堆积改用有界队列 + 合理拒绝策略

十、最佳实践总结

10.1 编码规范

// ✅ 正确:显式创建 ThreadPoolExecutor
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    10,                                    // 核心线程数
    20,                                    // 最大线程数
    60L, TimeUnit.SECONDS,                 // 空闲超时
    new ArrayBlockingQueue<>(1000),        // 有界队列
    new NamedThreadFactory("biz-pool"),    // 自定义线程工厂
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

// ❌ 错误:使用 Executors 快捷方法
ExecutorService pool = Executors.newFixedThreadPool(10);

10.2 必须设置的内容

  • [ ] 有界队列(避免 OOM)
  • [ ] 合适的拒绝策略(根据业务选型)
  • [ ] 有意义的线程名称(便于排查问题)
  • [ ] 监控指标采集(JMX / Micrometer)
  • [ ] 优雅关闭逻辑(应用停止时调用)

10.3 参数选择参考

业务类型corePoolSizemaximumPoolSize队列类型队列容量
计算密集型 APICPU 核数 + 1CPU 核数 + 1ArrayBlockingQueue100~500
I/O 密集型 APICPU 核数 × 2CPU 核数 × 4ArrayBlockingQueue500~2000
后台批量处理1~51~10LinkedBlockingQueue10000+
即时响应任务10~5050~200SynchronousQueue0

本笔记涵盖了 Java 多线程线程池的核心知识点,建议结合实战加深理解。

1

评论

博主关闭了所有页面的评论