java线程池实现原理

本文深入解析了JDK线程池ThreadPoolExecutor的工作原理,包括生命周期管理、任务调度、线程管理以及生产者消费者模型的应用。理解线程池如何降低资源消耗、提高响应速度和线程管理,对于并发编程至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程池是什么

线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如MySQL。

线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。

而本文描述线程池是JDK中提供的ThreadPoolExecutor类。

当然,使用线程池可以带来一系列好处:

  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。

  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。

  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

#线程池解决的问题是什么

线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:

  • 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
  • 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
  • 系统无法合理管理内部的资源分布,会降低系统的稳定性。

为解决资源分配这个问题,线程池采用了“池化”(Pooling)思想。池化,顾名思义,是为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。

“池化”思想不仅仅能应用在计算机领域,在金融、设备、人员管理、工作管理等领域也有相关的应用。

在计算机领域中的表现为:统一管理IT资源,包括服务器、存储、和网络资源等等。通过共享资源,使用户在低投入中获益。除去线程池,还有其他比较典型的几种使用策略包括:

  • 内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。

  • 连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。

  • 实例池(Object Pooling):循环使用对象,减少资源在初始化和释放时的昂贵损耗。

线程池核心设计与实现

总体设计

Java中的线程池核心实现类是ThreadPoolExecutor,本章基于JDK 1.8的源码来分析Java线程池的核心设计与实现。我们首先来看一下ThreadPoolExecutor的UML类图,了解下ThreadPoolExecutor的继承关系。

在这里插入图片描述

ThreadPoolExecutor实现的顶层接口是Executor,顶层接口Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。ExecutorService接口增加了一些能力:

  • 扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;
  • 提供了管控线程池的方法,比如停止线程池的运行。

AbstractExecutorService则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。

ThreadPoolExecutor是如何运行,如何同时维护线程和执行任务的呢?其运行机制如下图所示:
在这里插入图片描述

线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管理、线程管理。任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:

  • 直接申请线程执行该任务;
  • 缓冲到队列中等待线程执行;
  • 拒绝该任务。线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。

接下来,我们会按照以下三个部分去详细讲解线程池运行机制:

  1. 线程池如何维护自身状态。

  2. 线程池如何管理任务。

  3. 线程池如何管理线程。

生命周期管理

线程池运行的状态,并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护。线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量 (workerCount)。在具体实现中,线程池将运行状态(runState)、线程数量 (workerCount)两个关键参数的维护放在了一起,如下代码所示:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctl这个AtomicInteger类型,是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它同时包含两部分的信息:线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),高3位保存runState,低29位保存workerCount,两个变量之间互不干扰。用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。通过阅读线程池源代码也可以发现,经常出现要同时判断线程池运行状态和线程数量的情况。线程池也提供了若干方法去供用户获得线程池当前的运行状态、线程个数。这里都使用的是位运算的方式,相比于基本运算,速度也会快很多。

关于内部封装的获取生命周期状态、获取线程池线程数量的计算方法如以下代码所示:

private static int runStateOf(int c)     { return c & ~CAPACITY; } //计算当前运行状态
private static int workerCountOf(int c)  { return c & CAPACITY; }  //计算当前线程数量
private static int ctlOf(int rs, int wc) { return rs | wc; }   //通过状态和线程数生成ctl

ThreadPoolExecutor的运行状态有5种,分别为:
在这里插入图片描述

其生命周期转换如下入所示:

在这里插入图片描述

任务执行机制

任务调度

任务调度是线程池的主要入口,当用户提交了一个任务,接下来这个任务将如何执行都是由这个阶段决定的。了解这部分就相当于了解了线程池的核心运行机制。

首先,所有任务的调度都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。其执行过程如下:

  • 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。

  • 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。

  • 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。

  • 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。

  • 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

其执行流程如下图所示:
在这里插入图片描述

任务缓冲

任务缓冲模块是线程池能够管理任务的核心部分。线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

下图中展示了线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素:
在这里插入图片描述

使用不同的队列可以实现不一样的任务存取策略。在这里,我们可以再介绍下阻塞队列的成员:
在这里插入图片描述

