【深入理解 线程池】
创始人
2024-05-30 16:24:57
0

深入理解 线程池

  • 介绍
  • 源码学习
    • 线程池的类继承体系
    • ThreadPoolExector
      • 核心数据结构
      • 核心配置参数
        • 线程池的执行流程如图:
      • 线程池的优雅关闭
        • 线程池的生命周期
        • 正确关闭线程池的步骤
      • 任务的提交过程分析
      • 任务的执行过程
        • shutdonw() 与任务执行过程综合分析
        • shutdonwNow() 与任务执行过程综合分析
  • 总结

介绍

线程池(Thread Pool) 把一个或多个线程通过统一的方式进行调度和重复使用的技术(采用池化思想)。
优点:
1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
3.提高线程的可管理性

源码学习

线程池的类继承体系

在这里插入图片描述
在类图中,有两个核心的类。ThreadPoolExectorScheduledThreadPoolExecutor。后者不仅可以执行某个任务,还可以周期性地执行任务。向线程池中提交的每个任务,都必须实现Runnable接口。通过最上面的Executor 接口中的execute(Ruunable task)向线程池提交任务。同时,在ExecutorService中,定义了线程池的关闭接口shutdown。还定义了可以有返回值的任务。也就是Callable

ThreadPoolExector

核心数据结构

public class ThreadPoolExecutor extends AbstractExecutorService {//状态变量private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//存放任务的阻塞队列private final BlockingQueue workQueue;//对线程池内部各种变量进行互斥访问控制private final ReentrantLock mainLock = new ReentrantLock();private final Condition termination = mainLock.newCondition();//线程集合private final HashSet workers = new HashSet();......}

每一个线程是一个Worker对象。Worker继承自AQS。具有锁的一些特性,对于完成线程池的关闭和执行任务起到关键作用。

 private final class Workerextends AbstractQueuedSynchronizerimplements Runnable{/*** This class will never be serialized, but we provide a* serialVersionUID to suppress a javac warning.*/private static final long serialVersionUID = 6138294804551838833L;/** Thread this worker is running in.  Null if factory fails. *///Worker 封装的线程final Thread thread;/** Initial task to run.  Possibly null. */// Worker 接受到的第一个任务Runnable firstTask;/** Per-thread task counter *///Worker 执行完毕的任务个数volatile long completedTasks;.....
}

核心配置参数

 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}

面试常考:
corePoolSize:在线程池中始终维护的线程个数。
maxPoolSize:在corePoolSize 已满,队列也满的情况下,扩充线程到该值。
keepAliveTime/TimeUnit: maxPoolSize 中的空闲线程,销毁所需要的时间。总线程回收至corePoolSize.
TimeUnit 是时间单位。
blockingQueue:线程池所用的队列类型。一定要设置队列大小。
threadFactory: 线程创建工厂,可以自定义。一般我们采用默认值。
RejectedExecutionHandler:corePoolSize 已满,队列已满,maxPoolSize已满,最后的拒绝策略。

线程池的执行流程如图:

在这里插入图片描述

首先是判断corePoolSize,其次判断blockingQueue是否已满,接着判断maxPoolSize.最后使用拒绝策略。所以 一定要设置blockingQueue的大小。不然会一直往队列中塞任务,撑爆服务器内存然后宕机。

线程池的优雅关闭

线程池的关闭对比于线程的关闭,更加复杂。 比如 当关闭一个线程池的时候,有的线程正在执行某个任务,有的调用者正在向线程池提交任务,并且队列中还有未执行的任务。因此,关闭过程不可能是立刻马上关闭,需要有一个平滑的过渡,这里就涉及线程池的完整生命周期管理。

线程池的生命周期

