线程池的实现原理

笔者本来是想要自己去研读美团的文章(https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html),去实现一个动态线程池,虽然最后没有成功,因为发现已经有大佬写好了,我们只需要引入依赖就可以了,但是通过这篇文章,我也更加深刻的了解了线程池的工作流程和原理

一、线程池的概述

1.1 线程池是什么

线程池就是管理一系列线程的资源池。当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。

1.2 线程池的好处

  1. 降低资源消耗:使用池化技术重复利用已创建的线程,降低线程的创建和销毁造成的损耗。
  2. 提高响应速度:任务到达的时候,无须等待线程创建即可立即执行
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  4. 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

二、如何设计和实现线程池

线程池

2.1 线程池的状态

image

2.2 线程池的生命周期转换

image

2.3 线程池的状态维护

线程池运行的状态不是由用户显示设置的,而是伴随着线程池的运行由内部来维护的。由内部来维护。线程池内部使用一个变量维护两个值:运行状态(runState)线程数量 (workerCount)。在具体实现中,线程池将运行状态(runState)、线程数量 (workerCount)两个关键参数的维护放在了一起。
/* by 01130.hk - online tools website : 01130.hk/zh/calcvolume.html */ private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl这个AtomicInteger类型,是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段。他包含两部分信息,线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),高3位保存runState,低29位保存workerCount。

使用一个变量存储两个值的好处:由于某些情况下,需要同时去判断这两个值的状态,用一个变量存储两个值可以避免在做相关的决策的时候,出现不一致的情况,不必为维护两者的一致性,而占用锁资源。

三、线程

3.1 线程的封装

在线程池里面的线程被封装成工作线程worker,他的代码如下:

/* by 01130.hk - online tools website : 01130.hk/zh/calcvolume.html */
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    final Thread thread;//Worker持有的线程
    Runnable firstTask;//初始化的任务,可以为null
}

这个工作线程,实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask。thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务。firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;如果这个值是null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建。
也就说对于线程池里面的线程而言,他每次每次被创建出来,他如果当前线程数量小于核心线程数量,那么此时的worker线程就是核心线程,他被创建的时候就是来执行当前的任务,反之此时的worker线程就是非核心线程,此时他并不是去执行当前的线程,而是从阻塞队列里面拿去任务来执行。

3.2 线程的存储

线程池需要管理线程的生命周期,需要在线程长时间不运行的时候进行回收。线程池使用一张Hash表去持有线程的引用,这样可以通过添加引用、移除引用这样的操作来控制线程的生命周期。这个时候重要的就是如何判断线程是否在运行。
​Worker是通过继承AQS,使用AQS来实现独占锁这个功能。没有使用可重入锁ReentrantLock,而是使用AQS,为的就是实现不可重入的特性去反应线程现在的执行状态。

使AQS的独占锁,而不是使用可重入锁ReentrantLock的原因是因为我们需要去通过是否获取锁成功来判断该线程是否是在执行任务。

3.3 线程的增加

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

3.4 线程的执行任务

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

1.while循环不断地通过getTask()方法获取任务。 2.getTask()方法从阻塞队列中取任务。 3.如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态。 4.执行任务。 5.如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程。

3.5 线程池的销毁

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

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

四、任务的执行机制

4.1 任务调度

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

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

  1. 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
  2. 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
  3. 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
  4. 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
  5. 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
    image

4.2 任务缓冲

任务缓冲其实指的就是线程池里面的阻塞队列。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。使用不同的队列可以实现不一样的任务存取策略。
image

4.3 任务申请

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

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

4.4 任务拒绝

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

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

用户可以通过实现这个接口去定制拒绝策略,也可以选择JDK提供的四种已有拒绝策略。
image

