资讯专栏INFORMATION COLUMN

手撕ThreadPoolExecutor线程池源码

Corwien / 2494人阅读

摘要:所以,在时执行也是为了保证线程池在状态下必须要有一个线程来执行任务。

</>复制代码

  1. 这篇文章对ThreadPoolExecutor创建的线程池如何操作线程的生命周期通过源码的方式进行详细解析。通过对execute方法、addWorker方法、Worker类、runWorker方法、getTask方法、processWorkerExit从源码角度详细阐述,文末有彩蛋。
exexcte方法

</>复制代码

  1. public void execute(Runnable command) {
  2. if (command == null)
  3. throw new NullPointerException();
  4. int c = ctl.get();
  5. /**
  6. * workerCountOf方法取出低29位的值,表示当前活动的线程数;
  7. * 如果当前活动的线程数小于corePoolSize,则新建一个线程放入线程池中,并把该任务放到线程中
  8. */
  9. if (workerCountOf(c) < corePoolSize) {
  10. /**
  11. * addWorker中的第二个参数表示限制添加线程的数量 是根据据corePoolSize 来判断还是maximumPoolSize来判断;
  12. * 如果是ture,根据corePoolSize判断
  13. * 如果是false,根据maximumPoolSize判断
  14. */
  15. if (addWorker(command, true))
  16. return;
  17. /**
  18. * 如果添加失败,则重新获取ctl值
  19. */
  20. c = ctl.get();
  21. }
  22. /**
  23. * 如果线程池是Running状态,并且任务添加到队列中
  24. */
  25. if (isRunning(c) && workQueue.offer(command)) {
  26. //double-check,重新获取ctl的值
  27. int recheck = ctl.get();
  28. /**
  29. * 再次判断线程池的状态,如果不是运行状态,由于之前已经把command添加到阻塞队列中,这时候需要从队列中移除command
  30. * 通过handler使用拒绝策略对该任务进行处理,整个方法返回
  31. */
  32. if (!isRunning(recheck) && remove(command))
  33. reject(command);
  34. /**
  35. * 获取线程池中的有效线程数,如果数量是0,则执行addWorker方法;
  36. * 第一个参数为null,表示在线程池中创建一个线程,但不去启动
  37. * 第二个参数为false,将线程池的线程数量的上限设置为maximumPoolSize,添加线程时根据maximumPoolSize来判断
  38. */
  39. else if (workerCountOf(recheck) == 0)
  40. addWorker(null, false);
  41. /**
  42. * 执行到这里,有两种情况:
  43. * 1、线程池的状态不是RUNNING;
  44. * 2、线程池状态是RUNNING,但是workerCount >= corePoolSize, workerQueue已满
  45. * 这个时候,再次调用addWorker方法,第二个参数传false,将线程池的有限线程数量的上限设置为maximumPoolSize;
  46. * 如果失败则执行拒绝策略;
  47. */
  48. } else if (!addWorker(command, false))
  49. reject(command);
  50. }

简单来说,在执行execute()方法时如果状态一直是RUNNING时,的执行过程如下:

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

务;

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

加到该阻塞队列中;

如 果 workerCount >= corePoolSize && workerCount <

maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新
提交的任务;

如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根

据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

这里要注意一下addWorker(null, false);,也就是创建一个线程,但并没有传入任务,因为
任务已经被添加到workQueue中了,所以worker在执行的时候,会直接从workQueue中
获取任务。所以,在workerCountOf(recheck) == 0时执行addWorker(null, false);也是
为了保证线程池在RUNNING状态下必须要有一个线程来执行任务。

addWorker方法

addWorker方法的主要作用是在线程池中创建一个新的线程并执行,firstTask参数用于指定新增的线程执行的第一个任务,core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize ,false表示新增线程前需要判断当前活动的线程数是否少于maximumPoolSize