任务申请

由上文的任务分配部分可知,任务的执行有两种可能:一种是任务直接由新创建的线程执行。另一种是线程从任务队列中获取任务然后执行,执行完任务的空闲线程会再次去从队列中申请任务再去执行。第一种情况仅出现在线程初始创建的时候,第二种是线程获取任务绝大多数的情况。

线程需要从任务缓存模块中不断地取任务执行,帮助线程从阻塞队列中获取任务,实现线程管理模块和任务管理模块之间的通信。这部分策略由getTask方法实现,其执行流程如下图所示:

在这里插入图片描述

getTask这部分进行了多次判断,为的是控制线程的数量,使其符合线程池的状态。如果线程池现在不应该持有那么多线程,则会返回null值。工作线程Worker会不断接收新任务去执行,而当工作线程Worker接收不到任务的时候,就会开始被回收。

任务拒绝

任务拒绝模块是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。

拒绝策略是一个接口,其设计如下:

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

用户可以通过实现这个接口去定制拒绝策略,也可以选择JDK提供的四种已有拒绝策略,其特点如下:

在这里插入图片描述

##Worker线程管理

Worker线程

线程池为了掌握线程的状态并维护线程的生命周期,设计了线程池内的工作线程Worker。我们来看一下它的部分代码:

	private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    final Thread thread;//Worker持有的线程
    Runnable firstTask;//初始化的任务,可以为null
	}

Worker这个工作线程,实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask。thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务;firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;如果这个值是null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建。

Worker执行任务的模型如下图所示:
在这里插入图片描述

线程池需要管理线程的生命周期,需要在线程长时间不运行的时候进行回收。线程池使用一张Hash表去持有线程的引用,这样可以通过添加引用、移除引用这样的操作来控制线程的生命周期。这个时候重要的就是如何判断线程是否在运行。

Worker是通过继承AQS,使用AQS来实现独占锁这个功能。没有使用可重入锁ReentrantLock,而是使用AQS,为的就是实现不可重入的特性去反应线程现在的执行状态。

  • lock方法一旦获取了独占锁,表示当前线程正在执行任务中。

  • 如果正在执行任务,则不应该中断线程。

  • 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断。

  • 线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;如果线程是空闲状态则可以安全回收。

在线程回收过程中就使用到了这种特性,回收过程如下图所示:

在这里插入图片描述

Worker线程增加

增加线程是通过线程池中的addWorker方法,该方法的功能就是增加一个线程,该方法不考虑线程池是在哪个阶段增加的该线程,这个分配线程的策略是在上个步骤完成的,该步骤仅仅完成增加线程,并使它运行,最后返回是否成功这个结果。addWorker方法有两个参数:firstTask、core。firstTask参数用于指定新增的线程执行的第一个任务,该参数可以为空;core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize,其执行流程如下图所示:
在这里插入图片描述

Worker线程回收

线程池中线程的销毁依赖JVM自动的回收,线程池做的工作是根据当前线程池的状态维护一定数量的线程引用,防止这部分线程被JVM回收,当线程池决定哪些线程需要回收时,只需要将其引用消除即可。Worker被创建出来后,就会不断地进行轮询,然后获取任务去执行,核心线程可以无限等待获取任务,非核心线程要限时获取任务。当Worker无法获取到任务,也就是获取的任务为空时,循环会结束,Worker会主动消除自身在线程池内的引用。

try {
  while (task != null || (task = getTask()) != null) {
    //执行任务
  }
} finally {
  processWorkerExit(w, completedAbruptly);//获取不到任务时,主动回收自己
}

线程回收的工作是在processWorkerExit方法完成的。
在这里插入图片描述

事实上,在这个方法中,将线程引用移出线程池就已经结束了线程销毁的部分。但由于引起线程销毁的可能性有很多,线程池还要判断是什么引发了这次销毁,是否要改变线程池的现阶段状态,是否要根据新状态,重新分配线程。

Worker线程执行任务

