通过线程池源码分析其运行机制

欢迎关注个人技术公众号:htzhanTech

由于项目原因,近期使用多线程较为频繁,因此对多线程产生了浓厚的兴趣,也一直想弄懂它的原理,但每次都是浅尝而止。
决定提笔写这文章源于 前两天code review的时候,组里的一位同事无意间提了一个问题:
线程池中,如果一个线程中的任务执行过程中,抛了异常,会发生什么?线程池中的线程个数会因此变少吗?

思考这个问题的过程中,我意识到,是时候对线程池知识做一个总结了。

接着往下看之前,也希望各位可以花个几分钟自己在自己脑子里好好思考下这个问题,然后带着问题往下接着走。
接下来,本文将结合JDK1.8的ThreadPoolExecutor类源码,从如下几个方面对线程池进行详细的阐述:
  1. 线程池的重要性
  2. 线程池整体工作示意图
  3. 线程池的创建
  4. 任务的提交
  5. 总结
一: 线程池的重要性
我记得李智慧老师的《大型网站技术架构》一书中说到,提高性能和可用性有三驾马车,分别是:缓存,异步和集群。而线程池正是异步的常用手段,
他合适使得主逻辑和附加逻辑进行解耦,能提高响应速度,提高性能,还能在高并发情况下达到削峰的作用。
在如今多核处理器的时代,多线程开发发挥着巨大的作用,总的来说,我认为它至少有如下三个明细优势:
     (1) 降低cpu等资源消耗:通过对线程的重用,避免不断的创建和销毁线程占用大量的cpu等资源
     (2) 提高速度和性能:利用多线程进行异步,并行 处理任务
     (3) 简单方便:线程池统一进行线程的管理,调度

二: 线程池整体工作示意图
该示意图描述了线程池的整个运行过程:
(1) 通过submit(Callable<T> task ) 或者 execute(Runnable command)方法提交任务,submit方法其实最后也是调用的execute方法,只是调用前将任务封装成了FutureTask,它会将任务运行的结果或者 任务抛出的异常封装进 Future中,返回给调用方,这样主线程就能拿到返回值或者异常信息了。
(2) 如果线程池中线程个数小于corePoolSize,则新建一个线程用来执行这个任务。
线程池,其实说白了就是一个装有Worker的HashSet,Worker是线程池中的一个内部类,它有两个重要属性:线程和任务
线程池新建线程其实就是new 一个Worker然后加进Set中,这个Worker会新建一个线程,并在此线程中,进行任务的处理。
(3) 如果线程个数达到 corePoolSize时,任务则会被放进队列进行排队
(4) 如果 队列也放满了,则会继续新建Worker,直到个数达到maxPoolSize
(5) 如果 线程个数已经达到max PoolSize,并且队列也满了,则会走拒绝策略,常见策略包括:主线程执行(CallerRunsPolicy),抛异常(AbortPolicy)和丢弃(DiscardPplicy)三种

三: 线程池的创建
可以通过ThreadPoolExecutor的构造方法直接创建线程池,更常见的一般是使用Executors中的工厂方法进行创建。
如果项目使用的spring框架,建议使用ThreadPoolTaskExecutor类,然后corePoolSize, maxPoolSize,queueCapacity和拒绝策略通过配置进行合理的配置。
orePoolSize, maxPoolSize和queueCapacity大小怎么设置,可以参考本期的另外一篇文章。


四: 任务的提交

