再聊线程池

本文详细介绍了线程池的工作原理及内部实现机制,包括RejectedExecutionHandler的四种拒绝策略,以及ThreadPoolExecutor的状态变化过程。此外还介绍了核心类Worker的职责。

  关于线程池的介绍在http://blog.youkuaiyun.com/zero__007/article/details/43795287http://blog.youkuaiyun.com/zero__007/article/details/44102239 已经介绍了,这里只是稍微补充补充。

RejectedExecutionHandler
  ThreadPoolExecutor的构造函数中会有该参数,表示拒绝策略。当队列满且线程池大小>=maximumPoolSize时会触发驳回,因为这时线程池已经不能响应新提交的任务,驳回时就会回调这个接口rejectedExecution方法,JDK默认提供了4种驳回策略,可以根据业务场景来选择,线程池的默认策略是AbortPolicy。

    ThreadPoolExecutor.AbortPolicy:直接抛出运行时异常。
    ThreadPoolExecutor.CallerRunsPolicy: 转成同步调用。
    ThreadPoolExecutor.DiscardPolicy:  直接丢弃。
    ThreadPoolExecutor.DiscardOldestPolicy: 取出队列的头并丢弃,重新提交该任务。

  ThreadPoolExecutor预留了以下三个方法,我们可以通过继承该类来做一些扩展,比如监控、日志等等。

protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Thread t, Runnable r) { }
protected void terminated() { }

ThreadPoolExecutor状态
  RUNNING:初始状态,接受新任务并且处理已经在队列中的任务。
  SHUTDOWN:不接受新任务,但处理队列中的任务。
  STOP:不接受新任务,不处理排队的任务,并中断正在进行的任务。
  TIDYING:所有任务已终止,workerCount为零,线程转换到状态TIDYING,这时回调terminate()方法。
  TERMINATED:终态,terminated()执行完成。

Worker
  ThreadPoolExecutor中重要的类是Worker:

   private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        // worker 所对应的线程
        final Thread thread;
        // worker所对应的第一个任务
        Runnable firstTask;
        // 已完成任务数量
        volatile long completedTasks;
    }

  该类继承了AQS与Runnable,其重载的run中会不断从queue中获取任务并执行。corePoolSize、maximumPoolSize控制的即是该类的数量。