</>复制代码

  1. private boolean addWorker(Runnable firstTask, boolean core) {
  2. retry:
  3. /**
  4. * 由于线程执行过程中,各种情况都有可能处于,通过自旋的方式来保证worker的增加;
  5. */
  6. for (; ; ) {
  7. int c = ctl.get();
  8. //获取线程池运行状态
  9. int rs = runStateOf(c);
  10. /**
  11. *
  12. * 如果rs >= SHUTDOWN, 则表示此时不再接收新任务;
  13. * 接下来是三个条件 通过 && 连接,只要有一个任务不满足,就返回false
  14. * 1.rs == SHUTDOWN,表示关闭状态,不再接收提交的任务,但却可以继续处理阻塞队列中已经保存的任务;
  15. * 2.fisrtTask为空
  16. * 3.Check if queue empty only if necessary.
  17. */
  18. if (rs >= SHUTDOWN &&
  19. !(rs == SHUTDOWN &&
  20. firstTask == null &&
  21. !workQueue.isEmpty()))
  22. return false;
  23. for (; ; ) {
  24. //获取线程池的线程数
  25. int wc = workerCountOf(c);
  26. /**
  27. * 如果线程数 >= CAPACITY, 也就是ctl的低29位的最大值,则返回false
  28. * 这里的core用来判断 限制线程数量的上限是corePoolSize还是maximumPoolSize;
  29. * 如果core是ture表示根据corePoolSize来比较;
  30. * 如果core是false表示根据maximumPoolSize来比较;
  31. */
  32. if (wc >= CAPACITY ||
  33. wc >= (core ? corePoolSize : maximumPoolSize))
  34. return false;
  35. /**
  36. * 通过CAS原子的方式来增加线程数量;
  37. * 如果成功,则跳出第一个for循环;
  38. */
  39. if (compareAndIncrementWorkerCount(c))
  40. break retry;
  41. c = ctl.get(); // Re-read ctl
  42. //如果当前运行的状态不等于rs,说明线程池的状态已经改变了,则返回第一个for循环继续执行
  43. if (runStateOf(c) != rs)
  44. continue retry;
  45. // else CAS failed due to workerCount change; retry inner loop
  46. }
  47. }
  48. boolean workerStarted = false;
  49. boolean workerAdded = false;
  50. Worker w = null;
  51. try {
  52. //根据firstTask来创建Worker对象
  53. w = new Worker(firstTask);
  54. //每一个Worker对象都会创建一个线程
  55. final Thread t = w.thread;
  56. if (t != null) {
  57. //创建可重入锁
  58. final ReentrantLock mainLock = this.mainLock;
  59. mainLock.lock();
  60. try {
  61. // 获取线程池的状态
  62. int rs = runStateOf(ctl.get());
  63. /**
  64. * 线程池的状态小于SHUTDOWN,表示线程池处于RUNNING状态;
  65. * 如果rs是RUNNING状态或rs是SHUTDOWN状态并且firstTask为null,向线程池中添加线程;
  66. * 因为在SHUTDOWN状态时不会再添加新的任务,但还是处理workQueue中的任务;
  67. */
  68. if (rs < SHUTDOWN ||
  69. (rs == SHUTDOWN && firstTask == null)) {
  70. if (t.isAlive()) // precheck that t is startable
  71. throw new IllegalThreadStateException();
  72. //workers是一个hashSet
  73. workers.add(w);
  74. int s = workers.size();
  75. //largestPoolSize记录线程池中出现的最大的线程数量
  76. if (s > largestPoolSize)
  77. largestPoolSize = s;
  78. workerAdded = true;
  79. }
  80. } finally {
  81. mainLock.unlock();
  82. }
  83. if (workerAdded) {
  84. //启动线程,Worker实现了Running方法,此时会调用Worker的run方法
  85. t.start();
  86. workerStarted = true;
  87. }
  88. }
  89. } finally {
  90. if (!workerStarted)
  91. addWorkerFailed(w);
  92. }
  93. return workerStarted;
  94. }
Worker类

线程池中的每一个对象被封装成一个Worker对象,ThreadPool维护的就是一组Worker对象。
Worker类继承了AQS,并实现了Runnable接口,其中包含了两个重要属性:firstTask用来保存传入的任务,thread是在调用构造方法是通过ThreadFactory来创建的线程,是用来处理任务的线程。