前面介绍了,submit方法最后也是调用的execute方法,只是将任务进行了适当的包装处理,因此这里详细介绍execute方法。根据章节二的描述可知,提交任务无非就是往Set添加新的Worker,要么是任务入队列排队,或者进行拒绝策略。下面从execute方法开始结合源码进行详细的介绍:
execute作为提交任务的方法,要么新建并添加线程(addWorker),要么入队列(workQueue.offer()) ,或者走拒绝策略。

  1. public void execute(Runnable command) {
  2. if (command == null)
  3. throw new NullPointerException();
  4. /**
  5. * ct1是个AtomicInteger类型,int是32位的
  6. * 前3位用于表示线程池的状态,后29位表示Worker(线程池中线程)的数量
  7. * 因此最大的线程个数为2的29次方减一
  8. */
  9. int c = ctl.get();
  10. /**
  11. * 这块代码主要就是,如果当前线程个数小于corePoolSize
  12. * 就addWorker,新创建一个worker并加入Set<Worker>
  13. */
  14. if (workerCountOf(c) < corePoolSize) {
  15. if (addWorker(command, true))
  16. return;
  17. c = ctl.get();
  18. }
  19. /**
  20. * 这块代码是当前线程个数大于等于corePoolSize或者上面addWorker失败
  21. * 然后进行入队列的操作
  22. * 入队列成功后,如果线程池状态突变了,则会出队并拒绝这个任务
  23. * 接下来会再次检查线程池的线程个数
  24. * 如果为0,那么就没线程执行队列等待的任务了,会addWorker,新建一个
  25. */
  26. if (isRunning(c) && workQueue.offer(command)) {
  27. int recheck = ctl.get();
  28. if (! isRunning(recheck) && remove(command))
  29. reject(command);
  30. else if (workerCountOf(recheck) == 0)
  31. addWorker(null, false);
  32. }
  33. /**
  34. * 走到这,说明队列满了,会再次addWorker(直到maxPoolSize)
  35. * 如果失败,则走拒绝策略拒绝这个任务
  36. */
  37. else if (!addWorker(command, false))
  38. reject(command);
  39. }

execute方法里面的核心,是addWorker,下面看源码:

  1. private boolean addWorker(Runnable firstTask, boolean core) {
  2. // goto用法的label,用来方便的跳出双层循环
  3. retry:
  4. /**
  5. * 这快代码主要是校验线程池状态和线程个数
  6. * 判断是否还能添加Worker
  7. */
  8. for (;;) {
  9. int c = ctl.get();
  10. int rs = runStateOf(c);
  11. /**
  12. * 这块代码比较绕,读懂的关键在于把握 &&运算符的短路特性
  13. * 主要功能是看什么情况下会返回false,即addWorker失败
  14. *
  15. * 首先介绍下,线程池包括如下状态:
  16. * RUNNING:可以新加线程,同时可以处理queue中的线程
  17. * SHUTDOWN:不增加新线程,但是处理queue中的线程
  18. * STOP:不增加新线程,但是处理queue中的线程
  19. * TIDYING:所有的线程都终止了(queue中),同时workerCount为0,那么此时进入TIDYING
  20. * TERMINATED:terminated()方法结束,变为TERMINATED
  21. *
  22. * 分析可知,addWorker失败有如下情况:
  23. * 1. rs>SHUTDOWN
  24. * 2. rs=SHUTDOWN,firstTask != null,因为shutDown状态下不接受新线程,只处理queue中的任务
  25. * 3. rs=SHUTDOWN,firstTask = null,queue为空:此时说明是新增线程用来消耗queue中的任务,但
  26. * queue为空,所以也返回失败
  27. */
  28. if (rs >= SHUTDOWN &&
  29. ! (rs == SHUTDOWN &&
  30. firstTask == null &&
  31. ! workQueue.isEmpty()))
  32. return false;
  33. /**
  34. * 上面关注的是线程池的状态,这块代码关注的则是worker(即线程)的数量
  35. */
  36. for (;;) {
  37. int wc = workerCountOf(c);
  38. /**
  39. * capacity为线程的最大个数,受int的29位限制,大于此值直接返回失败
  40. * core表示的是以corePoolSize还是maximumPoolSize为上限
  41. * 最初始阶段,线程是逐渐增加至corePoolSize的,core为true
  42. * 后面,对列也满了后,就也maximumPoolSize为上限了,core为false
  43. */
  44. if (wc >= CAPACITY ||
  45. wc >= (core ? corePoolSize : maximumPoolSize))
  46. return false;
  47. // workerCount加1成功后,跳出循环
  48. if (compareAndIncrementWorkerCount(c))
  49. break retry;
  50. c = ctl.get(); // Re-read ctl
  51. if (runStateOf(c) != rs)
  52. continue retry;
  53. }
  54. }
  55. /**
  56. * 接下来的这块代码,就是新建线程并且维护进set中
  57. */
  58. boolean workerStarted = false;
  59. boolean workerAdded = false;
  60. Worker w = null;
  61. try {
  62. // Worker实现了Runnable接口,内部维护了线程和任务两个重要属性,关键是它的run()方法
  63. w = new Worker(firstTask);
  64. final Thread t = w.thread;
  65. if (t != null) {
  66. // HashSet是线程不安全的,所以操作要加锁
  67. final ReentrantLock mainLock = this.mainLock;
  68. mainLock.lock();
  69. try {
  70. int rs = runStateOf(ctl.get());
  71. /**
  72. * rs < SHUTDOWN(即running):可以正常添加线程
  73. * rs == SHUTDOWN && firstTask == null:此时添加的是消耗queue中任务的线程
  74. */
  75. if (rs < SHUTDOWN ||
  76. (rs == SHUTDOWN && firstTask == null)) {
  77. if (t.isAlive()) // precheck that t is startable
  78. throw new IllegalThreadStateException();
  79. workers.add(w);
  80. int s = workers.size();
  81. if (s > largestPoolSize)
  82. largestPoolSize = s;
  83. workerAdded = true;
  84. }
  85. } finally {
  86. mainLock.unlock();
  87. }
  88. /**
  89. * 添加成功后,启动新worker中的线程
  90. * 拿到时间片后,会执行Worker中的runWorker()方法
  91. */
  92. if (workerAdded) {
  93. t.start();
  94. workerStarted = true;
  95. }
  96. }
  97. } finally {
  98. if (! workerStarted)
  99. addWorkerFailed(w);
  100. }
  101. return workerStarted;
  102. }
