线程池的工作原理?

1 个回答

  • 药老
    纵然前路迷茫,纵然天隔一方,纵然我手中的刀刃已然破碎,也无法阻挡我寻找你的道路。
    # 一、线程池的核心部件 * **corePoolSize**:核心线程数。优先创建并常驻(可设置 `allowCoreThreadTimeOut` 让其空闲超时回收)。 * **maximumPoolSize**:最大线程数(= 核心 + 非核心)。 * **workQueue**:任务队列(有界/无界/直移交等不同实现)。 * **keepAliveTime**:非核心线程空闲多久回收;`allowCoreThreadTimeOut=true` 时也作用于核心线程。 * **ThreadFactory**:线程创建方式(命名、守护、优先级、异常处理)。 * **RejectedExecutionHandler**:拒绝策略(饱和时触发)。 * **Worker**:线程池内部对每个工作线程的包装,负责取任务、执行、统计等。 # 二、任务提交流程(`execute(Runnable)` 伪代码) ```text 1) 若当前工作线程数 < corePoolSize -> 直接新建线程执行任务(不进队列) 否则 2) 尝试将任务放入 workQueue -> 放入成功:交由已有线程从队列取出执行 -> 放入失败(队列已满): 3) 若当前工作线程数 < maximumPoolSize -> 再创建“非核心线程”来执行 否则 4) 触发拒绝策略 RejectedExecutionHandler ``` > 这四步决定了“先扩核心、再排队、队列满再扩到最大、最后才拒绝”的基本策略。 # 三、工作线程如何运行 * 每个 **Worker** 线程循环:**先跑自己被分派的首个任务** → 然后不断从 `workQueue.take()/poll()` 取后续任务执行。 * 空闲超过 `keepAliveTime`(对非核心必生效)会退出;核心线程是否退出取决于 `allowCoreThreadTimeOut`。 * 线程复用:任务结束后不销毁线程,继续取队列任务,降低创建/销毁开销。 # 四、常见队列类型与行为差异 * **`SynchronousQueue`**(直移交,无容量):不存任务,提交时必须有空闲线程接手;促使线程数**快速扩张**到 `maximumPoolSize`。适合“低延迟、高并发、小任务”。 * **`LinkedBlockingQueue`**(常无界,也可有界):优先**排队**,核心线程用满后就排队,**不再扩到最大**(除非你设置有界并且满了)。适合“稳定吞吐、避免线程爆炸”。 * **`ArrayBlockingQueue`**(有界数组):有界排队,队列满后才会继续扩到最大线程,**更易形成背压**,可控。 * **`DelayQueue/DelayedWorkQueue`**(`ScheduledThreadPoolExecutor` 用):按执行时间出队,支持定时/周期任务。 # 五、拒绝策略(饱和时) * **AbortPolicy(默认)**:抛 `RejectedExecutionException`。 * **CallerRunsPolicy**:提交任务的线程自己执行(形成强背压,常用于限流)。 * **DiscardPolicy**:丢弃当前任务。 * **DiscardOldestPolicy**:丢队列里最老的,再尝试入队当前任务(谨慎使用,会丢历史任务)。 # 六、线程池状态与关闭 * 状态流转:**RUNNING → SHUTDOWN → STOP → TIDYING → TERMINATED**。 * `shutdown()`:不再接收新任务,**处理完队列**后退出。 * `shutdownNow()`:尝试中断正在执行的任务,清空队列并返回未执行任务列表。 * 钩子:`beforeExecute` / `afterExecute` / `terminated` 可用来做日志与清理。 # 七、`submit()` 与 `execute()` 的区别 * `execute(Runnable)` 无返回值,不捕获异常。 * `submit(Callable/Runnable)` 返回 `Future`,可 `get()` 结果与异常(异常会包装在 `ExecutionException`)。 # 八、调优与选型要点(实战) 1. **先估并发与任务特征** * CPU 密集:`corePoolSize ≈ CPU 核心数`;队列宜小,避免切换。 * I/O 密集:线程数可放大(比如核心数的几倍),或在 JDK 21+ 用**虚拟线程**。 2. **选择合适队列** * 需要**快速扩容、少排队**:`SynchronousQueue` + 大 `maximumPoolSize`。 * 需要**稳定吞吐、有限内存**:`ArrayBlockingQueue(有界)` + 明确拒绝策略(常用 `CallerRuns`)。 * 默认 `LinkedBlockingQueue(无界)` 容易**堆积**,要么设上**界**,要么监控严格。 3. **拒绝策略映射业务语义** * 延迟可接受 → `CallerRunsPolicy`(把压力回传给上游)。 * 可以丢弃低优先级任务 → `DiscardPolicy/DiscardOldestPolicy`。 4. **设置线程名与异常处理**(`ThreadFactory` + `UncaughtExceptionHandler`)便于排障。 5. **监控** * 线程数、队列长度、任务等待/处理时长、拒绝次数、任务失败率。 * 定期 `jstack`/可视化看阻塞点,防止隐形的 I/O 阻塞拖垮池子。 6. **避免“双重排队”** * 应用上层**还有一个队列**时,不要再给线程池配大队列,否则延迟不可控;通常保留**一层有界队列**即可。 # 九、一个“可控背压”的模板 ```java ExecutorService pool = new ThreadPoolExecutor( 8, 16, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), // 有界队列,背压可控 r -> { Thread t = new Thread(r, "biz-"); t.setDaemon(false); return t; }, new ThreadPoolExecutor.CallerRunsPolicy() // 饱和时让提交方执行,限流 ); ``` > 流程:先用满 8 个核心 → 任务入队(最多 1000)→ 队列满才扩到 16 → 再满就让**调用线程**自己执行,流量自然被“顶住”,系统不崩。
    发布于 2025-08-14 01:15 •IP属地 河北
    举报
    评论
    收藏