线程池任务执行流程

我们从一个API开始接触Executor是如何处理任务队列的。

java.util.concurrent.Executor.execute(Runnable)

Executes the given task sometime in the future. The task may execute in a new thread or in an existing pooled thread. If the task cannot be submitted for execution, either because this executor has been shutdown or because its capacity has been reached, the task is handled by the current RejectedExecutionHandler.

线程池中所有任务执行都依赖于此接口。这段话有以下几个意思:

  1. 任务可能在将来某个时刻被执行,有可能不是立即执行。为什么这里有两个“可能”?继续往下面看。
  2. 任务可能在一个新的线程中执行或者线程池中存在的一个线程中执行。
  3. 任务无法被提交执行有以下两个原因:线程池已经关闭或者线程池已经达到了容量限制。
  4. 所有失败的任务都将被“当前”的任务拒绝策略RejectedExecutionHandler 处理。

回答上面两个“可能“。任务可能被执行,那不可能的情况就是上面说的情况3;可能不是立即执行,是因为任务可能还在队列中排队,因此还在等待分配线程执行。了解完了字面上的问题,我们再来看具体的实现。

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        if (runState == RUNNING && workQueue.offer(command)) {
            if (runState != RUNNING || poolSize == 0)
                ensureQueuedTaskHandled(command);
        }
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated
    }
}

这一段代码看起来挺简单的,其实这就是线程池最重要的一部分,如果能够完全理解这一块,线程池还是挺容易的。整个执行流程是这样的:

  1. 如果任务command为空,则抛出空指针异常,返回。否则进行2。
  2. 如果当前线程池大小 大于或等于 核心线程池大小,进行4。否则进行3。
  3. 创建一个新工作队列(线程,参考上一节),成功直接返回,失败进行4。
  4. 如果线程池正在运行并且任务加入线程池队列成功,进行5,否则进行7。
  5. 如果线程池已经关闭或者线程池大小为0,进行6,否则直接返回。
  6. 如果线程池已经关闭则执行拒绝策略返回,否则启动一个新线程来进行执行任务,返回。
  7. 如果线程池大小 不大于 最大线程池数量,则启动新线程来进行执行,否则进行拒绝策略,结束。

文字描述步骤不够简单?下面图形详细表述了此过程。

老实说这个图比上面步骤更难以理解,那么从何入手呢。

流程的入口很简单,我们就是要执行一个任务(Runnable command),那么它的结束点在哪或者有哪几个?

根据左边这个图我们知道可能有以下几种出口:

(1)图中的P1、P7,我们根据这条路径可以看到,仅仅是将任务加入任务队列(offer(command))了;

(2)图中的P3,这条路径不将任务加入任务队列,但是启动了一个新工作线程(Worker)进行扫尾操作,用户处理为空的任务队列;

(3)图中的P4,这条路径没有将任务加入任务队列,但是启动了一个新工作线程(Worker),并且工作现场的第一个任务就是当前任务;

(4)图中的P5、P6,这条路径没有将任务加入任务队列,也没有启动工作线程,仅仅是抛给了任务拒绝策略。P2是任务加入了任务队列却因为线程池已经关闭于是又从任务队列中删除,并且抛给了拒绝策略。

如果上面的解释还不清楚,可以去研究下面两段代码:

java.util.concurrent.ThreadPoolExecutor.addIfUnderCorePoolSize(Runnable)
java.util.concurrent.ThreadPoolExecutor.addIfUnderMaximumPoolSize(Runnable)
java.util.concurrent.ThreadPoolExecutor.ensureQueuedTaskHandled(Runnable)

那么什么时候一个任务被立即执行呢?

在线程池运行状态下,如果线程池大小 小于 核心线程池大小或者线程池已满(任务队列已满)并且线程池大小 小于 最大线程池大小(此时线程池大小 大于 核心线程池大小的),用程序描述为:

runState == RUNNING && ( poolSize < corePoolSize || poolSize < maxnumPoolSize && workQueue.isFull())

上面的条件就是一个任务能够被立即执行的条件。

有了execute的基础,我们看看ExecutorService中的几个submit方法的实现。

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Object> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

很简单,不是么?对于一个线程池来说复杂的地方也就在execute方法的执行流程。在下一节中我们来讨论下如何获取任务的执行结果,也就是Future类的使用和原理。