线程池中,把线程数量(workCount)和线程池状态(runState)这两个变量打包存储在一个字段里,即ctl变量。如下图,最高位的三位存储线程池状态,其余29位存储线程个数。在jdk6中,这两个变量是分开存储。
在这里插入图片描述

    //初始化时,线程池状态为RUNNING,线程数为0private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//最高的三位表示线程池的状态private static final int COUNT_BITS = Integer.SIZE - 3;private static final int CAPACITY   = (1 << COUNT_BITS) - 1;// runState is stored in the high-order bits//线程池的5钟状态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;// Packing and unpacking ctl//从ctl 中分别解包出runState和workerCount private static int runStateOf(int c)     { return c & ~CAPACITY; }private static int workerCountOf(int c)  { return c & CAPACITY; }//rs 即runState,wc即workerCount,两个变量打包成ctl一个变量private static int ctlOf(int rs, int wc) { return rs | wc; }

从上面的代码可以看出,ctl变量被拆成两半,最高的3位用来表示线程池的状态,低的29位表示线程的个数。线程池的状态有5种。分别是RUNNING,SHUTDOWN,STOP和TERMINATED.
5种状态的迁移过程如图:
在这里插入图片描述
线程池有两个关闭函数,shutdown()和shutdownNow(),这两个函数会让线程池切换到不同的状态。在队列为空,线程池也为空之后,进入TIDYING状态。最后执行一个钩子函数terminated(),进入TERMINATED状态。线程池才 “结束生命”。
这里的状态迁移,只能从小到大迁移,不能逆向迁移。

正确关闭线程池的步骤

通过上面的分析,我们了解到 线程池的关闭需要一个过程,在调用shutdown 或者shutdownNow之后,线程池并不会立即关闭,接下来需要调用awaitTermination来等待线程池关闭。关闭线程池的正确步骤如下:
调用完 shutdown或者 shutdownNow后。然后循环调用 awaitTermination()方法来判断线程池的状态。

