深入浅出 Java Concurrency (34): 线程池 part 7 线程池的实现及原理 (2)[转]
线程池任务执行流程
我们从一个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.
线程池中所有任务执行都依赖于此接口。这段话有以下几个意思:
- 任务可能在将来某个时刻被执行,有可能不是立即执行。为什么这里有两个“可能”?继续往下面看。
- 任务可能在一个新的线程中执行或者线程池中存在的一个线程中执行。
- 任务无法被提交执行有以下两个原因:线程池已经关闭或者线程池已经达到了容量限制。
- 所有失败的任务都将被“当前”的任务拒绝策略RejectedExecutionHandler 处理。
回答上面两个“可能“。任务可能被执行,那不可能的情况就是上面说的情况3;可能不是立即执行,是因为任务可能还在队列中排队,因此还在等待分配线程执行。了解完了字面上的问题,我们再来看具体的实现。
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
}
}
这一段代码看起来挺简单的,其实这就是线程池最重要的一部分,如果能够完全理解这一块,线程池还是挺容易的。整个执行流程是这样的:
- 如果任务command为空,则抛出空指针异常,返回。否则进行2。
- 如果当前线程池大小 大于或等于 核心线程池大小,进行4。否则进行3。
- 创建一个新工作队列(线程,参考上一节),成功直接返回,失败进行4。
- 如果线程池正在运行并且任务加入线程池队列成功,进行5,否则进行7。
- 如果线程池已经关闭或者线程池大小为0,进行6,否则直接返回。
- 如果线程池已经关闭则执行拒绝策略返回,否则启动一个新线程来进行执行任务,返回。
- 如果线程池大小 不大于 最大线程池数量,则启动新线程来进行执行,否则进行拒绝策略,结束。
文字描述步骤不够简单?下面图形详细表述了此过程。
老实说这个图比上面步骤更难以理解,那么从何入手呢。
流程的入口很简单,我们就是要执行一个任务(Runnable command),那么它的结束点在哪或者有哪几个?
根据左边这个图我们知道可能有以下几种出口:
(1)图中的P1、P7,我们根据这条路径可以看到,仅仅是将任务加入任务队列(offer(command))了;
(2)图中的P3,这条路径不将任务加入任务队列,但是启动了一个新工作线程(Worker)进行扫尾操作,用户处理为空的任务队列;
(3)图中的P4,这条路径没有将任务加入任务队列,但是启动了一个新工作线程(Worker),并且工作现场的第一个任务就是当前任务;
(4)图中的P5、P6,这条路径没有将任务加入任务队列,也没有启动工作线程,仅仅是抛给了任务拒绝策略。P2是任务加入了任务队列却因为线程池已经关闭于是又从任务队列中删除,并且抛给了拒绝策略。
如果上面的解释还不清楚,可以去研究下面两段代码:
java.util.concurrent.ThreadPoolExecutor.addIfUnderMaximumPoolSize(Runnable)
java.util.concurrent.ThreadPoolExecutor.ensureQueuedTaskHandled(Runnable)
那么什么时候一个任务被立即执行呢?
在线程池运行状态下,如果线程池大小 小于 核心线程池大小或者线程池已满(任务队列已满)并且线程池大小 小于 最大线程池大小(此时线程池大小 大于 核心线程池大小的),用程序描述为:
上面的条件就是一个任务能够被立即执行的条件。
有了execute的基础,我们看看ExecutorService中的几个submit方法的实现。
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)[转]的更多相关文章
- 深入浅出 Java Concurrency (27): 并发容器 part 12 线程安全的List/Set[转]
本小节是<并发容器>的最后一部分,这一个小节描述的是针对List/Set接口的一个线程版本. 在<并发队列与Queue简介>中介绍了并发容器的一个概括,主要描述的是Queue的 ...
- 深入浅出 Java Concurrency (35): 线程池 part 8 线程池的实现及原理 (3)[转]
线程池任务执行结果 这一节来探讨下线程池中任务执行的结果以及如何阻塞线程.取消任务等等. 1 package info.imxylz.study.concurrency.future;2 3 publ ...
- 深入浅出 Java Concurrency (33): 线程池 part 6 线程池的实现及原理 (1)[转]
线程池数据结构与线程构造方法 由于已经看到了ThreadPoolExecutor的源码,因此很容易就看到了ThreadPoolExecutor线程池的数据结构.图1描述了这种数据结构. 图1 Thre ...
- 深入浅出 Java Concurrency (28): 线程池 part 1 简介[转]
从这一节开始正式进入线程池的部分.其实整个体系已经拖了很长的时间,因此后面的章节会加快速度,甚至只是一个半成品或者简单化,以后有时间的慢慢补充.完善. 其实线程池是并发包里面很重要的一部分,在实际情况 ...
- 深入浅出 Java Concurrency (36): 线程池 part 9 并发操作异常体系[转]
并发包引入的工具类很多方法都会抛出一定的异常,这些异常描述了任务在线程池中执行时发生的例外情况,而通常这些例外需要应用程序进行捕捉和处理. 例如在Future接口中有如下一个API: java.uti ...
- 深入浅出 Java Concurrency (29): 线程池 part 2 Executor 以及Executors[转]
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具.真正的线程池接口是ExecutorService. 下面这张图完整描述了线程 ...
- 深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题
主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的 ...
- 深入浅出 Java Concurrency (10): 锁机制 part 5 闭锁 (CountDownLatch)
此小节介绍几个与锁有关的有用工具. 闭锁(Latch) 闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态.通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻 ...
- 深入浅出 Java Concurrency (21): 并发容器 part 6 可阻塞的BlockingQueue (1)[转]
在<并发容器 part 4 并发队列与Queue简介>节中的类图中可以看到,对于Queue来说,BlockingQueue是主要的线程安全版本.这是一个可阻塞的版本,也就是允许添加/删除元 ...
随机推荐
- 多线程中Runnable 和Thread关于synchronized的疑点
学java时和同学碰到的一道题: 转自https://blog.csdn.net/qq_40857349/article/details/102809100 某公司组织年会,会议入场时有两个入口,在入 ...
- 全球CMOS图像传感器厂商
近期,台湾地区的Yuanta Research发布报告,介绍了其对CMOS图像传感器(CIS)市场的看法,以及到2022年的前景预期. 从该研究报告可以看出,2018年全球CMOS图像传感器的市场规模 ...
- .NET Core 3.0之深入源码理解Startup的注册及运行
原文:.NET Core 3.0之深入源码理解Startup的注册及运行 写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...
- 笔记:简单的面向对象-web服务器
import socket import re import multiprocessing import time import mini_frame class WSGIServer(object ...
- mysql sql时间戳格式化语句
FROM_UNIXTIME(c.lastUpdateTime/1000,'%Y-%c-%d %h:%i:%s' ) as updatetime; select c.roleid, r.username ...
- PHP算法之整数反转
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示例 1: 输入: 123输出: 321 示例 2: 输入: -123输出: -321示例 3: 输入: 120输出: 21注 ...
- pd.Panel转化成json,然后再还原回来
在使用tornado的write时候有一个需求,是将panel转化成json;而接收端再将json还原成panel格式. 尝试了很久,终于实现了. panel1 =pd.Panel({"on ...
- 如何理解 if __name__ == "__main__"
小明.py 朋友眼中你是小明(__name__ == '小明'), 你自己眼中你是你自己(__name__ == '__main__'), 你编程很好, 朋友调你去帮他写程序(import 小明, 这 ...
- pipenv的使用
首先,确保pip install pipenv已经安装 1.新建一个文件夹,并在地址栏输入cmd,回车. 2.输入pipenv install,等待虚拟环境搭建完毕. 3.输入pipenv shell ...
- csps模拟87888990部分题解
题面:https://www.cnblogs.com/Juve/articles/11752338.html https://www.cnblogs.com/Juve/articles/1175241 ...