先来看ThreadPoolExecutor的execute方法,这个方法能体现出一个Task被加入到线程池之后都发生了什么:

  1. public void execute(Runnable command) {
  2. if (command == null)
  3. throw new NullPointerException();
  4.  
  5. /* 如果运行中的worker线程数少于设定的常驻线程数,增加worker线程,把task分配给新建的worker线程 */
  6. int c = ctl.get();
  7. if (workerCountOf(c) < corePoolSize) {
  8. if (addWorker(command, true))
  9. return;
  10. c = ctl.get();
  11. }
  12.  
  13. // 如果任务可以被加入到任务队列中,即等待的任务数还在允许的范围内,
  14. // 再次检查线程池是否被关闭,如果关闭的话,则移除任务并拒绝该任务
  15. if (isRunning(c) && workQueue.offer(command)) {
  16. int recheck = ctl.get();
  17. if (! isRunning(recheck) && remove(command))
  18. reject(command);
  19. else if (workerCountOf(recheck) == 0)
  20. addWorker(null, false);
  21. }
  22.  
  23. // 如果任务数超过了现有worker线程的承受范围,尝试新建worker线程
  24. // 如果无法添加新的worker线程,则会拒绝该任务
  25. else if (!addWorker(command, false))
  26. reject(command);
  27. }

在执行任务时,需要经常检查线程池的状态,那么接下来说说线程池是如何进行状态控制的。上面的代码有个成员变量叫做ctl,它用于标记线程池状态和worker线程的数量,是一个AutomaticInteger对象。

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

ctl是一个32位的整数,最高的3位表示状态:

111为running,

000为shutdown,

001为stop,

010为tidying,

011为ternimated。

因此状态值就是这三位加上29个0,因此running的值是个负整数(最高位为1),其他状态都是正整数,后面判断状态会比较值的大小时会用到这点。

剩下的29位表示worker线程的数量(因此最大允许的线程数就是2的29方减1)。

这里是说说这几个状态的意义,这几个状态发生的顺序正好就是上面列出的顺序:

running表示正常运行状态

shutdown状态意味着发出了一个shutdown信号,类似于你点击了windows的关机按钮

stop表示shutdown信号收到,等于windows响应了这个信号,发出正在关机的信息

tidying发生在stop之后做出的响应,表示这个时候在清理一些资源,

ternimated发生在tidying完成之后,表示关闭完成。

接着来看添加一个worker线程时都发生了什么:

  1. private boolean addWorker(Runnable firstTask, boolean core) {
  2. retry:
  3. for (;;) {
  4. int c = ctl.get();
  5. int rs = runStateOf(c);
  6.  
  7. // 返回false的情况:
  8. // 1. rs>shutdown,即shutdown和running以外的状态
  9. // 2. shutdown的状态
  10. // 1)firstTask不为null,即有task分配
  11. // 2)没有task,但是workQueue(等待任务队列)为空
  12. if (rs >= SHUTDOWN &&
  13. ! (rs == SHUTDOWN &&
  14. firstTask == null &&
  15. ! workQueue.isEmpty()))
  16. return false;
  17.  
  18. for (;;) {
  19. // 1. 如果没有设定线程数的限制,worker线程数不能大于最大值(2的29次方-1)
  20. // 2. 如果是固定尺寸的线程池,不能大于固定尺寸
  21. // 3. 如果是可扩展的线程池,不能大于规定的线程数的上限
  22. int wc = workerCountOf(c);
  23. if (wc >= CAPACITY ||
  24. wc >= (core ? corePoolSize : maximumPoolSize))
  25. return false;
  26. // 用CAS操作增加线程数量,如果失败,重新循环
  27. if (compareAndIncrementWorkerCount(c))
  28. break retry;
  29. c = ctl.get(); // Re-read ctl
  30. if (runStateOf(c) != rs)
  31. continue retry;
  32. loop
  33. }
  34. }
  35.  
  36. // 新建worker线程
  37. Worker w = new Worker(firstTask);
  38. Thread t = w.thread;
  39.  
  40. final ReentrantLock mainLock = this.mainLock;
  41. mainLock.lock();
  42. try {
  43. int c = ctl.get();
  44. int rs = runStateOf(c);
  45.  
  46. // 检查以下任一状态是否出现:
  47. // 1. 创建线程失败
  48. // 2. rs>shutdown,即shutdown和running以外的状态
  49. // 3. rs==shutdown,有任务分配
  50. if (t == null ||
  51. (rs >= SHUTDOWN &&
  52. ! (rs == SHUTDOWN &&
  53. firstTask == null))) {
  54. decrementWorkerCount();
  55. tryTerminate();
  56. return false;
  57. }
  58.  
  59. workers.add(w);
  60.  
  61. int s = workers.size();
  62. if (s > largestPoolSize)
  63. largestPoolSize = s;
  64. } finally {
  65. mainLock.unlock();
  66. }
  67.  
  68. t.start();
  69. // 这里考虑一种极少出现的情况,如果worker线程调用start没有完成时,
  70. // 线程池进入Stop状态,这个时候会调用Thread#interrupt中断每个
  71. // worker线程,但是 interrupt对没有start的线程不一定起作用,这样
  72. // 就会漏掉了对这个thread的interrupt,因此在worker线程start之后
  73. // 检查以下,如果stop了,而这个线程却没有被interrupt,补上这个漏掉
  74. // 的interrupt。
  75. if (runStateOf(ctl.get()) == STOP && ! t.isInterrupted())
  76. t.interrupt();
  77.  
  78. return true;
  79. }

