ThreadPoolExecutor.execute()源代码提供了大量凝视来解释该方法的设计考虑。以下的源代码来自jdk1.6.0_37

  1. public void execute(Runnable command) {
  2. if (command == null)
  3. throw new NullPointerException();
  4. if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
  5. if (runState == RUNNING && workQueue.offer(command)) {
  6. if (runState != RUNNING || poolSize == 0)
  7. ensureQueuedTaskHandled(command);
  8. }
  9. else if (!addIfUnderMaximumPoolSize(command))
  10. reject(command); // is shutdown or saturated
  11. }
  12. }

使用这么多if-else就是为了性能考虑,减小锁的使用范围,避免execute方法整个运行过程中都持有mainLock锁。可以看到仅仅有调用addIfUnderCorePoolSize、ensureQueuedTaskHandled、addIfUnderMaximumPoolSize这3个方法才须要持有锁。假设新提交的任务。不会进入这3个方法。那么就不须要持有锁。我们来看下。execute方法的设计是否可以有效地降低进入这3个方法的次数,实现快进快出。

第4行代码poolSize >= corePoolSize。为什么要加这个推断呢?

假设通过prestartAllCoreThreads事先启动了全部核心线程,或者是提交的任务已经让当前大小poolSize达到了核心大小 corePoolSize,而且核心线程不会死亡(allowCoreThreadTimeOut=false不同意核心线程超时退出。而且任务运行过程没有抛出异常导致线程退出),那么线程池中的线程数一定不会比corePoolSize小。此后假设再提交新任务。那么就不会进入addIfUnderCorePoolSize方法。poolSize
>= corePoolSize就是为了降低进入addIfUnderCorePoolSize函数的次数,降低锁的获取和释放次数
。假设poolSize<corePoolSize,那么就会进入addIfUnderCorePoolSize。该方法会在加锁情况下。推断线程池的状态和当前大小,然后决定是否须要新加线程来处理任务。因为addIfUnderCorePoolSize会持有mainLock锁,所以能够防止其它线程对线程池的并发改动。也就说能够保证:在不须要加入新线程的时候,就不会加入。The
call to addIfUnderCorePoolSize rechecks runState and pool size under lock (they change only under lock) so prevents false alarms that would add threads when it shouldn't。源代码中这段凝视,说的就是这个效果。