</>复制代码

  1. private final class Worker
  2. extends AbstractQueuedSynchronizer
  3. implements Runnable {
  4. final Thread thread;
  5. Runnable firstTask;
  6. volatile long completedTasks;
  7. Worker(Runnable firstTask) {
  8. /**
  9. * 把state设置为-1,,阻止中断直到调用runWorker方法;
  10. * 因为AQS默认state0,如果刚创建一个Worker对象,还没有执行任务时,这时候不应该被中断
  11. */
  12. setState(-1);
  13. this.firstTask = firstTask;
  14. /**
  15. * 创建一个线程,newThread方法传入的参数是this,因为Worker本身继承了Runnable接口,也就是一个线程;
  16. * 所以一个Worker对象在启动的时候会调用Worker类中run方法
  17. */
  18. this.thread = getThreadFactory().newThread(this);
  19. }
  20. }

Worker类继承了AQS,使用AQS来实现独占锁的功能。为什么不使用ReentrantLock来实现?
可以看到tryAcquire方法,他是不允许重入的,而ReentrantLock是允许可重入的:

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

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

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

线程池中执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;

之所以设置为不可重入的,是因为在任务调用setCorePoolSize这类线程池控制的方法时,不会中断正在运行的线程

所以,Worker继承自AQS,用于判断线程是否空闲以及是否处于被中断。

</>复制代码

  1. protected boolean tryAcquire(int unused) {
  2. /**
  3. * cas修改state,不可重入;
  4. * state根据0来判断,所以Worker构造方法中讲state置为-1是为了禁止在执行任务前对线程进行中断;
  5. * 因此,在runWorker方法中会先调用Worker对象的unlock方法将state设置为0
  6. */
  7. if (compareAndSetState(0, 1)) {
  8. setExclusiveOwnerThread(Thread.currentThread());
  9. return true;
  10. }
  11. return false;
  12. }
runWorker方法

在Worker类中的run方法调用了runWorker方法来执行任务

</>复制代码

  1. final void runWorker(Worker w) {
  2. Thread wt = Thread.currentThread();
  3. //获取第一个任务
  4. Runnable task = w.firstTask;
  5. w.firstTask = null;
  6. //允许中断
  7. w.unlock();
  8. //是否因异常退出循环
  9. boolean completedAbruptly = true;
  10. try {
  11. //如果task为空,则通过getTask来获取任务
  12. while (task != null || (task = getTask()) != null) {
  13. w.lock();
  14. /**
  15. * 如果线程池正在停止,那么要保证当前线程时中断状态;
  16. * 如果不是的话,则要保证当前线程不是中断状态
  17. */
  18. if ((runStateAtLeast(ctl.get(), STOP) ||
  19. (Thread.interrupted() &&
  20. runStateAtLeast(ctl.get(), STOP))) &&
  21. !wt.isInterrupted())
  22. wt.interrupt();
  23. try {
  24. //beforeExecute和afterExecute是留给子类来实现的
  25. beforeExecute(wt, task);
  26. Throwable thrown = null;
  27. try {
  28. //通过任务方式执行,不是线程方式
  29. task.run();
  30. } catch (RuntimeException x) {
  31. thrown = x;
  32. throw x;
  33. } catch (Error x) {
  34. thrown = x;
  35. throw x;
  36. } catch (Throwable x) {
  37. thrown = x;
  38. throw new Error(x);
  39. } finally {
  40. afterExecute(task, thrown);
  41. }
  42. } finally {
  43. task = null;
  44. w.completedTasks++;
  45. w.unlock();
  46. }
  47. }
  48. completedAbruptly = false;
  49. } finally {
  50. //processWorkerExit会对completedAbruptly进行判断,表示在执行过程中是否出现异常
  51. processWorkerExit(w, completedAbruptly);
  52. }
  53. }

总结一下runWorker方法的执行过程:

while循环不断地通过getTask方法来获取任务;

getTask方法从阻塞队列中获取任务;

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

调用task.run()执行任务;

如果task为null则会跳出循环,执行processWorkerExit方法;

runWorker方法执行完毕,也代表着Worker中的run方法执行完毕,销毁线程。

getTask方法

getTask方法用于从阻塞队列中获取任务

