B2. Concurrent 线程池(Executor)
【概述】
与数据库连接管理类似,线程的创建和销毁会耗费较大的开销,使用 “池化技术” 来更好地利用当前线程资源,减少因线程创建和销毁带来的开销,这就是线程池产生的原因。
【无限创建线程的不足】
在生产环境中,若没有线程池,则需要采用的是 “为每个任务创建一个线程” 的方法,当出现大量的请求时需要创建大量的线程:
- 线程生命周期的开销非常高:线程的创建和销毁并不是没有代价的。根据平台的不同,实际的开销也有所不同,但线程的创建过程都需要时间,延迟处理的请求,并且需要 JVM 和操作系统提供一些辅助操作。如果请求的到达率非常高且请求的处理过程是轻量级的,例如大多数服务器应用程序就是这种情况,那么为每个请求创建一个新线程将消耗大量的计算资源。
- 资源消耗:活跃的线程会消耗系统资源,尤其是内存。如果可运行的线程数量多于可用处理器的数量,那么有些线程将闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量线程在竞争 CPU 资源时还将产生其他的性能开销。如果你已经拥有足够多的线程使 CPU 保持忙碌状态,那么再创建更多的线程反而会降低性能。
- 稳定性:在可创建线程的数量上存在一个限制。这个限制值将随着平台的不同而不同,并且受多个因素制约,包括 JVM 的启动参数、Thread 构造函数中请求的栈大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么很可能会抛出 OOM 异常,要想从这种错误中恢复过来是非常危险的,更简单的方法是通过构造程序来避免超出这些限制。
在一定的范围内,增加线程可以提供系统的吞吐率,但如果超出了这个范围,再创建再多的线程只会降低程序的执行速度,并且如果过多地创建一个线程,那么整个应用程序将会崩溃。要想避免这种危险,就应该对应用程序可以创建的线程数量进行限制,并且全面地测试应用程序,从而确保在线程数量达到限制时,程序也也不会耗尽资源。
“为每个任务分配一个线程” 这种方法的问题在于,它没有限制可创建线程的数量,只限制了远程用户提交 HTTP 请求的速率。与其他的并发危险一样,在原型设计和开发阶段,无限制地创建线程或许还能较好地运行,但在应用程序部署后并处于高负载下运行时,才会有问题不断地暴露出来。因此,某个恶意的用户或者过多的用户,都会使 Web 服务器的负载达到某个阈值,从而使服务器崩溃。如果服务器需要提供高可用性,并且在高负载情况下能平缓地降低性能,那么这将是一个严重的故障。
【线程池的执行策略】
1). 在执行(execute)一个任务(command)时,获取当前线程池状态(c),然后获取线程池工作线程的数量(workerCountOf(c)),如果当前线程池的工作线程数量少于定义的核心工作线程的数量(corePoolSize),则使用工厂(threadFactory)创建一个核心工作线程;若核心工作线程已满,则无需再创建核心工作线程,继续下一步。
2). 这里对线程池状态进行进行双检查(double-check)。首先进行第一次状态检查,获取线程池的状态(ctl),判断其是否处于运行状态(isRunning);
2.1). 如果线程池处于运行状态(isRunning 方法返回结果为 true),则将任务加入到工作队列中(workQueue,是一个阻塞队列 BlockingQueue);如果成功加入(offer 方法返回 true)到工作队列(workQueue),此时进行第二次状态检查,获取线程池的状态(recheck),判断其是否处于运行状态(isRunning 方法返回结果为 true);
2.1.1). 如果线程池处于运行状态,则表示该任务可以被执行,此时判断当前工作线程数量是否为空(workerCountOf(recheck) 返回 0),若为空则使用工厂(threadFactory)创建一个非核心工作线程(总的工作线程数量不能大于最大工作线程数量 maximumPoolSize);
2.1.2). 如果线程池处于非运行状态,则表示该任务不可以被执行,此时需要从工作队列中移除(remove)该任务,若移除任务失败,则需要交给拒绝执行处理器(handler)进行拒绝处理(reject)。
2.2). 如果线程池处于非运行状态(isRunning 方法返回结果为 false)或加入工作队列(workQueue)失败(offer 方法返回 false),则使用工厂(threadFactory)创建一个非核心工作线程(总的工作线程数量不能大于最大工作线程数量 maximumPoolSize);如果创建失败,则需要交给拒绝执行处理器(handler)进行拒绝处理(reject)。
JDK 中实现代码如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
B2. Concurrent 线程池(Executor)的更多相关文章
- Java并发——线程池Executor框架
线程池 无限制的创建线程 若采用"为每个任务分配一个线程"的方式会存在一些缺陷,尤其是当需要创建大量线程时: 线程生命周期的开销非常高 资源消耗 稳定性 引入线程池 任务是一组逻辑 ...
- 多线程之线程池Executor应用
JDK1.5之后,提供了自带的线程池,以便我们更好的处理线程并发问题. Executor类给我提供了多个线程池创建的方式: 创建固定的线程池 Executors.newFixedThreadPool( ...
- (Java 多线程系列)Java 线程池(Executor)
线程池简介 线程池是指管理同一组同构工作线程的资源池,线程池是与工作队列(Work Queue)密切相关的,其中在工作队列中保存了所有等待执行的任务.工作线程(Worker Thread)的任务很简单 ...
- Java 线程池 Executor 框架
在Java中,可以通过new Thread 的方法来创建一个新的线程执行任务,但是线程的创建是非常耗时的,而且创建出来的新的线程都各自运行.缺乏统一的管理,这样的后果是可能导致创建过多的线程从而过度消 ...
- Java线程池Executor&ThreadPool
java自1.5版本之后,提供线程池,供开发人员快捷方便的创建自己的多线程任务.下面简单的线程池的方法及说明. 1.Executor 线程池的顶级接口.定义了方法execute(Runnable),该 ...
- (转)深入详解Java线程池——Executor框架
转:https://yq.aliyun.com/articles/633782?utm_content=m_1000015330 在Java中,使用线程来异步执行任务.Java线程的创建与销毁需要一定 ...
- Concurrent - 线程池
原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/11426981.html ThreadPoolExecutor底层方法参数: @param corePo ...
- Java线程池 Executor框架概述
线程池的意义 循环利用线程资源,避免重复创建和销毁线程 线程池的任务是异步执行的,只要提交完成就能快速返回,可以提高应用响应性 Java线程池还有一个很重要的意义:Java线程池就是JDK 5 推出的 ...
- Java线程池 / Executor / Callable / Future
为什么需要线程池? 每次都要new一个thread,开销大,性能差:不能统一管理:功能少(没有定时执行.中断等). 使用线程池的好处是,可重用,可管理. Executor 4种线程 ...
随机推荐
- Codeforces 633H. Fibonacci-ish II
题目大意: 一个数列 q次询问 每次询问l r 将数列中l-r的位置排序去重后的数列成为b 输出 sigma b i * F i (其中F i为斐波那契数列中的第i项) 思路: 由于要去重 考虑权值线 ...
- bzoj 3991 寻宝游戏
题目大意: 一颗树 有一个点的集合 对于每个集合的答案为 从集合内一个点遍历集合内所有点再返回的距离最小值 每次可以选择一个点 若在集合外便加入集合 若在集合内就删除 求每次操作后这个集合的答案 思路 ...
- 腾讯API
相关文档: API列表 腾讯开放平台联调工具集 公共返回码说明 SDK下载
- ubuntu 12.04 alt+tab无法切换窗口的问题(转载)
转自:http://www.2cto.com/os/201209/153282.html ubuntu 12.04 alt+tab无法切换窗口的问题 安装cpmpiz配置管理程序. sudo ...
- pip 的具体含义
Python 2.7.9 and later (on the python2 series), and Python 3.4 and later include pip (pip3 for Pytho ...
- ES的简单增删改查
1.查看全部索引 GET:192.168.100.102:9200/_cat/indices 2.创建名为news的索引 PUT:192.168.100.102:9200/news 3.新增docum ...
- django views视图函数
Django views.py视图文件 一. 创建views.py文件,在工程文件夹根目录创建views.py视图文件,其实任意文件名都可以,使用views是为了遵循传统. 注:所有的views函数都 ...
- [UOJ386]鸽子固定器
题解 堆+贪心 题意就是给你\(n\)个物品,让你最多选\(m\)个 每个物品有两个属性\(a_i,b_i\) 最大化\((\sum_{a_i})^{dv}+(max(b_i)-min(b_i))^{ ...
- _bzoj1031 [JSOI2007]字符加密Cipher【后缀数组】
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1031 保存后缀数组模版. 其实如果数据范围小一点,或者空间限制再大一点,或者甚至只要字母表再 ...
- Kuskal/Prim POJ 1789 Truck History
题目传送门 题意:给出n个长度为7的字符串,一个字符串到另一个的距离为不同的字符数,问所有连通的最小代价是多少 分析:Kuskal/Prim: 先用并查集做,简单好写,然而效率并不高,稠密图应该用Pr ...