Java线程池是如何保证核心线程不被销毁的

对于Java中 Thread 对象,同一个线程对象调用 start 方法后,会在执行完run 后走向终止(TERMINATED)状态,也就是说一个线程对象是不可以通过多次调用 start 方法重复执行 run 方法内容的。

详情可通过该链接了解:Java同一个线程对象能否多次调用start方法

问题:那 Java 线程池中是如何保证核心线程不会终止的呢?

接下来将通过源码分析线程池是如何保证核心线程不被终止的,在分析前需要了解 ThreadPoolExecutor中几个重要成员变量和方法,便于下面源码阅读:

ThreadPoolExecutor 成员变量和方法介绍

  1. ctl 原子整型变量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  • ctl 包含两个字段
    • workerCount:表示线程池中实际生效的线程数;
    • runState:表示线程池运行状态(注意和线程状态进行区分),线程池状态包括以下几种:
      • RUNNING:可以接收新任务并可执行队列中的任务;
      • SHUTDOWN:不接收新任务,但可执行队列中的任务;
      • STOP:不接收新任务,不执行队列任务并且中断正在执行的任务;
      • TIDYING:所有任务已终止,workerCount 为0,将会运行钩子方法 terminated;
      • TERMINATED:terminated 方法调用完成的状态。
  1. 线程池中实际生效线程的最大容量
//Integer.SIZE等于32
private static final int COUNT_BITS = Integer.SIZE - 3;
//实际有效线程的最大容量
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
  • 实际生效最大线程数为
    2 29 − 1 2^{29} - 1 2291
  • 为何使用int类型是因为相对于long运行的更快一点,如果将来int不够用,可以使用AtomicLong代替。
  1. 线程池运行状态
// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

这里我们仅需要知道只有运行(RUNNING)状态是小于0的,其他状态下都是大于等于0的。
4. 获取线程池运行状态

private static int runStateOf(int c)     {
   
    return c & ~CAPACITY; }

方法入参 c 即为 ctl,通过该方法位运算可以得到线程池的状态值,并与 3 中的状态进行比较来进行逻辑处理。
5. 获取线程池中当前实际有效的线程数量

private static int workerCountOf(int c)  {
   
    return c & CAPACITY; }

同上,方法入参 c 为 ctl 原子整形变量,通过位运算得到线程池中实际的线程数 workCount。
6. 工作线程集合

private final HashSet<Worker> workers = new HashSet<Worker>();

线程池中每一个有效线程都会被包装为 Worker 对象。
7. Worker 内部类

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
  • 类 Worker 主要用于维护运行任务线程的中断控制状态;
  • 继承 AQS 实现了一个简单的不可重入互斥锁,而不是使用可重入锁,因为不希望工作任务在调用setCorePoolSize之类的池控制方法时能够重新获取锁;
  • 为了在线程真正开始运行任务之前禁止中断,将锁状态初始化为负值,并在启动时清除它(runWorker中
线程池进入终止状态后,核心线程会被销毁。终止状态通常通过调用线程池的 `shutdown()` 或 `shutdownNow()` 方法触发。在调用 `shutdown()` 后,线程池再接受新任务,但会等待已提交的任务成。所有线程(包括核心线程)在任务成后会被销毁[^1]。而 `shutdownNow()` 则会立即尝试停止所有正在执行的任务,并返回未开始的任务列表,线程池中的线程(包括核心线程)会被中断并销毁[^1]。 此外,如果线程池所在的应用程序结束或 JVM 退出,所有线程(包括核心线程)也会被自动销毁。 ### 核心线程的超时销毁机制 默认情况下,核心线程会因为空闲而被销毁。然而,如果调用了 `allowCoreThreadTimeOut(true)`,核心线程的行为会与非核心线程类似:当核心线程空闲时间超过 `keepAliveTime` 时,会被销毁[^1]。例如,如果 `corePoolSize=2`、`keepAliveTime=60` 秒,且设置了 `allowCoreThreadTimeOut(true)`,则核心线程在空闲 60 秒后会被销毁。 ### 示例代码 以下是一个简单的示例,展示如何设置 `allowCoreThreadTimeOut(true)` 并观察核心线程的超时销毁行为: ```java import java.util.concurrent.*; public class ThreadPoolExample { public static void main(String[] args) throws InterruptedException { ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); pool.allowCoreThreadTimeOut(true); for (int i = 1; i <= 2; i++) { pool.submit(() -> { Thread thread = Thread.currentThread(); String name = thread.getName(); System.out.println("任务执行中,线程名称: " + name); }); } Thread.sleep(70000); // 等待足够长的时间,让核心线程超时销毁 System.out.println("线程数: " + pool.getPoolSize() + ", 活跃线程数: " + pool.getActiveCount()); } } ``` 在上述代码中,核心线程会在空闲 60 秒后被销毁,因此在 `Thread.sleep(70000)` 之后,线程池中的线程数应减少。 ### 总结 - **终止状态**:通过调用 `shutdown()` 或 `shutdownNow()`,线程池进入终止状态后,核心线程会被销毁。 - **超时销毁**:通过设置 `allowCoreThreadTimeOut(true)`,核心线程可以在空闲时间超过 `keepAliveTime` 后被销毁
评论 15
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值