</>复制代码

  1. private Runnable getTask() {
  2. //timeout变量的值表示上次从阻塞队列中获取任务是否超时
  3. boolean timedOut = false;
  4. for (; ; ) {
  5. int c = ctl.get();
  6. int rs = runStateOf(c);
  7. /**
  8. * 如果rs >= SHUTDOWN,表示线程池非RUNNING状态,需要再次判断:
  9. * 1、rs >= STOP ,线程池是否正在STOP
  10. * 2、阻塞队列是否为空
  11. * 满足上述条件之一,则将workCount减一,并返回null
  12. * 因为如果当前线程池的状态处于STOP及以上或队列为空,不能从阻塞队列中获取任务;
  13. */
  14. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
  15. decrementWorkerCount();
  16. return null;
  17. }
  18. int wc = workerCountOf(c);
  19. /**
  20. * timed变量用于判断是否需要进行超时控制;
  21. * allowCoreThreadTimeOut默认是false,也就是核心线程不允许进行超时;
  22. * wc > corePoolSize,表示当前线程数大于核心线程数量;
  23. * 对于超过核心线程数量的这些线程,需要进行超时控制;
  24. */
  25. boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
  26. /**
  27. * wc > maximumPoolSize的情况是因为可能在此方法执行阶段同时执行了 setMaximumPoolSize方法;
  28. * timed && timedOut 如果为true,表示当前操作需要进行超时控制,并且上次从阻塞队列中获取任务发生了超时;
  29. * 接下来判断,如果有效咸亨数量大于1,或者workQueue为空,那么将尝试workCount减1;
  30. * 如果减1失败,则返回重试;
  31. * 如果wc==1时,也就说明当前线程是线程池中的唯一线程了;
  32. */
  33. if ((wc > maximumPoolSize || (timed && timedOut))
  34. && (wc > 1 || workQueue.isEmpty())) {
  35. if (compareAndDecrementWorkerCount(c))
  36. return null;
  37. continue;
  38. }
  39. /**
  40. * timed为trure,则通过workQueue的poll方法进行超时控制,如果在keepAliveTime时间内没有获取任务,则返回null
  41. * 否则通过take方法,如果队列为空,则take方法会阻塞直到队列中不为空;
  42. */
  43. try {
  44. Runnable r = timed ?
  45. workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
  46. workQueue.take();
  47. if (r != null)
  48. return r;
  49. //如果r==null,说明已经超时了,timedOut = true;
  50. timedOut = true;
  51. } catch (InterruptedException retry) {
  52. //如果获取任务时当前线程发生了中断,则将timedOut = false;
  53. timedOut = false;
  54. }
  55. }
  56. }

注意:第二个if判断,目的是为了控制线程池的有效线程数量。
有上文分析得到,在execute方法时,如果当前线程池的线程数量超过coolPoolSize且小于maxmumPoolSize,并且阻塞队列已满时,则可以通过增加工作线程。但是如果工作线程在超时时间内没有获取到任务,timeOut=true,说明workQueue为空,也就说当前线程池不需要那么多线程来执行任务了,可以把多于的corePoolSize数量的线程销毁掉,保证线程数量在corePoolSize即可。

什么时候会销毁线程?
当然是runWorker方法执行完后,也就是Worker中的run方法执行完后,由JVM自动回收。

processWorkerExit方法

</>复制代码

  1. private void processWorkerExit(Worker w, boolean completedAbruptly) {
  2. /**
  3. * 如果completedAbruptly为true,则说明线程执行时出现异常,需要将workerCount数量减一
  4. * 如果completedAbruptly为false,说明在getTask方法中已经对workerCount进行减一,这里不用再减
  5. */
  6. if (completedAbruptly)
  7. decrementWorkerCount();
  8. final ReentrantLock mainLock = this.mainLock;
  9. mainLock.lock();
  10. try {
  11. //统计完成的任务数
  12. completedTaskCount += w.completedTasks;
  13. //从workers中移除,也就表示从线程池中移除一个工作线程
  14. workers.remove(w);
  15. } finally {
  16. mainLock.unlock();
  17. }
  18. //钩子函数,根据线程池的状态来判断是否结束线程池
  19. tryTerminate();
  20. int c = ctl.get();
  21. /**
  22. * 当前线程是RUNNING或SHUTDOWN时,如果worker是异常结束,那么会直接addWorker;
  23. * 如果allowCoreThreadTimeOut=true,那么等待队列有任务,至少保留一个worker;
  24. * 如果allowCoreThreadTimeOut=false,workerCount少于coolPoolSize
  25. */
  26. if (runStateLessThan(c, STOP)) {
  27. if (!completedAbruptly) {
  28. int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
  29. if (min == 0 && !workQueue.isEmpty())
  30. min = 1;
  31. if (workerCountOf(c) >= min)
  32. return; // replacement not needed
  33. }
  34. addWorker(null, false);
  35. }
  36. }