深入浅出 Java Concurrency (34): 线程池 part 7 线程池的实现及原理 (2)[转]的更多相关文章

  1. 深入浅出 Java Concurrency (27): 并发容器 part 12 线程安全的List/Set[转]

    本小节是<并发容器>的最后一部分,这一个小节描述的是针对List/Set接口的一个线程版本. 在<并发队列与Queue简介>中介绍了并发容器的一个概括,主要描述的是Queue的 ...

  2. 深入浅出 Java Concurrency (35): 线程池 part 8 线程池的实现及原理 (3)[转]

    线程池任务执行结果 这一节来探讨下线程池中任务执行的结果以及如何阻塞线程.取消任务等等. 1 package info.imxylz.study.concurrency.future;2 3 publ ...

  3. 深入浅出 Java Concurrency (33): 线程池 part 6 线程池的实现及原理 (1)[转]

    线程池数据结构与线程构造方法 由于已经看到了ThreadPoolExecutor的源码,因此很容易就看到了ThreadPoolExecutor线程池的数据结构.图1描述了这种数据结构. 图1 Thre ...

  4. 深入浅出 Java Concurrency (28): 线程池 part 1 简介[转]

    从这一节开始正式进入线程池的部分.其实整个体系已经拖了很长的时间,因此后面的章节会加快速度,甚至只是一个半成品或者简单化,以后有时间的慢慢补充.完善. 其实线程池是并发包里面很重要的一部分,在实际情况 ...

  5. 深入浅出 Java Concurrency (36): 线程池 part 9 并发操作异常体系[转]

    并发包引入的工具类很多方法都会抛出一定的异常,这些异常描述了任务在线程池中执行时发生的例外情况,而通常这些例外需要应用程序进行捕捉和处理. 例如在Future接口中有如下一个API: java.uti ...

  6. 深入浅出 Java Concurrency (29): 线程池 part 2 Executor 以及Executors[转]

    Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具.真正的线程池接口是ExecutorService. 下面这张图完整描述了线程 ...

  7. 深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题

      主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的 ...

  8. 深入浅出 Java Concurrency (10): 锁机制 part 5 闭锁 (CountDownLatch)

    此小节介绍几个与锁有关的有用工具. 闭锁(Latch) 闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态.通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻 ...

  9. 深入浅出 Java Concurrency (21): 并发容器 part 6 可阻塞的BlockingQueue (1)[转]

    在<并发容器 part 4 并发队列与Queue简介>节中的类图中可以看到,对于Queue来说,BlockingQueue是主要的线程安全版本.这是一个可阻塞的版本,也就是允许添加/删除元 ...

随机推荐

  1. USACO 2003 Fall Orange Popular Cows /// tarjan缩点 oj22833

    题目大意: n头牛,m个崇拜关系,并且崇拜具有传递性 如果a崇拜b,b崇拜c,则a崇拜c 求最后有几头牛被所有牛崇拜 强连通分量内任意两点都能互达 所以只要强联通分量内有一点是 那么其它点也都会是 按 ...

  2. LA 3263 /// 欧拉定理 oj21860

    题目大意: n个端点的一笔画 第n个和第1个重合 即一笔画必定是闭合曲线 输出平面被分成的区域数 欧拉定理 V+F-E=2 即 点数+面数-边数=2 (这里的面数包括了外部) #include < ...

  3. NEO4J亿级数据全文索引构建优化

    NEO4J亿级数据全文索引构建优化 一.数据量规模(亿级) 二.构建索引的方式 三.构建索引发生的异常 四.全文索引代码优化 1.Java.lang.OutOfMemoryError 2.访问数据库时 ...

  4. JDBC_数据库连接池工具类

    //定义一个类JDBCUtils public class JDBCUtils { //1.定义成员方法 private static DataSource ds; //2.提供静态代码块 stati ...

  5. spring MVC 转发与重定向(传参)

    return "forward:index.jsp"; //转发  return "forward:user.do?method=reg5"; //转发 ret ...

  6. js获取table checkbox选中行的值.mdjs获取table checkbox选中行的

    <!DOCTYPE html> <html> <head> <script src="https://cdn.staticfile.org/jque ...

  7. 关于jquery的一些插件

    1.fullPage.js插件 fullPage.js 是一个基于 jQuery 的插件,它能够很方便.很轻松的制作出全屏网站.如今我们经常能见到全屏网站,在手机上也经常能看到一些活动页面.这些网站用 ...

  8. VBA字典做数据有效性

    Private Sub Worksheet_SelectionChange(ByVal Target As Range)If Target.Column = 26 And Range("f& ...

  9. springboot EL @Value

    一,springboot中 看一下代码: @Controller public class HelloController { //读取枚举值 @Value("#{T(com.example ...

  10. 家庭-养老院模型理解IOC和DI

    控制反转 IOC 1. 概念 应用内部不负责依赖对象的创建和维护, 由第三方负责, 这样控制权就由应用内部转移到外部容器, 控制权的转移就是所谓的反转. 2. 比喻 有一户家庭(应用)有个老人(依赖对 ...