到目前为止,就算是往线程池添加线程成功了,接下来就是线程执行任务。
Worker创建成功后,先执行firstTask,之后工作就是不断的去队列拿任务执行
runWorker()方法:

  1. final void runWorker(Worker w) {
  2. // 获取当前线程,worker的构造方法会通过线程工厂方法新建一个线程
  3. Thread wt = Thread.currentThread();
  4. // 刚创建worker的firstTask才可能非null,创建用来消耗queue中的任务的firstTask都是给的null
  5. Runnable task = w.firstTask;
  6. w.firstTask = null;
  7. w.unlock();
  8. boolean completedAbruptly = true;
  9. try {
  10. // task不为null执行task,否者通过getTask()去队列拿
  11. while (task != null || (task = getTask()) != null) {
  12. w.lock();
  13. // 再次判断状态
  14. if ((runStateAtLeast(ctl.get(), STOP) ||
  15. (Thread.interrupted() &&
  16. runStateAtLeast(ctl.get(), STOP))) &&
  17. !wt.isInterrupted())
  18. wt.interrupt();
  19. try {
  20. // 线程池提供的一个钩子方法
  21. beforeExecute(wt, task);
  22. Throwable thrown = null;
  23. try {
  24. // 执行任务
  25. task.run();
  26. } catch (RuntimeException x) {
  27. thrown = x; throw x;
  28. } catch (Error x) {
  29. thrown = x; throw x;
  30. } catch (Throwable x) {
  31. thrown = x; throw new Error(x);
  32. } finally {
  33. afterExecute(task, thrown);
  34. }
  35. } finally {
  36. task = null;
  37. w.completedTasks++;
  38. w.unlock();
  39. }
  40. }
  41. /** completedAbruptly默认是true,只有当线程正常跳出循环
  42. * 即队列任务此时刻没有了任务,才会为false
  43. * 异常退出情况,依然是默认值false
  44. */
  45. completedAbruptly = false;
  46. } finally {
  47. /**
  48. * 注意这里,这个方法实现就是文章开头问题的答案所在
  49. * 如果线程执行任务过程中抛了异常,会发生什么
  50. * 注意入参 completedAbruptly
  51. */
  52. processWorkerExit(w, completedAbruptly);
  53. }
  54. }

runWorker()方法比较简单,关键在于去队列拿任务的getTask()方法,和线程异常关闭处理的 processWorkerExit方法,下面分别进行阐述:

