线程池实现的原理

本文介绍了线程池的三大优点,包括线程复用、控制最大并发数和管理线程。线程复用通过保持线程存活状态实现,借助ThreadPoolExecutor的Worker类;控制最大并发数依据corePoolSize和maximumPoolSize;管理线程则通过ctl变量和shutdown、shutdownNow方法。

线程池的优点是可总结为以下三点:

  1. 线程复用
  2. 控制最大并发数
  3. 管理线程

1.线程复用过程

理解线程复用原理首先应了解线程生命周期。

在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。

Thread通过new来新建一个线程,这个过程是是初始化一些线程信息,如线程名,id,线程所属group等,可以认为只是个普通的对象。调用Thread的start()后Java虚拟机会为其创建方法调用栈和程序计数器,同时将hasBeenStarted为true,之后调用start方法就会有异常。

处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。当线程获取cpu后,run()方法会被调用。不要自己去调用Thread的run()方法。之后根据CPU的调度在就绪——运行——阻塞间切换,直到run()方法结束或其他方式停止线程,进入dead状态。

所以实现线程复用的原理应该就是要保持线程处于存活状态(就绪,运行或阻塞)。接下来来看下ThreadPoolExecutor是怎么实现线程复用的。

在ThreadPoolExecutor主要Worker类来控制线程的复用。看下Worker类简化后的代码,这样方便理解:

private final class Worker 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){
		task.run();
	}
}

 

Worker是一个Runnable,同时拥有一个thread,这个thread就是要开启的线程,在新建Worker对象时同时新建一个Thread对象,同时将Worker自己作为参数传入TThread,这样当Thread的start()方法调用时,运行的实际上是Worker的run()方法,接着到runWorker()中,有个while循环,一直从getTask()里得到Runnable对象,顺序执行。getTask()又是怎么得到Runnable对象的呢?

依旧是简化后的代码:

private Runnable getTask() {
    if(一些特殊情况) {
        return null;
    }

    Runnable r = workQueue.take();

    return r;
}

这个workQueue就是初始化ThreadPoolExecutor时存放任务的BlockingQueue队列,这个队列里的存放的都是将要执行的Runnable任务。因为BlockingQueue是个阻塞队列,BlockingQueue.take()得到如果是空,则进入等待状态直到BlockingQueue有新的对象被加入时唤醒阻塞的线程。所以一般情况Thread的run()方法就不会结束,而是不断执行从workQueue里的Runnable任务,这就达到了线程复用的原理了。

2.控制最大并发数

那Runnable是什么时候放入workQueue?Worker又是什么时候创建,Worker里的Thread的又是什么时候调用start()开启新线程来执行Worker的run()方法的呢?有上面的分析看出Worker里的runWorker()执行任务时是一个接一个,串行进行的,那并发是怎么体现的呢?

很容易想到是在execute(Runnable runnable)时会做上面的一些任务。看下execute里是怎么做的。

execute:

简化后的代码

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

     int c = ctl.get();
    // 当前线程数 < corePoolSize
    if (workerCountOf(c) < corePoolSize) {
        // 直接启动新的线程。
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }

    // 活动线程数 >= corePoolSize
    // runState为RUNNING && 队列未满
    // workQueue.offer(command)表示添加到队列,如果添加成功返回true,否则返回false
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 再次检验是否为RUNNING状态
        // 非RUNNING状态 则从workQueue中移除任务并拒绝
        if (!isRunning(recheck) && remove(command))
            reject(command);// 采用线程池指定的策略拒绝任务
        // 两种情况:
        // 1.非RUNNING状态拒绝新的任务
        // 2.队列满了启动新的线程失败(workCount > maximumPoolSize)
    } else if (!addWorker(command, false))
        reject(command);
}

addWorker:

简化后的代码

private boolean addWorker(Runnable firstTask, boolean core) {

    int wc = workerCountOf(c);
    if (wc >= (core ? corePoolSize : maximumPoolSize)) {
        return false;
    }

    w = new Worker(firstTask);
    final Thread t = w.thread;
    t.start();
}

根据代码再来看上面提到的线程池工作过程中的添加任务的情况:

  • 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
  • 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
  • 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
  • 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常RejectExecutionException。

这就是Android的AsyncTask在并行执行是在超出最大任务数是抛出RejectExecutionException的原因所在,详见基于最新版本的AsyncTask源码解读及AsyncTask的黑暗面

通过addWorker如果成功创建新的线程成功,则通过start()开启新线程,同时将firstTask作为这个Worker里的run()中执行的第一个任务。

虽然每个Worker的任务是串行处理,但如果创建了多个Worker,因为共用一个workQueue,所以就会并行处理了。

所以根据corePoolSize和maximumPoolSize来控制最大并发数。大致过程可用下图表示。

上面的讲解和图来可以很好的理解的这个过程。

如果是做Android开发的,并且对Handler原理比较熟悉,你可能会觉得这个图挺熟悉,其中的一些过程和Handler,Looper,Meaasge使用中,很相似。Handler.send(Message)相当于execute(Runnuble),Looper中维护的Meaasge队列相当于BlockingQueue,只不过需要自己通过同步来维护这个队列,Looper中的loop()函数循环从Meaasge队列取Meaasge和Worker中的runWork()不断从BlockingQueue取Runnable是同样的道理。

3.管理线程

通过线程池可以很好的管理线程的复用,控制并发数,以及销毁等过程,线程的复用和控制并发上面已经讲了,而线程的管理过程已经穿插在其中了,也很好理解。

在ThreadPoolExecutor有个ctl的AtomicInteger变量。通过这一个变量保存了两个内容:

  • 所有线程的数量
  • 每个线程所处的状态

其中低29位存线程数,高3位存runState,通过位运算来得到不同的值。

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

//得到线程的状态
private static int runStateOf(int c) {
    return c & ~CAPACITY;
}


//得到Worker的的数量
private static int workerCountOf(int c) {
    return c & CAPACITY;
}


// 判断线程是否在运行
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

 

这里主要通过shutdown和shutdownNow()来分析线程池的关闭过程。首先线程池有五种状态来控制任务添加与执行。主要介绍以下三种:

  • RUNNING状态:线程池正常运行,可以接受新的任务并处理队列中的任务;
  • SHUTDOWN状态:不再接受新的任务,但是会执行队列中的任务;
  • STOP状态:不再接受新任务,不处理队列中的任务

shutdown这个方法会将runState置为SHUTDOWN,会终止所有空闲的线程,而仍在工作的线程不受影响,所以队列中的任务人会被执行。shutdownNow方法将runState置为STOP。和shutdown方法的区别,这个方法会终止所有的线程,所以队列中的任务也不会被执行了。

原文地址

<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]?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值