在Worker类中的run方法调用了runWorker方法来执行任务,runWorker方法的执行过程如下:

while循环不断地通过getTask()方法获取任务。

getTask()方法从阻塞队列中取任务。

如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态。

执行任务。

如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程。

执行流程如下图所示:

在这里插入图片描述

转载自 美团技术

<think>嗯,用户之前问了关于System.nanoTime()补偿延迟的问题,现在又转向Java线程池实现原理。看起来他们对Java并发编程有持续的兴趣,可能是开发中遇到了性能优化或者任务调度的问题。需要确定他们的实际需求,是单纯学习还是解决具体问题。 线程池原理涉及核心概念如任务队列、线程管理、拒绝策略等。用户可能想了解如何高效使用线程池,或者优化现有代码中的线程池配置。他们可能在使用线程池时遇到了资源耗尽、任务堆积或者响应延迟的问题,需要理解内部机制来调整参数或选择合适的策略。 另外,用户之前的问题是关于延迟补偿,可能他们现在在处理需要高并发的定时任务,比如游戏服务器、高频交易系统等,这时候线程池的正确使用至关重要。需要深入讲解线程池的工作流程、参数配置,以及如何避免常见问题,比如资源泄露或线程饥饿。 需要检查是否要补充线程池的生命周期管理,比如如何优雅关闭,或者不同线程池类型的区别,如FixedThreadPool和CachedThreadPool的适用场景。用户可能对Executors工厂类提供的线程池和自定义ThreadPoolExecutor的区别不太清楚,需要强调安全性和最佳实践,比如为什么不推荐使用Executors创建线程池。 另外,线程池的拒绝策略也是一个重点,用户可能遇到过任务被拒绝的情况,需要解释四种内置策略以及如何自定义。还有线程池的状态转换,比如RUNNING、SHUTDOWN、STOP等,这些对于正确管理线程池生命周期很重要。 可能用户对源码实现感兴趣,比如任务队列如何工作,线程是如何复用的。这时候需要结合源码结构,比如ThreadPoolExecutor中的Worker类,如何从队列中取任务执行。还要注意线程池如何动态调整线程数量,比如核心线程和最大线程的关系,keepAliveTime的作用。 最后,用户可能需要实际应用中的建议,比如如何根据业务场景选择合适的线程池参数,监控线程池状态的方法,或者常见的性能调优技巧。需要总结关键点,确保他们能够将理论应用到实际开发中,避免常见错误。</think>Java 线程池是并发编程中的核心组件,通过**复用线程资源、控制并发数量**来优化任务执行效率。其实现原理基于以下几个关键模块: --- ### **1. 线程池的核心组成** #### **a. 任务队列(Work Queue)** - **作用**:存放待执行的任务(`Runnable` 或 `Callable`)。 - **实现类**:通常是 `BlockingQueue`,如: - `LinkedBlockingQueue`(无界队列,默认容量 `Integer.MAX_VALUE`)。 - `SynchronousQueue`(直接传递任务,不存储任务)。 - `ArrayBlockingQueue`(有界队列,需指定容量)。 #### **b. 线程集合(Worker Threads)** - **Worker 类**:内部封装了线程和任务,每个 Worker 绑定一个线程,循环从队列中获取任务执行。 - **线程复用**:任务执行完毕后,Worker 不销毁,而是继续从队列获取新任务。 #### **c. 线程池控制器(ThreadPoolExecutor)** - **核心参数**: ```java public ThreadPoolExecutor( int corePoolSize, // 核心线程数(长期存活的线程) int maximumPoolSize, // 最大线程数(队列满时创建临时线程) long keepAliveTime, // 空闲线程存活时间(超时后销毁) TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 RejectedExecutionHandler handler // 拒绝策略 ) ``` --- ### **2. 线程池的工作流程** 1. **提交任务**:调用 `execute(Runnable task)` 或 `submit(Callable task)`。 2. **创建核心线程**: - 如果当前线程数 < `corePoolSize`,立即创建新线程执行任务。 3. **任务入队**: - 如果核心线程已满,任务进入队列等待。 4. **创建临时线程**: - 如果队列已满且线程数 < `maximumPoolSize`,创建临时线程处理任务。 5. **拒绝策略**: - 如果队列满且线程数已达上限,触发拒绝策略(如抛出异常、丢弃任务等)。 ![线程池工作流程示意图](https://miro.medium.com/v2/resize:fit:1400/1*W5Lv7X6MDrDvmdEMx4H8Bg.png) --- ### **3. 核心源码逻辑(简化版)** #### **任务提交与执行** ```java public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); // 1. 当前线程数 < corePoolSize:创建核心线程 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } // 2. 任务入队 if (isRunning(c) && workQueue.offer(command)) { // 二次检查线程池状态 int recheck = ctl.get(); if (!isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); // 创建非核心线程 } // 3. 队列已满,尝试创建临时线程 else if (!addWorker(command, false)) reject(command); // 触发拒绝策略 } ``` #### **Worker 线程的生命周期** ```java private final class Worker extends AbstractQueuedSynchronizer implements Runnable { final Thread thread; // 绑定的线程 Runnable firstTask; // 初始任务 Worker(Runnable firstTask) { this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } public void run() { runWorker(this); // 核心循环逻辑 } } final void runWorker(Worker w) { Runnable task = w.firstTask; w.firstTask = null; while (task != null || (task = getTask()) != null) { // 循环获取任务 try { task.run(); // 执行任务 } finally { task = null; } } processWorkerExit(w); // 线程退出 } ``` -- ### **4. 拒绝策略(RejectedExecutionHandler)** | 策略名称 | 行为 | |-------------------------|----------------------------------------------------------------------| | `AbortPolicy`(默认) | 直接抛出 `RejectedExecutionException` | | `CallerRunsPolicy` | 由提交任务的线程直接执行该任务(如主线程执行) | | `DiscardPolicy` | 静默丢弃被拒绝的任务 | | `DiscardOldestPolicy` | 丢弃队列中最旧的任务,然后重新尝试提交当前任务 | --- ### **5. 线程池状态管理** 线程池通过 `ctl`(AtomicInteger)同时维护**线程数量**和**运行状态**: - **状态标志(高 3 位)**: - `RUNNING`:接受新任务并处理队列中的任务。 - `SHUTDOWN`:不再接受新任务,但处理队列中的任务。 - `STOP`:不再接受新任务,不处理队列中的任务,并中断正在执行的任务。 - `TIDYING`:所有任务已终止,线程数为 0。 - `TERMINATED`:`terminated()` 方法已执行完毕。 --- ### **6. 常见线程池类型** 通过 `Executors` 工厂类创建: | 类型 | 特点 | |--| | `newFixedThreadPool` | 固定线程数,使用无界队列(可能堆积 OOM) | | `newCachedThreadPool` | 线程数无上限,空闲线程 60 秒回收(可能创建过多线程导致 CPU 过载) | | `newSingleThreadExecutor` | 单线程,保证任务顺序执行 | | `newScheduledThreadPool` | 支持定时或周期性任务 | **注意**:生产环境中建议**手动配置 `ThreadPoolExecutor`**,避免使用 `Executors` 的预设方法(防止资源耗尽风险)。 --- ### **7. 关键优化点** 1. **合理设置核心参数**: - `corePoolSize`:根据 CPU 密集型或 I/O 密集型任务调整(通常为 CPU 核数 ±2)。 - `workQueue`:有界队列防止内存溢出。 2. **监控线程池状态**: - 使用 `ThreadPoolExecutor` 的 `getActiveCount()`、`getQueue().size()` 等方法。 3. **优雅关闭**: - 调用 `shutdown()` 或 `shutdownNow()`,结合 `awaitTermination()` 等待任务结束。 --- ### **8. 适用场景** - **Web 服务器**:处理高并发请求。 - **批量数据处理**:如日志分析、文件导入导出。 - **异步任务处理**:发送邮件、消息推送等。 --- 通过理解线程池实现原理,可以更高效地管理多线程任务,避免资源浪费和性能瓶颈。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值