getTask()方法:

  1. private Runnable getTask() {
  2. // 判断最后的poll是否要超时,用于考量当前的线程个数是否富余
  3. boolean timedOut = false; // Did the last poll() time out?
  4. for (;;) {
  5. int c = ctl.get();
  6. int rs = runStateOf(c);
  7. /**
  8. * 如果rs=shutdown,queue为空:减少workerCount并返回null
  9. * 如果rs>=stop:同样减少workerCount并返回null
  10. */
  11. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
  12. decrementWorkerCount();
  13. return null;
  14. }
  15. int wc = workerCountOf(c);
  16. /**
  17. * 允许核心线程超时退出 或者 当前worker个数 > corePoolSize时
  18. * timed = true, 表示当前线程允许退出
  19. */
  20. boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
  21. /**
  22. * 这里关键在于分析if里面什么时候为true
  23. * (1): wc > maximumPoolSize, 为true, 减少线程数,返回null
  24. * (2): 1 < wc <= maximumPoolSize,里面简化为 timed && timedOut
  25. * 允许线程退出,并且当前线程富余,则减少线程
  26. * (3): wc <= 1, 里面简化为:timed && timedOut && workQueue.isEmpty()
  27. * 此时只有一个工作线程,只要队列不为空,就得一直处理,不能减小线程数
  28. */
  29. if ((wc > maximumPoolSize || (timed && timedOut))
  30. && (wc > 1 || workQueue.isEmpty())) {
  31. if (compareAndDecrementWorkerCount(c))
  32. return null;
  33. continue;
  34. }
  35. try {
  36. /**
  37. * 1. timed为true,当前线程允许退出,通过poll从队列拿
  38. * 拿到直接返回任务,如果超时时间内没拿到,说明任务不多,但线程多
  39. * 即worker有富余,因此timedOu 置为 true,上面就能减少线程数了
  40. * 2. timed为false,不允许当前线程退出,拿不到任务一直阻塞
  41. */
  42. Runnable r = timed ?
  43. workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
  44. workQueue.take();
  45. if (r != null)
  46. return r;
  47. timedOut = true;
  48. } catch (InterruptedException retry) {
  49. timedOut = false;
  50. }
  51. }
  52. }
processWorkerExit()方法:
  1. private void processWorkerExit(Worker w, boolean completedAbruptly) {
  2. // 先把异常的那个线程个数减掉
  3. if (completedAbruptly)
  4. decrementWorkerCount();
  5. final ReentrantLock mainLock = this.mainLock;
  6. mainLock.lock();
  7. try {
  8. // 任务抛异常了,线程池也任务此线程完成了它的工作
  9. completedTaskCount += w.completedTasks;
  10. workers.remove(w);
  11. } finally {
  12. mainLock.unlock();
  13. }
  14. tryTerminate();
  15. int c = ctl.get();
  16. if (runStateLessThan(c, STOP)) {
  17. /**
  18. * 如果线程异常退出,completedAbruptly为true,不会进if,直接走后面的addWorker添加一个新的线程
  19. * 如果线程正常跳出runWorker方法里面的那个循环到这的话,进if里面代码块,
  20. * 根据情况判断是否需要添加线程
  21. */
  22. if (!completedAbruptly) {
  23. /**
  24. * 关键在这里
  25. * 如果允许核心线程超时退出,并且队列不为空,有任务需要处理,则 min = 1
  26. * 如果不允许核心线程超时退出,min = corePoolSize
  27. *
  28. * 如果当前的线程数小于min,则新建一个线程进行补位
  29. */
  30. int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
  31. if (min == 0 && ! workQueue.isEmpty())
  32. min = 1;
  33. if (workerCountOf(c) >= min)
  34. return;
  35. }
  36. addWorker(null, false);
  37. }
  38. }

五: 总结

至此,关于线程池原理就介绍完了,下面进行简单的总结。
线程池,说白了就是一个 Set<Worker>,Worker是线程池的内部类,它实现了Runnable接口,维护了线程和任务,当新建一个Worker时,它的构造方法内会新创建一个线程用来执行任务,他首先条件入参传进来的任务,执行完后,就不管的从workQueue中不断的通过getTask()从队列获取任务来执行。











猜你喜欢

转载自blog.csdn.net/zhanht/article/details/79544233