第5行代码if (runState == RUNNING && workQueue.offer(command)),假设程序能走到这行代码。从任务提交者的角度来看,此时线程池大小已经达到了核心大小(尽管事实情况不一定如此,由于没有加锁,存在并发改动的可能。并且线程池中的线程也可能会死亡。假设满足条件:

runState == RUNNING && workQueue.offer(command) 这意味着:线程池仍然处于执行状态,而且任务排队已经成功。

为什么程序执行到这里依旧不能结束,还是须要走之后的代码呢?由于没有加锁,推断条件不一定可靠

考虑以下2个场景:假设任务刚開始调用offer(还没有成功地插入到堵塞队列中)。线程池中的线程所有死亡,那么就没有线程来处理当前提交的这个任务了。假设任务刚刚排队成功。别的线程调用了shutdownNow()关闭了线程池。那么依照shutdownNow函数的语义,这个任务不应该被处理。

由于存在这2种特殊情况。所以必须进行兴许的处理。but
may also fail to add them when they should. This is compensated within the following steps.这就是说:addIfUnderCorePoolSize可以保证不须要新线程的时候就不加入。可是不能保证须要加入新线程的时候就加入。所以让任务排队成功的时候。须要在锁的保护下,推断是否须要删除这个任务,或者是否须要新增线程来处理任务。

第6行代码if (runState != RUNNING || poolSize == 0),假设任务成功排队(workQueue.offer()返回true)后,线程池被关闭或者没有存活的线程,那么就须要执行ensureQueuedTaskHandled(command)。也就是说这样的情况下。任务可能没有得到合适的处置。

假设任务成功排队后,线程池仍然处在执行状态,并且有存活的线程。那么就行确保该新提交的任务一定会被处理。

为什么会这样呢?我们知道假设想关闭ThreadPoolExecutor,仅仅有3种途径:调用shutdown方法。调用shutdownNow方法,线程池最后一个工作者退出(相应workerDone方法)。

查看源代码我们知道,这3个可能导致线程池关闭的方法,终于都会调用tryTerminate()方法。也就是说假设线程池想要终止,就必须通过该方法。

  1. /**
  2. * Transitions to TERMINATED state if either (SHUTDOWN and pool
  3. * and queue empty) or (STOP and pool empty), otherwise unless
  4. * stopped, ensuring that there is at least one live thread to
  5. * handle queued tasks.
  6. *
  7. * This method is called from the three places in which
  8. * termination can occur: in workerDone on exit of the last thread
  9. * after pool has been shut down, or directly within calls to
  10. * shutdown or shutdownNow, if there are no live threads.
  11. */
  12. private void tryTerminate() {
  13. if (poolSize == 0) {
  14. int state = runState;
  15. if (state < STOP && !workQueue.isEmpty()) {
  16. state = RUNNING; // disable termination check below
  17. Thread t = addThread(null);
  18. if (t != null)
  19. t.start();
  20. }
  21. if (state == STOP || state == SHUTDOWN) {
  22. runState = TERMINATED;
  23. termination.signalAll();
  24. terminated();
  25. }
  26. }
  27. }

当线程池的线程池数是0的时候。假设线程池是running或者shutdown状态,而且任务队列不为空,那么就会新增1个线程来处理任务;假设线程池是状态,或者处于shutdown状态而且任务队列为空。那么线程池就会退出。也就是说,假设任务排队成功后,线程池还没有终止,那么该任务一定会得到合理的处置。

假设在任务插入之前或者插入的过程中。线程池不在执行状态runState != RUNNING 或者没有存活的线程poolSize == 0,那么就须要自己考虑下:怎样处理该提交的任务。这通过ensureQueuedTaskHandled来完毕。假设if (runState == RUNNING && workQueue.offer(command))条件是真,那么随后if (runState != RUNNING || poolSize == 0)基本上非常少出现。大部分场景下都不须要执行ensureQueuedTaskHandled方法,就不须要获取和释放锁。以下我们通过源代码看下,ensureQueuedTaskHandled方法是怎样处理异常场景的。

  1. /**
  2. * Rechecks state after queuing a task. Called from execute when
  3. * pool state has been observed to change after queuing a task. If
  4. * the task was queued concurrently with a call to shutdownNow,
  5. * and is still present in the queue, this task must be removed
  6. * and rejected to preserve shutdownNow guarantees. Otherwise,
  7. * this method ensures (unless addThread fails) that there is at
  8. * least one live thread to handle this task
  9. * @param command the task
  10. */
  11. private void ensureQueuedTaskHandled(Runnable command) {
  12. final ReentrantLock mainLock = this.mainLock;
  13. mainLock.lock();
  14. boolean reject = false;
  15. Thread t = null;
  16. try {
  17. int state = runState;
  18. if (state != RUNNING && workQueue.remove(command))
  19. reject = true;
  20. else if (state < STOP &&
  21. poolSize < Math.max(corePoolSize, 1) &&
  22. !workQueue.isEmpty())
  23. t = addThread(null);
  24. } finally {
  25. mainLock.unlock();
  26. }
  27. if (reject)
  28. reject(command);
  29. else if (t != null)
  30. t.start();
  31. }

该方法持有mainLock锁。所以能够防止线程池的并发改动。

假设线程池不在running状态(state != RUNNING ),而且新提交的任务还驻留在任务队列中(workQueue.remove(command)返回true),那么当前提交的任务会被拒绝运行(调用reject(comma)方法)。也就是说。仅仅要线程池不是running状态,那么就一定会拒绝运行当前提交的任务,除非该任务已经被线程池中的线程处理了(workQueue.remove返回false)。这里不区分究竟是shutdown()关闭的线程池。还是通过shutdownNow关闭的线程池。假设新提交一个任务,而且运行流程进入了ensureQueuedTaskHandled()函数。那么该任务可能会被拒绝运行。也可能会被正常运行。

假设线程池是running或者shutdown状态(state
< STOP)。而且线程池中已经没有存活的线程,而且任务队列非空。那么就须要新加1个线程,来处理等待运行的任务。

可以看到:通过多次无锁的条件推断,可以有效地降低提交任务时对mainLock锁的竞争;也可以确在并发运行的情况下。当前新提交的任务和线程池中等待运行的任务,都能得到合适的处理。

不知道大师Doug Lea是怎样想到这么巧妙的设计和实现execute()方法的!尽管execute方法可以提高性能,可是牺牲了代码的可读性和简易性。对于并发程序,还是那句经典的老话make it right before
you make it faster。

理解ThreadPoolExecutor源代码(二)execute函数的巧妙设计和阅读心得的更多相关文章

  1. C语言函数篇(二)函数参数基础设计

    形参实现一种数据传入的接口 ,由 实参 拷贝给 形参. 拷贝!!!!!!!!!!! 例1: void func(int tmp){ //意图是实现传进来的参数 +1 tmp++; } int mian ...

  2. Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理

    相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...

  3. 理解函数式编程中的函数组合--Monoids(二)

    使用函数式语言来建立领域模型--类型组合 理解函数式编程语言中的组合--前言(一) 理解函数式编程中的函数组合--Monoids(二) 继上篇文章引出<范畴论>之后,我准备通过几篇文章,来 ...

  4. Android Java 线程池 ThreadPoolExecutor源代码篇

    线程池简单点就是任务队列+线程组成的. 接下来我们来简单的了解下ThreadPoolExecutor的源代码. 先看ThreadPoolExecutor的简单类图,对ThreadPoolExecuto ...

  5. JDK1.7中的ThreadPoolExecutor源代码剖析

    JDK1. 7中的ThreadPoolExecutor 线程池,顾名思义一个线程的池子,池子里存放了非常多能够复用的线程,假设不用线程池相似的容器,每当我们须要创建新的线程时都须要去new Threa ...

  6. Java 1.7 ThreadPoolExecutor源代码解析

    相比1.6,1.7有些变化: 1.        添加了一个TIDYING状态.这个状态是介于STOP和TERMINATED之间的.假设运行完terminated钩子函数后状态就变成TERMINATE ...

  7. 深入理解NIO(二)—— Tomcat中对NIO的应用

    深入理解NIO(二)—— Tomcat中对NIO的应用 老哥行行好,转载和我说一声好吗,我不介意转载的,但是请把原文链接贴大点好吗 Tomcat大致架构 先贴两张图大致看一眼Tomcat的架构 Tom ...

  8. 简单理解ECMAScript2015中的箭头函数新特性

    箭头函数(Arrow functions),是ECMAScript2015中新加的特性,它的产生,主要有以下两个原因:一是使得函数表达式(匿名函数)有更简洁的语法,二是它拥有词法作用域的this值,也 ...

  9. Java线程池使用和分析(二) - execute()原理

    相关文章目录: Java线程池使用和分析(一) Java线程池使用和分析(二) - execute()原理 execute()是 java.util.concurrent.Executor接口中唯一的 ...

随机推荐

  1. [转]Java加密算法

    如基本的单向加密算法: BASE64 严格地说,属于编码格式,而非加密算法 MD5(Message Digest algorithm 5,信息摘要算法) SHA(Secure Hash Algorit ...

  2. 给Elasticsearch 5.2.2 设置用户权限 how to setting security for elasticsearch on windows

    1. download the plugin of elasticsearch: 下载 readonlyrest-1.14.0_es5.2.2.zip 2. install readonlyrest ...

  3. php7+apache2.4 (Windows7下)安装

    条件: ( 电脑必须win7 sp1, .netframework4 ) 一.下载php7和apache2.4 首先下载php7的windows压缩包,到这里下载http://windows.php. ...

  4. Android 开发工具介绍-SDK工具和平台工具

    原文链接:http://android.eoe.cn/topic/android_sdk Android的SDK提供各种工具可以帮你为Android平台开发移动应用程序.这些工具被分类成两组:SDK工 ...

  5. 不错位的java .class 反编译工具推荐

    我们经常会反编译看一些class文件,但是反编译出来的文件里面会有很多杂乱的东西 一直以来都是用的idea来反编译的,只要把class文件往里面一拖就行了 这么用没问题,用来看看源码什么的都OK 但是 ...

  6. csc.exe已退出,代码为-532462766

    我的surface pro4爆屏了 打电话给微软客服,那边说3,4天内给我回复 只能转移源码等资料到老电脑上,老电脑是神舟 精盾K480N I7D2,装的是WIN10预览版build 1625.rs2 ...

  7. Requests: 让 HTTP 服务人类

    requests 2.18.1文档 requests流式post文件 Calling SOAP Web service using requests module of

  8. pandas数组(pandas Series)-(4)NaN的处理

    上一篇pandas数组(pandas Series)-(3)向量化运算里说到,将两个 pandas Series 进行向量化运算的时候,如果某个 key 索引只在其中一个 Series 里出现,计算的 ...

  9. 网摘Android调用WebService

    这边特别注意调用的.net WCF 接口的绑定方式.以前一直用的wxHttpbinding,一直连不上.改成BasicHTTPbinding就能连上了 上篇文章已经对Web Service及其相关知识 ...

  10. 使用nginx搭建tomcat集群配置

    软件准备: (1)jdk-8u73-linux-x64.tar.gz (2)apache-tomcat-7.0.57.tar.gz (3)nginx-1.7.7.tar.gz 准备3台Linux机器, ...