executor.shutdown();
//调用shutdown()后,调用 awaitTermination
try{boolean loop=true;do{loop=!executor.awaitTermination(2,TimeUnit.SECONDS);//阻塞,直到线程池里所有的任务结束	}while(loop)
}catch(InterruptedException e){}

awaitTermination 函数
不断循环判断线程池是否达到了最终状态TERMINATED,如果达到了,就返回,如果不是,则通过termination条件变量阻塞一段时间。"苏醒"之后继续判断。

public boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException {long nanos = unit.toNanos(timeout);final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//不断循环for (;;) {//判断线程池的状态是否是TERMINATEDif (runStateAtLeast(ctl.get(), TERMINATED))return true;if (nanos <= 0)return false;//如果不是,进行阻塞nanos = termination.awaitNanos(nanos);}} finally {mainLock.unlock();}}

shutdown 和shutdownNow的区别
1.前者不会清空任务队列,会等待所有任务执行完成,后者会直接清空队列
2.前者只会中断空闲的线程,后者会中断所有的线程。

public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//判断是否有权限关闭线程池checkShutdownAccess();//设置状态为SHUTDOWNadvanceRunState(SHUTDOWN);//只中断空闲的线程interruptIdleWorkers();//钩子函数,目前是空的,为自定义的线程池个性化扩展onShutdown(); // hook for ScheduledThreadPoolExecutor} finally {mainLock.unlock();}tryTerminate();}
  public List shutdownNow() {List tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//判断是否有权限关闭线程池checkShutdownAccess();//设置状态为STOPadvanceRunState(STOP);//中断所有线程interruptWorkers();//清空队列tasks = drainQueue();} finally {mainLock.unlock();}tryTerminate();return tasks;}

继续分析下 interruptIdleWorkers()中断空闲线程和 interruptWorkers()中断所有线程

  private void interruptIdleWorkers() {interruptIdleWorkers(false);}private void interruptIdleWorkers(boolean onlyOne) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers) {Thread t = w.thread;//状态是是非interrutped,并且能拿到锁。work也是继承AQS,说明是空闲的//此时才执行interupt,中断信号if (!t.isInterrupted() && w.tryLock()) {try {t.interrupt();} catch (SecurityException ignore) {} finally {w.unlock();}}if (onlyOne)break;}} finally {mainLock.unlock();}}
private void interruptWorkers() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers)w.interruptIfStarted();} finally {mainLock.unlock();}}void interruptIfStarted() {Thread t;//只要线程启动,并且状态不是interrupted。就执行中断信号if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {try {t.interrupt();} catch (SecurityException ignore) {}}}

shutdown和shutdownNow 最后都执行了tryTerminate();
tryTerminate不会强行终止线程池,只是做了检测。当 workerCount为0,workerQueue为空时,先把状切换为TIDYING。然后调用钩子函数terminated,随后把状态改成TERMINATED。最后执行 termination.sinaAll().通知前面阻塞在awaitTermination的所有调用线程。

final void tryTerminate() {for (;;) {int c = ctl.get();if (isRunning(c) ||runStateAtLeast(c, TIDYING) ||(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))return;if (workerCountOf(c) != 0) { // Eligible to terminateinterruptIdleWorkers(ONLY_ONE);return;}//当workQueue为空,workCount为0时,才会到这final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//cas 修改状态为 TIDYINGif (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {try {//调用钩子函数terminated();} finally {//设置状态为 TERMINATEDctl.set(ctlOf(TERMINATED, 0));//唤醒所有线程--在awaitTerminationtermination.signalAll();}return;}} finally {mainLock.unlock();}// else retry on failed CAS}}

任务的提交过程分析

execute

  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的值c = ctl.get();}//到这步,说明上面步骤添加线程失败了,因为多线程的原因,此时线程数量大于核心线程数了。//如果线程池是运行的,此时尝试将任务加入到队列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);}//放入队列失败,尝试创建线程执行else if (!addWorker(command, false))//创建线程失败,说明超过最大线程池数了,采用拒绝策略reject(command);}

addWorker 此函数用于开一个新的线程,如果第二个参数core为true,则用corePoolSize作为上界,如果为false,则用maxPoolSize作为上界。

private boolean addWorker(Runnable firstTask, boolean core) {retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary.//如果线程池的状态为SHUTDOWN,说明线程池进入了关闭过程// 并且 1. 线程状态已经大于 SHUTDOWN 就直接返回 false,//      2.新加的任务 不是null.也不能加任务了,则直接返回 false//     3. 队列是空的,也不加任务了,直接返回 falseif (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;for (;;) {int wc = workerCountOf(c);if (wc >= CAPACITY ||//线程数超过了上界,则不会创建,直接返回false;wc >= (core ? corePoolSize : maximumPoolSize))return false;//worker加一,则跳出循环if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get();  // Re-read ctl//重新判断c.如果值不相等,说明有其他线程增加了进来。则需要从新循环if (runStateOf(c) != rs)continue retry;// else CAS failed due to workerCount change; retry inner loop}}//到这里说明worker加1了,后面的逻辑就是具体实例化一个worker.boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {//进行初始化w = new Worker(firstTask);final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// Recheck while holding lock.// Back out on ThreadFactory failure or if// shut down before lock acquired.int rs = runStateOf(ctl.get());//再次校验rs 的值。即线程池的状态//线程池的状态还是 RUNNING,//如果是 SHUTDOWN.可以加null任务if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {if (t.isAlive()) // precheck that t is startable//线程还没启动,此时是alive().则抛出异常throw new IllegalThreadStateException();//把线程加入到线程集合workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {//此时才启动该线程t.start();workerStarted = true;}}} finally {if (! workerStarted)//如果加入线程失败。则把workerCount减一addWorkerFailed(w);}return workerStarted;}

任务的执行过程

在上面的任务提交过程中,可能会开启一个新的Worker,并把任务本身作为firstTask赋给该Worker。但对于一个Worker来说,不是只执行一个任务,而是源源不断地从队列中取任务执行,这是一个不断循环的过程。

    private final class Workerextends AbstractQueuedSynchronizerimplements Runnable{/*** This class will never be serialized, but we provide a* serialVersionUID to suppress a javac warning.*/private static final long serialVersionUID = 6138294804551838833L;/** Thread this worker is running in.  Null if factory fails. *///对于的线程final Thread thread;/** Initial task to run.  Possibly null. *///对于的任务Runnable firstTask;/** Per-thread task counter *///统计线程完成的任务数量volatile long completedTasks;/*** Creates with given first task and thread from ThreadFactory.* @param firstTask the first task (null if none)*/Worker(Runnable firstTask) {//初始值设置的是-1setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;this.thread = getThreadFactory().newThread(this);}/** Delegates main run loop to outer runWorker  */public void run() {//调用的是ThreadPoolExecutor 的runWorker方法runWorker(this);}}

runWorker

 //运行的任务有两种类型 一种是线程数不到核心线程数或者队列满了线程数不到最大线程数,直接new Work执行
// 从队列中取final void runWorker(Worker w) {Thread wt = Thread.currentThread();// 从worker中取的任务Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {//不断循环从队列中获取任务执行//task 首先从work中取,取不到然后再从队列中取(getTask())while (task != null || (task = getTask()) != null) {//执行任务先加锁。此处对应了shutdown 来判断线程是否是空闲时的操作tryLock.//如果能获取到锁,说明没有任务执行,线程是空闲的。w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted.  This// requires a recheck in second case to deal with// shutdownNow race while clearing interrupt//对线程池和线程状态的校验判断,if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())//不符合就interupt,给自己发送中断信号wt.interrupt();try {//任务之前的钩子函数,目前是空beforeExecute(wt, task);Throwable thrown = null;try {//执行任务task.run();} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {//任务执行完成后的钩子函数,目前是空afterExecute(task, thrown);}} finally {task = null;//完成任务数加1w.completedTasks++;//释放锁w.unlock();}}//判断是正常退出还是异常退出,用于finally里面里面进行worker退出的逻辑处理completedAbruptly = false;} finally {//worker退出processWorkerExit(w, completedAbruptly);}}

shutdonw() 与任务执行过程综合分析

把任务的执行过程和上面的线程池的关闭过程结合起来进行分析。当调用shutdown()的时候,可能会出现几种情况
场景一:
当调用shutdown时候,所有线程都处于空闲状态。
这意味着任务队列一定是空的。此时,所有线程都会阻塞在getTask()的地方,然后,所有线程都会收到interruptIdleWorkers()发过来的中断信号。getTask()会响应中断,然后返回null.所有Worker都会退出while循环,然后执行processWorkerExit;

场景二:
当调用shutdown时,所有线程都处于忙碌状态
此时队列可能为空,也可能是非空的,interruptIdleWorkers()内部的tryLock调用失败。什么都不会做,直至队列任务为空。interruptIdleWorkers()内部的tryLock调用成功。此时和场景一一样。

场景三:
当调用shutdown时,部分线程忙碌,部分线程空闲。
有部分线程空闲,说明队列是空的。忙碌的线程按照场景一处理,不忙碌的线程按照场景一处理。

getTask()函数

private Runnable getTask() {boolean timedOut = false; // Did the last poll() time out?for (;;) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary.//rs >= SHUTDOWN 说明调用了shutdown。// 并且队列为空 或者 rs >= STOP 调用了shutdownNow ,则返回null if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}int wc = workerCountOf(c);// Are workers subject to culling?boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))return null;continue;}try {//1.队列为空,就会阻塞在此处的poll或者take。poll 带超时时间。take不带超时时间。//2.一旦收到中断信号,就会进入catch代码,设置 timedOut=false;Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r != null)return r;timedOut = true;} catch (InterruptedException retry) {//可以响应 interruptIdleWorkers 发出的中断信号 timedOut = false;}}}

shutdonwNow() 与任务执行过程综合分析

shutdownNow()比较粗暴。和shutdown()相比,多了一个清除队列的操作。少了一些判断而已。

总结

整体来讲,线程池的设计还是比较复杂的。本文首先从整体架构来介绍了线程池,然后逐步从线程池的提交执行和关闭等操作进行了源码分析。希望能对大家理解线程池有所帮助。

相关内容

热门资讯

你的晚安是我的早安是什么歌曲 你的晚安是我的早安是什么歌曲苏天伦《你的早安是我的晚安》“你的晚安是我的早安”是单小源的歌曲《东京遇...
积极进取的小故事 积极进取的小故事现代的普通人,不要名人的。不能与别人重复,尽快回答   啄木鸟的故事       啄...
熊出没之探险日记3什么时候播出... 熊出没之探险日记3什么时候播出?熊出没之探险日记3,春节前播放。熊出没只是探险日记三2020年5月4...
谁知道所有有关“七”的歌?拜托... 谁知道所有有关“七”的歌?拜托了各位 谢谢就是歌曲名里有“七”这个字的!谢谢七月七迅谈日晴 看我七十...
求一本小说 女主穿越了三次 每... 求一本小说 女主穿越了三次 每次都在福临身边 后来怀孕了孩子被打掉了那个 女主叫什么雯?那个女主就是...
如果记忆不说话,流年也会开出花... 如果记忆不说话,流年也会开出花的基本信息书 名:《如果记忆不弯饥好说话,流年也会开出花》埋铅 作 者...
你好,旧时光漫画版在哪里可以看... 你好,旧时光漫画版在哪里可以看?暂时在绘心上连载
一首英文歌,男的组合唱的,MV... 一首英文歌,男的组合唱的,MV是一个婚礼的过程。求歌名。是不是darin的can'tstoplove...
为什么很多人喜欢用胶片相机? 为什么很多人喜欢用胶片相机?有一种情怀叫做“怀旧“吧,现在数码相机越来越普遍了,已经到了”全民摄影“...
女主先爱上男主,男主却不喜欢女... 女主先爱上男主,男主却不喜欢女主或者是另有所爱,最后女主男主还是在一起的穿越小说。有木有再生缘:我的...
爱情失恋伤感句子 爱情失恋伤感句子越是美好的从前,越幸福的曾经,现在只能带来锥心的疼痛,痛到撕心裂肺,肝肠寸断,终于痛...
24岁穿这个会不会显老 24岁穿这个会不会显老有点显老,这个颜色款式,颜色有点暗,没有活力,属于那种气质佳,长得高雅的女人,...
哈尔的移动城堡英语版 哈尔的移动城堡英语版可以发给我吗度盘~请查收~
秦时明月之万里长城什么时候播 秦时明月之万里长城什么时候播据说是今年暑假开播别急,官网什么的信他你就输了,12年之前底应该会出,杭...
孩子会得抽动症吗? 孩子会得抽动症吗?我天生的气性比较大,有时跟别人斗嘴时候就会手脚哆嗦,麻木,我问一下这是不是抽动症就...
亨德尔一生为音乐献出了怎样的贡... 亨德尔一生为音乐献出了怎样的贡献?亨德尔一生写了歌剧41部,清唱剧21部,以及大量的管乐器与弦乐器的...
礼仪起源和发展的经典故事? 礼仪起源和发展的经典故事?一、礼仪的起源;1、天神生礼仪;2、礼为天地人的统一体;3、礼产生于人的自...
描写桂林山水的句子有哪些? 描写桂林山水的句子有哪些?天下风光数桂林有杨万里的“梅花五岭八桂林,青罗带绕碧玉簪”;有邹应龙的“无...
避免与强敌正面对决的成语 避免与强敌正面对决的成语避免与强敌正面对决的成语避实就虚 【近义】避重就轻、避难就易、声东击西【反义...
多愁善感类的成语 多愁善感类的成语心细如发【解释】:极言小心谨慎,考虑周密。亦作“心细于发”。【出自】:吴梅《题天香石...