<think>我们正在讨论线程池的创建参数、优缺点、线程分配顺序、拒绝策略以及线程池类型。以下将逐一展开: ### 一、线程池创建参数(以Java的ThreadPoolExecutor为例) 线程池的创建通常需要配置以下核心参数: 1. **corePoolSize(核心线程数)** 线程池中保持活动状态的最小线程数,即使它们处于空闲状态也不会被销毁(除非设置`allowCoreThreadTimeOut`为true)[^4]。 2. **maximumPoolSize(最大线程数)** 线程池允许创建的最大线程数。当任务队列满时,线程池会创建新线程直到达到此上限[^4]。 3. **keepAliveTime(线程空闲时间)** 非核心线程空闲时存活的时间。超过此时间且无任务可执行时,非核心线程会被回收[^4]。 4. **unit(时间单位)** `keepAliveTime`的时间单位(如秒、毫秒)[^4]。 5. **workQueue(任务队列)** 用于存放待执行任务的阻塞队列,常见类型有: - `ArrayBlockingQueue`:基于数组的有界队列 - `LinkedBlockingQueue`:基于链表的无界队列(需警惕OOM) - `SynchronousQueue`:不存储元素的直接传递队列 - `PriorityBlockingQueue`:带优先级的无界队列 6. **threadFactory(线程工厂)** 用于创建新线程,可自定义线程名称、优先级等[^4]。 7. **rejectedExecutionHandler(拒绝策略)** 当任务队列满且线程数达到最大值时,对新提交任务的处理策略(详见第四部分)[^2]。 --- ### 二、线程池的优缺点 #### 优点: 1. **降低资源消耗** 复用已创建的线程,减少频繁创建/销毁线程的开销(系统调用、内存分配等)[^3]。 2. **提高响应速度** 任务到达时可直接使用空闲线程执行,无需等待线程创建[^3]。 3. **控制并发数量** 避免无限制创建线程导致系统资源耗尽(如OOM、CPU过载)[^3]。 4. **统一管理机制** 提供任务队列、监控接口、拒绝策略等标准化功能[^1]。 #### 缺点: 1. **复杂度提升** 需合理配置参数(如线程数、队列容量),不当配置可能导致性能下降或资源浪费。 2. **死锁风险** 若线程池任务相互等待且无空闲线程,可能引发死锁。 3. **调试困难** 线程池内任务执行与提交解耦,异常堆栈信息可能不完整。 --- ### 三、线程分配顺序(任务调度流程) 当新任务提交时,线程池按以下顺序处理: ```mermaid graph TD A[提交新任务] --> B{核心线程空闲?} B -->|是| C[分配核心线程执行] B -->|否| D{任务队列未满?} D -->|是| E[任务入队等待] D -->|否| F{当前线程数 < 最大线程数?} F -->|是| G[创建新线程执行] F -->|否| H[触发拒绝策略] ``` 关键规则: 1. 优先使用核心线程执行任务 2. 核心线程全忙时,任务进入队列 3. 队列满时才创建非核心线程 4. 线程数达最大值且队列满时拒绝任务 > ⚠️ 注意:部分线程池(如`ThreadPoolExecutor`的子类)可能调整此逻辑,例如`SynchronousQueue`会直接创建新线程而非入队。 --- ### 四、拒绝策略 当线程池无法处理新任务时(队列满且线程数达上限),会触发拒绝策略。Java提供四种标准策略[^2]: | 策略名 | 行为 | 适用场景 | |----------------------------------|----------------------------------------------------------------------|--------------------------------------| | `AbortPolicy`(默认) | 直接抛出`RejectedExecutionException` | 需严格保障任务不丢失的场景 | | `CallerRunsPolicy` | 由提交任务的线程直接执行该任务 | 需要降低提交速度的场景(如生产者限流)| | `DiscardPolicy` | 静默丢弃新任务,不抛异常也不执行 | 允许丢弃部分任务的监控统计场景 | | `DiscardOldestPolicy` | 丢弃队列中最旧的任务(即队首任务),然后重试提交当前任务 | 允许丢弃旧任务的实时流处理场景 | > 自定义策略:可通过实现`RejectedExecutionHandler`接口定义特殊逻辑(如记录日志或持久化存储)。 --- ### 五、线程池种类(Java标准实现) Java通过`Executors`工具类提供以下常见线程池: | 线程池类型 | 实现原理 | 特点 | |------------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------| | **FixedThreadPool** | `new ThreadPoolExecutor(n, n, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())` | 固定线程数,无界队列,可能堆积大量任务导致OOM | | **CachedThreadPool** | `new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>())` | 线程数无限扩张,适合短时任务,可能创建过多线程导致资源耗尽 | | **SingleThreadExecutor** | `new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())` | 单线程顺序执行,无界队列,保证任务顺序性 | | **ScheduledThreadPool** | `new ScheduledThreadPoolExecutor(corePoolSize)` | 支持定时/周期性任务,使用`DelayedWorkQueue`存储任务 | > ⚠️ 生产建议:因`FixedThreadPool`/`CachedThreadPool`有资源耗尽风险,推荐直接使用`ThreadPoolExecutor`手动配置参数。 --- ### 总结 线程池通过**参数精细化配置**(核心/最大线程数、队列类型等)实现任务的高效调度,其**核心价值**在于平衡系统资源与并发需求。开发者需结合业务场景(任务性质、并发量、容忍度)选择参数和拒绝策略,避免盲目使用默认配置。 --- §§相关问题§§ 1. 如何根据CPU核心数和任务类型计算线程池的核心线程数? 2. 无界队列(如`LinkedBlockingQueue`)在线程池中会导致什么问题? 3. 如何实现一个自定义拒绝策略(如将拒绝任务存入数据库)? 4. 为什么阿里巴巴开发规范禁止使用`Executors`创建线程池? 5. 线程池的非核心线程在空闲时一定会被回收吗?</think>### 线程池详解 #### 一、创建参数(核心配置) 线程池的创建需要配置以下关键参数: 1. **corePoolSize(核心线程数)** 线程池长期维持的线程数量,即使空闲也不会被回收[^4] **示例**:CPU密集型任务建议设为`CPU核心数+1`,I/O密集型可设为`2*CPU核心数` 2. **maximumPoolSize(最大线程数)** 线程池允许创建的最大线程数量(当队列满时启用)[^4] **经验值**:通常设为`corePoolSize * 2` 3. **keepAliveTime(线程空闲时间)** 非核心线程空闲时的存活时间,超时将被回收[^4] **单位**:配合`TimeUnit`使用(秒/毫秒等) 4. **workQueue(任务队列)** 存储待执行任务的阻塞队列类型: - `ArrayBlockingQueue`:有界队列(需指定容量) - `LinkedBlockingQueue`:无界队列(可能导致OOM) - `SynchronousQueue`:直接传递队列(不存储任务) - `PriorityBlockingQueue`:带优先级的队列 5. **threadFactory(线程工厂)** 自定义线程创建方式(可设置线程名、优先级等) 6. **rejectedExecutionHandler(拒绝策略)** 当队列满且线程数达上限时的处理策略(详见第四部分)[^2] --- #### 二、优缺点分析 | **优点** | **缺点** | |----------|----------| | **✅ 降低资源消耗**:复用线程减少创建/销毁开销[^3] | **❌ 配置复杂**:参数需根据业务精细调整 | | **✅ 提高响应速度**:任务到达可直接执行[^1] | **❌ 死锁风险**:任务间依赖可能导致阻塞 | | **✅ 控制并发数量**:防止线程爆炸引发OOM[^3] | **❌ 调试困难**:异步任务异常堆栈不完整 | | **✅ 提供管理功能**:统一监控/终止/拒绝机制[^1] | **❌ 资源泄漏**:未正确关闭会导致线程驻留 | --- #### 三、线程分配顺序 当新任务提交时,线程池按以下优先级分配资源: ```mermaid graph LR A[新任务] --> B{核心线程空闲?} B -->|是| C[立即执行] B -->|否| D{队列未满?} D -->|是| E[加入队列等待] D -->|否| F{线程数<最大值?} F -->|是| G[创建新线程执行] F -->|否| H[触发拒绝策略] ``` **关键规则**: 1. 优先使用核心线程 2. 次优先入队等待 3. 最后创建非核心线程 4. 队列满且线程达上限时拒绝任务 --- #### 四、拒绝策略详解 当线程池无法处理新任务时,触发以下策略[^2]: | **策略类型** | **行为** | **适用场景** | |--------------|----------|--------------| | `AbortPolicy`(默认) | 抛出`RejectedExecutionException` | 需严格保证任务不丢失的场景 | | `CallerRunsPolicy` | 由提交任务的线程直接执行 | 需要降低任务提交速度的场景 | | `DiscardPolicy` | 静默丢弃新任务 | 允许丢弃部分任务的监控场景 | | `DiscardOldestPolicy` | 丢弃队列最旧任务后重试 | 实时流处理(保留最新数据) | **自定义策略示例**(记录日志后存储到DB): ```java new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { logger.warn("任务被拒绝: " + r); db.saveRejectedTask(r); // 持久化存储 } } ``` --- #### 五、线程池类型(Java标准实现) 1. **FixedThreadPool** ```java Executors.newFixedThreadPool(int nThreads) ``` - 固定大小线程池 - 使用无界队列(`LinkedBlockingQueue`) - **风险**:可能堆积大量任务导致OOM 2. **CachedThreadPool** ```java Executors.newCachedThreadPool() ``` - 线程数可无限扩张 - 使用`SynchronousQueue`(直接传递任务) - **适用**:短时异步任务 - **风险**:可能创建过多线程耗尽资源 3. **SingleThreadExecutor** ```java Executors.newSingleThreadExecutor() ``` - 单线程顺序执行 - 保证任务执行顺序性 - **适用**:需要顺序执行的任务队列 4. **ScheduledThreadPool** ```java Executors.newScheduledThreadPool(int coreSize) ``` - 支持定时/周期性任务 - 使用`DelayedWorkQueue` - **适用**:定时任务调度 > ⚠️ **生产建议**:避免直接使用`Executors`创建,推荐手动配置`ThreadPoolExecutor`参数[^4]。 --- ### 总结 线程池通过**参数精细化配置**(核心/最大线程数、队列类型等)实现任务的高效调度,其**核心价值**在于平衡系统资源与并发需求。开发中需特别注意: 1. 根据任务类型(CPU/I/O密集型)设置线程数 2. 选择匹配业务场景的队列和拒绝策略 3. 避免使用隐含风险的默认线程池 4. 务必实现完善的关闭和监控机制 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值