基于径向基函数神经网络RBFNN的自适应滑模控制学习(Matlab代码实现)内容概要:本文介绍了基于径向基函数神经网络(RBFNN)的自适应滑模控制方法,并提供了相应的Matlab代码实现。该方法结合了RBF神经网络的非线性逼近能力和滑模控制的强鲁棒性,用于解决复杂系统的控制问题,尤其适用于存在不确定性和外部干扰的动态系统。文中详细阐述了控制算法的设计思路、RBFNN的结构与权重更新机制、滑模面的构建以及自适应律的推导过程,并通过Matlab仿真验证了所提方法的有效性和稳定性。此外,文档还列举了大量相关的科研方向和技术应用,涵盖智能优化算法、机器学习、电力系统、路径规划等多个领域,展示了该技术的广泛应用前景。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的研究生、科研人员及工程技术人员,特别是从事智能控制、非线性系统控制及相关领域的研究人员; 使用场景及目标:①学习和掌握RBF神经网络与滑模控制相结合的自适应控制策略设计方法;②应用于电机控制、机器人轨迹跟踪、电力电子系统等存在模型不确定性或外界扰动的实际控制系统中,提升控制精度与鲁棒性; 阅读建议:建议读者结合提供的Matlab代码进行仿真实践,深入理解算法实现细节,同时可参考文中提及的相关技术方向拓展研究思路,注重理论分析与仿真验证相结合。
<think>好的,我现在需要帮助用户理解Android中线程池实现原理。根据用户提供的引用资料,特别是引用[1]、[3]和[5],我需要系统地整理这些信息,并结合自己的知识来构建一个清晰的回答。首先,我需要确认线程池的核心组成部分,比如线程池状态、初始化、任务执行机制等。 引用[1]提到了线程池状态、初始化、执行任务的方法、钩子方法、等待队列、拒绝策略、关闭和动态调整容量。这些都是线程池实现的关键点。我需要详细解释每个部分的作用,比如线程池状态如何管理线程的生命周期,任务队列如何存储待处理的任务,以及拒绝策略在任务过载时的处理方式。 引用[3]中的Worker类代码显示,线程池中的每个工作线程都是通过线程工厂创建的,并且每个Worker封装了一个Runnable任务。这里需要解释Worker的作用,如何管理线程的执行,以及如何通过状态控制来确保线程安全。 引用[5]提到线程池在Android开发中的重要性,可能涉及面试题和实际应用场景。这部分可以强调线程池在避免资源竞争、提升性能方面的优势,以及不同线程池配置的适用情况。 接下来,我需要组织这些内容,确保结构清晰,从核心类ThreadPoolExecutor开始,逐步分解各个参数和机制。可能需要比较不同类型的线程池(如FixedThreadPool、CachedThreadPool)及其适用场景,同时结合实际代码示例,如Worker的初始化过程,来增强理解。 另外,用户可能对线程池的状态转换感兴趣,比如如何从RUNNING到SHUTDOWN再到TERMINATED,需要详细说明每个状态变化的条件和影响。此外,任务队列的工作机制和拒绝策略的不同实现也是重点,需要举例说明不同策略的应用场景。 最后,生成的相关问题需要覆盖性能优化、配置选择、动态调整和对比其他线程管理方法,如Handler,引用[2]提到的RxJava和协程。这些问题可以帮助用户进一步深入学习线程池的相关知识,并理解其在现代Android开发中的位置。 整个过程中,需要确保使用正确的中文表达,LaTeX公式的格式正确,并且引用标识自然添加。回答要专业但易于理解,帮助用户逐步掌握线程池实现原理。</think>### Android线程池实现原理解析 Android中的线程池实现基于Java的`ThreadPoolExecutor`类,其核心设计通过**线程复用**、**任务队列管理**和**资源控制**提升多线程性能。以下从关键模块详细解析: --- #### 1. **线程池状态与生命周期** 线程池通过$5$种状态管理运行周期: - **RUNNING**:可接收新任务并处理队列中的任务 - **SHUTDOWN**:不接收新任务,继续处理队列中的任务 - **STOP**:不接收新任务,中断正在执行的任务 - **TIDYING**:所有任务终止,工作线程数为$0$ - **TERMINATED**:`terminated()`钩子方法执行完毕 状态转换通过**原子变量**(如`AtomicInteger`)实现,确保线程安全[^1]。 --- #### 2. **核心参数配置** `ThreadPoolExecutor`构造函数包含$4$个关键参数: - **corePoolSize**:核心线程数(即使空闲也保留) - **maximumPoolSize**:最大线程数(任务队列满时创建) - **keepAliveTime**:非核心线程空闲存活时间 - **workQueue**:任务队列(如`LinkedBlockingQueue`) 例如,`Executors.newFixedThreadPool(5)`生成的线程池等价于: $$corePoolSize=5, maximumPoolSize=5, keepAliveTime=0, workQueue=LinkedBlockingQueue$$ --- #### 3. **任务执行流程** 1. **任务提交**:调用`execute(Runnable task)` 2. **线程分配**: - 当前线程数 < corePoolSize → 创建新线程执行任务 - 线程数 ≥ corePoolSize → 任务入队 - 队列满且线程数 < maximumPoolSize → 创建非核心线程 3. **拒绝策略**:队列满且线程数达上限时触发(如直接丢弃或抛出异常) ```java public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { // 省略部分逻辑 } else if (!addWorker(command, false)) reject(command); } ``` --- #### 4. **Worker线程管理** 每个`Worker`封装一个线程和初始任务,通过`HashSet<Worker>`维护工作线程集合。关键代码如下[^3]: ```java private final class Worker extends AbstractQueuedSynchronizer implements Runnable { final Thread thread; Runnable firstTask; Worker(Runnable firstTask) { setState(-1); this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); // 创建新线程 } public void run() { runWorker(this); // 执行任务循环 } } ``` --- #### 5. **性能优化与适用场景** - **避免资源竞争**:通过队列缓冲高并发任务 - **动态调整容量**:`setCorePoolSize()`方法可运行时修改核心线程数 - **推荐配置**: - **CPU密集型任务**:核心线程数 ≈ CPU核数 - **IO密集型任务**:核心线程数可适当增加(如CPU核数 × 2) --- §§ 1. 如何根据任务类型选择线程池参数(如CPU密集型 vs IO密集型)? 2. 线程池的`keepAliveTime`参数对性能有何影响? 3. 为什么Android推荐使用线程池而非直接创建线程[^5]? 4. 如何实现自定义拒绝策略(如记录日志后重试)? 5. 对比`Handler`与线程池的优缺点,为何现代开发推荐协程或RxJava[^2]?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值