# 说说ThreadPoolExecutor

## 认识
先来看看它所在的架构体系:
```java
package java.util.concurrent;
public interface Executor { void execute(Runnable command); }
public interface ExecutorService extends Executor {
//新加一些方法
}
public abstract class AbstractExecutorService implements ExecutorService {
//新加一些方法,以及一些方法的基本实现
}
public class ThreadPoolExecutor extends AbstractExecutorService {
//新加一些方法,以及继承方法的实现
}
```
- `Executor`接口就一个方法,用来执行Runnable。官方的说法是,将 `任务的执行` 与 `线程` 解耦和。
- `ExecutorService`接口,继承了`Executor`,同时添加了一些管理任务的方法,如submit/invokeAll/invokeAny/shutdown/shutdownNow 等。
- `AbstractExecutorService`抽象类,实现了`ExecutorService`,提供了默认的一些实现,并添加了三个工具方法。见下图:
- ![AbstractExecutorService.jpg](img/AbstractExecutorService.jpg)
-
- `ThreadPoolExecutor`类,直接可用的类,相对来说很复杂,也是本文的重点。
## 构造
先从构造方法入手。
![ThreadPoolExecutorConstructor.jpg](img/ThreadPoolExecutorConstructor.jpg)

由上图可见,有4个构造方法,大同小异。具体如下:
```java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
```
可见,本质上都是在调用最后一个。它的这些参数的含义如下:
- `corePoolSize`: 注意,这是核心线程池的尺寸,但并不是初始化时线程数。当提交的任务数量少于这个数值时,不会经过`workQueue`中转,直接创建新线程来执行。如果线程数量多于这个数值,且线程空闲(任务执行完毕)达到指定时间(keepAliveTime),则会干掉多出来的线程。 -- **需要结合下面的参来理解**
- `maximumPoolSize`: 这个很好理解,就是`线程池`所能提供的最大线程数量。
- `keepAliveTime`: 线程空闲时的存活时间 - 参考`corePoolSize`。
- `unit`: 存活时间的时间单位。
- `workQueue`: 工作队列 - 其实是任务队列。注意,可以使用有界队列,如ArrayBlockingQueue,也可以使用无界队列,如LinkedBlockingQueue。区别在于,能够接收有限/无限的任务。
- `threadFactory`: 线程工厂,提供线程用的。注意,不一定负责线程的管理,仅负责提供线程!线程的管理可能是ExecutorService负责的。
- `handler`: 任务被拒绝(执行或者提交)时的处理器。
## 怎么使用?
实际上,我们很少直接使用这个类,更多的时候是使用`Executors`这个工厂类的`#newFixedThreadPool(int nThreads)`,源码如下:
```java
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
```
对比前面的构造参数,很容易理解。
1. `corePoolSize`和`maximumPoolSize`相同 - 所以线程数量是一定的(当然开始的时候肯定不是,从0开始,见前面`corePoolSize`的说明部分)。
2. `keepAliveTime`是 **0**,所以没有等待时间(实际上这个参数用不到,因为最后的线程数量是一定的 - 如果都使用了)。
3. `workQueue`是`new LinkedBlockingQueue<Runnable>()`,所以这是无界队列,可以接收任意多任务。
这样使用的话,`threadFactory`和`handler`都是默认的,前者是 `Executors.defaultThreadFactory()`,看源码仅是在创建线程的时候添加了"线程组"、"线程名";后者是 `ThreadPoolExecutor.defaultHandler` 。源码如下:
```java
//Executors.defaultThreadFactory() {return new DefaultThreadFactory();}
static class DefaultThreadFactory implements ThreadFactory {
// ...

public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
// ...
}

//ThreadPoolExecutor.defaultHandler
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy(); //什么都不做,直接抛出异常!
```
## 验证
我们来验证下:
1. `corePoolSize`和`workQueue`的关系 - 也就是前面提到的“当提交的任务数量少于`corePoolSize`时,直接开启新线程,不经过`workQueue`”。
2. 当任务无法提交或执行时,直接抛出异常!
```java
@Test
public void testRejectionHandler(){
int corePoolSize = 2; //提交任务时,如果线程数低于2,则创建新线程。完成后则会始终维持最少2个线程。
int maximumPoolSize = 5; //线程池最多允许5个线程存在。-和workQueue什么关系呢?
long keepAliveTime = 5; // 线程数量超过corePoolSize时,如果空闲了,那会空闲多久。
TimeUnit timeUnit = TimeUnit.SECONDS;

// TODO 注意,ArrayBlockingQueue是有界队列,还可以用LinkedBlockingQueue 无界队列 - 就是可以submit无限任务!
ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);//TODO 该队列仅hold由execute方法提交的Runnable tasks。submit会调用execute!
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, workQueue);
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName());
try{
Thread.sleep(1000L * 1000); //任务一直进行,这样线程就一直不释放
} catch(InterruptedException e){
e.printStackTrace();
}
};
try{
for(int i = 0; i < 10; i++){ //提交10个任务
System.out.println(i);
Future<?> submit = threadPoolExecutor.submit(runnable);
//submit后,Queue里可能有内容,也可能没有 - 可能已经移交给worker线程了!
//FIXME 下面队列用法不对。因为 只有在线程数少于corePoolSize时,才不走队列。
BlockingQueue<Runnable> queue = threadPoolExecutor.getQueue();
System.out.println("queue size: " + queue.size());
System.out.println("queue.remainingCapacity: " + queue.remainingCapacity()); // 这个可能有用
// queue.take();//wait until
System.out.println("queue.peek: " + queue.peek());//奇怪,总是同一个,难道需要同步?
// queue.poll();//retrive and remove head
}
System.out.println("----a");
threadPoolExecutor.execute(runnable); //嗯?什么时候用这个比较好?
System.out.println("----b");
} catch(Exception e){
System.out.println(threadPoolExecutor.isShutdown());
System.out.println(threadPoolExecutor.isTerminated());
System.out.println(threadPoolExecutor.isTerminating());
while(true){
try{
System.out.println(threadPoolExecutor.getQueue().take());
} catch(InterruptedException e1){
e1.printStackTrace();
}
}
}
try{
threadPoolExecutor.awaitTermination(1L, TimeUnit.HOURS);
} catch(InterruptedException e){
e.printStackTrace();
}
}
```
执行结果如下:
pool-1-thread-5
false
false
false
```

能够看到,前面2个任务提交的时候,队列中并没有内容,之后则一直有内容。很好的验证了第一条。
最后5行,则是异常后输出的内容,可以看出`threadPoolExecutor`仍在进行,但新提交的任务则被拒绝执行。- 其实这里应该吧try-catch放到循环里面,这样可以看到后续的提交都失败了。
感兴趣的可以自己试一下,同时输出下e.printStackTrace()。
## 其他
其实`ThreadPoolExecutor`的状态设计非常赞,不过那是另外的事了。
套用网友一句话,“李大爷设计的api,很巧妙,处处是坑”,哈哈。

稍稍解读下ThreadPoolExecutor的更多相关文章

  1. 稍稍解读下JDK8的HashMap

    首先,源码中上来就有一大段注释,但最重要的就是第一句. 大意如下: 本map经常用作一个 binned (bucketed) hash table (下面有解释),但是,当bins很大的时候,它们会被 ...

  2. weex官方demo weex-hackernews代码解读(下)

    weex 是阿里出品的一个类似RN的框架,可以使用前端技术来开发移动应用,实现一份代码支持H5,IOS和Android.而weex-hacknews则是weex官方出品的,首个使用 Weex 和 Vu ...

  3. SpringBoot项目框架下ThreadPoolExecutor线程池+Queue缓冲队列实现高并发中进行下单业务

    主要是自己在项目中(中小型项目) 有支付下单业务(只是办理VIP,没有涉及到商品库存),目前用户量还没有上来,目前没有出现问题,但是想到如果用户量变大,下单并发量变大,可能会出现一系列的问题,趁着空闲 ...

  4. jquery对div元素进行鼠标移动(稍稍修改下可以实现div跟随鼠标)

    /* 网上找了资料都是对于event.clientX和offset().left进行了计算,但是去掉了这个计算方式,直接使用当前坐标也一样,效果都一样不太好 strHeader:标题 jquery定位 ...

  5. 线程池ThreadPoolExecutor源码分析

    在阿里编程规约中关于线程池强制了两点,如下: [强制]线程资源必须通过线程池提供,不允许在应用中自行显式创建线程.说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源 ...

  6. 【转】 解读EOF

    解读EOF 标签: fplinuxc语言filestream 2012-01-31 22:05 439人阅读 评论(0) 收藏 举报  分类: C.C++_程序设计(20)  我学习C语言的时候,遇到 ...

  7. jdk1.8 ThreadPoolExecutor实现机制分析

    ThreadPoolExecutor几个重要的状态码字段 private static final int COUNT_BITS = Integer.SIZE - 3; private static ...

  8. Java Executor并发框架(二)剖析ThreadPoolExecutor运行过程

    上一篇从整体上介绍了Executor接口,从上一篇我们知道了Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newScheduledThrea ...

  9. Nginx 模块开发(1)—— 一个稍稍能说明问题模块开发 Step By Step 过程

    1. Nginx 介绍        Nginx是俄罗斯人编写的十分轻量级的HTTP服务器,它的发音为“engine X”, 是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/S ...

随机推荐

  1. 洛谷P1342 请柬(SPFA)

    To 洛谷.1342 请柬 题目描述 在电视时代,没有多少人观看戏剧表演.Malidinesia古董喜剧演员意识到这一事实,他们想宣传剧院,尤其是古色古香的喜剧片.他们已经打印请帖和所有必要的信息和计 ...

  2. Codeforces.842D.Vitya and Strange Lesson(Trie xor)

    题目链接 /* 异或只有两种情况,可以将序列放到01Tire树上做 在不异或的情况下在Tire上查找序列的mex很容易,从高位到低位 如果0位置上数没有满,则向0递归:否则向1 (0位置上的数都满了 ...

  3. 潭州课堂25班:Ph201805201 第十四课 异常,处理 (课堂笔记)

    程序难免会出现错误 : 语法错误 : 逻辑错误: AttributeError  -->>  试图访问一个对象没有的属性, IOError  ---->>  输入输出异常 In ...

  4. [HihoCoder1394]网络流四·最小路径覆盖

    题目大意:从有向无环图中选出若干点不想交的链,使得这些链覆盖所有的点,并且链的条数最小. 思路:设超级源点$S$.超级汇点$T$.将$N$个点复制一份,分为$A$部和$B$部.对于$A$部的所有点$A ...

  5. JVM调优总结 -Xms -Xmx -Xmn -Xss(转)

    堆大小设置JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制:系统的可用虚拟内存限制:系统的可用物理内存限制.32位系统下,一般限制在1.5G~2G:64为操作 ...

  6. Syntax error , insert “EnumBody” to complete EnumDeclaration

    当@Test写在方法前面的时候因为没有导入junit的jar包,如果已经导入架包依然是同样的错误,那就是方法没写对,或者还没有写方法

  7. git 先创建远程库后克隆到本地

    前面学习了先有本地库,后有远程库的时候,如何关联远程库. 现在,假设我们从零开发,那么最好的方式是先创建远程库,然后,从远程库克隆. 首先,登陆GitHub,创建一个新的仓库,名字叫gitskills ...

  8. phpstorm破解

    由于JetBrains系列新版本注册激活发生了变化,所以原来的激活方式已经不能在使用. 只能用新的方式来破解了.此方式支持所有系列的新版版.包括IDEA15,PHPSTORM10,WEBSTORM11 ...

  9. unity操作Hierarchy视图下同名的对象

    上周遇到了一个令我尴尬的问题,在同一个场景内有了两个名字相同的对象,给个形象化的栗子: 场景内有橱窗,橱窗是模型,窗户是可以打开的[点击控制],窗户可以控制打开和关闭的.然后我就选用了一个保守的方式进 ...

  10. IAAS、SAAS 和 PAAS 的区别、理解

    通俗的讲: 如果你是一个网站站长,想要建立一个网站.不采用云服务,你所需要的投入大概是:买服务器,安装服务器软件,编写网站程序.现在你追随潮流,采用流行的云计算,如果你采用IaaS服务,那么意味着你就 ...