这篇文章主要讲线程池如何处理任务,下一篇文章将会讲worker线程是如何工作的。

《java.util.concurrent 包源码阅读》11 线程池系列之ThreadPoolExecutor 第一部分的更多相关文章

  1. 《java.util.concurrent 包源码阅读》 结束语

    <java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...

  2. 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

    这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...

  3. 《java.util.concurrent 包源码阅读》04 ConcurrentMap

    Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...

  4. 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包

    Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...

  5. 《java.util.concurrent 包源码阅读》17 信号量 Semaphore

    学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...

  6. 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue

    对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...

  7. 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇

    concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...

  8. 《java.util.concurrent 包源码阅读》22 Fork/Join框架的初体验

    JDK7引入了Fork/Join框架,所谓Fork/Join框架,个人解释:Fork分解任务成独立的子任务,用多线程去执行这些子任务,Join合并子任务的结果.这样就能使用多线程的方式来执行一个任务. ...

  9. 《java.util.concurrent 包源码阅读》24 Fork/Join框架之Work-Stealing

    仔细看了Doug Lea的那篇文章:A Java Fork/Join Framework 中关于Work-Stealing的部分,下面列出该算法的要点(基本是原文的翻译): 1. 每个Worker线程 ...

随机推荐

  1. 怎么用SQL语句备份和恢复数据库?

    BACKUP DATABASE "mydb" TO DISK ='C:\mybak.db' with init RESTORE DATABASE "mydb" ...

  2. 最佳时间买入卖出股票 Best Time to Buy and Sell Stock LeetCode

    LeetCode 我们有一个股票的数组,数组是每时间的钱,我们只能买入一次和卖出一次,求我们的最大收益. 我们知道了一个数组,那么我们可以在低价买入,然后高价卖出,但是需要知道我们的低价需要在高价之前 ...

  3. win10 uwp 切换主题

    本文主要说如何在UWP切换主题,并且如何制作主题. 一般我们的应用都要有多种颜色,一种是正常的白天颜色,一种是晚上的黑夜颜色,还需要一种辅助的高对比颜色.这是微软建议的,一般应用都要包含的颜色. 我们 ...

  4. 自学jQueryMobile之简单创建页面

    首先简答介绍一下JQueryMobile吧,我觉得用一句话来讲就是可以 "写更少的代码,做更多的事情" : 它可以通过一个灵活及简单的方式来布局网页,且兼容所有移动设备.这也是我自 ...

  5. vue 父组件传递子组件事件

    在开发中,碰到一个需要从父组件传入方法,子组件点击触发,说白了就是,把方法传入给子组件调用 <el-col v-for='data in spreadFormat.icons' class=&q ...

  6. Maven 结合 IDEA 入门实践

    一.Maven 基本安装 1. 下载 首先来到 http://maven.apache.org/download.cgi ,直接下载以 -bin.zip 结尾的文件,如图 2. 存储位置 将其解压后, ...

  7. Single linked List by pointer

    其实本应该从一般性的表讲起的,先说顺序表,再说链表 .但顺序表的应用范围不是很广,而且说白了就是数组的高级版本,他的优势仅在于两点:1.逻辑直观,易于理解.2.查找某个元素只需要常数时间--O(1), ...

  8. 解决Python2.7的UnicodeEncodeError:'ascii' codec can't encode characters in position 0-78: ordinal not in range(128)异常错误

    解决Python2.7的UnicodeEncodeError: 'ascii' codec can't encode异常错误 大家都知道,在使用python进行网络爬虫时,最头疼的就是转码问题,下面是 ...

  9. Vue源码后记-vFor列表渲染(3)

    这一节肯定能完! 经过DOM字符串的AST转化,再通过render变成vnode,最后就剩下patch到页面上了. render函数跑完应该是在这里: function mountComponent( ...

  10. Eclipse+Spring+SpringMVC+Maven+Mybatis+MySQL+Tomcat项目搭建

    ---恢复内容开始--- 1. 建表语句及插入数据 CREATE TABLE `book_user` ( user_id INT(11) NOT NULL AUTO_INCREMENT, user_n ...