至此,processWorkerExit执行完之后,工作线程被销毁。

工作执行流程

工作线程的生命周期,从execute方法开始,Worker使用ThreadFactory创建新的工作线程,runWorker通过getTask获取任务,然后执行任务,如果getTask返回null,进入processWorkerExit,整个线程结束。

</>复制代码

  1. 还没关注我的公众号?

扫文末二维码关注公众号【小强的进阶之路】可领取如下:

学习资料: 1T视频教程:涵盖Javaweb前后端教学视频、机器学习/人工智能教学视频、Linux系统教程视频、雅思考试视频教程;

100多本书:包含C/C++、Java、Python三门编程语言的经典必看图书、LeetCode题解大全;

软件工具:几乎包括你在编程道路上的可能会用到的大部分软件;

项目源码:20个JavaWeb项目源码。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76287.html

相关文章

  • 线程,这一篇或许就够了

    摘要:创建方法最大线程数即源码单线程化的线程池有且仅有一个工作线程执行任务所有任务按照指定顺序执行,即遵循队列的入队出队规则创建方法源码还有一个结合了和,就不介绍了,基本不用。 *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 为什么用线程池 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率 >例如: > >记创建线程消耗时间T1,执行...

    UsherChen 评论0 收藏0
  • 使用 Executors,ThreadPoolExecutor,创建线程源码分析理解

    摘要:源码分析创建可缓冲的线程池。源码分析使用创建线程池源码分析的构造函数构造函数参数核心线程数大小,当线程数,会创建线程执行最大线程数,当线程数的时候,会把放入中保持存活时间,当线程数大于的空闲线程能保持的最大时间。 之前创建线程的时候都是用的 newCachedThreadPoo,newFixedThreadPool,newScheduledThreadPool,newSingleThr...

    Chiclaim 评论0 收藏0
  • 线程源码分析

    摘要:线程池的作用线程池能有效的处理多个线程的并发问题,避免大量的线程因为互相强占系统资源导致阻塞现象,能够有效的降低频繁创建和销毁线程对性能所带来的开销。固定的线程数由系统资源设置。线程池的排队策略与有关。线程池的状态值分别是。 线程池的作用 线程池能有效的处理多个线程的并发问题,避免大量的线程因为互相强占系统资源导致阻塞现象,能够有效的降低频繁创建和销毁线程对性能所带来的开销。 线程池的...

    enda 评论0 收藏0
  • Java线程从使用到阅读源码(3/10)

    摘要:最后,我们会通过对源代码的剖析深入了解线程池的运行过程和具体设计,真正达到知其然而知其所以然的水平。创建线程池既然线程池是一个类,那么最直接的使用方法一定是一个类的对象,例如。单线程线程池单线程线程 我们一般不会选择直接使用线程类Thread进行多线程编程,而是使用更方便的线程池来进行任务的调度和管理。线程池就像共享单车,我们只要在我们有需要的时候去获取就可以了。甚至可以说线程池更棒,...

    468122151 评论0 收藏0
  • 后端ing

    摘要:当活动线程核心线程非核心线程达到这个数值后,后续任务将会根据来进行拒绝策略处理。线程池工作原则当线程池中线程数量小于则创建线程,并处理请求。当线程池中的数量等于最大线程数时默默丢弃不能执行的新加任务,不报任何异常。 spring-cache使用记录 spring-cache的使用记录,坑点记录以及采用的解决方案 深入分析 java 线程池的实现原理 在这篇文章中,作者有条不紊的将 ja...

    roadtogeek 评论0 收藏0

发表评论

0条评论

Corwien

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<