java多线程系列:ThreadPoolExecutor
ThreadPoolExecutor自定义线程池
开篇一张图(图片来自阿里巴巴Java开发手册(详尽版)),后面全靠编

好了要开始编了,从图片中就可以看到这篇博文的主题了,ThreadPoolExecutor自定义线程池。
目录
- ThreadPoolExecutor构造函数介绍
- 核心线程数corePoolSize
- 最大线程数maximumPoolSize
- 线程存活时间keepAliveTime
- 线程存活时间单位unit
- 创建线程的工厂threadFactory
- 队列
- 拒绝策略
- 8.1 AbortPolicy
- 8.2 CallerRunsPolicy
- 8.3 DiscardPolicy
- 8.4 DiscardOldestPolicy
- 8.5 自定义拒绝策略
- 线程池扩展
ThreadPoolExecutor构造函数介绍
在介绍穿件线程池的方法之前要先介绍一个类ThreadPoolExecutor,因为Executors工厂大部分方法都是返回ThreadPoolExecutor对象,先来看看它的构造函数吧
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
参数介绍
| 参数 | 类型 | 含义 |
|---|---|---|
| corePoolSize | int | 核心线程数 |
| maximumPoolSize | int | 最大线程数 |
| keepAliveTime | long | 存活时间 |
| unit | TimeUnit | 时间单位 |
| workQueue | BlockingQueue | 存放线程的队列 |
| threadFactory | ThreadFactory | 创建线程的工厂 |
| handler | RejectedExecutionHandler | 多余的的线程处理器(拒绝策略) |
核心线程数corePoolSize
这个参数表示线程池中的基本线程数量也就是核心线程数量。
最大线程数maximumPoolSize
这个参数是线程池中允许创建的最大线程数量,当使用有界队列时,且队列存放的任务满了,那么线程池会创建新的线程(最大不会超过这个参数所设置的值)。需要注意的是,当使用无界队列时,这个参数是无效的。
线程存活时间keepAliveTime
这个就是线程空闲时可以存活的时间,一旦超过这个时间,线程就会被销毁。
线程存活时间单位unit
线程存活的时间单位,有NANOSECONDS(纳秒)、MICROSECONDS(微秒)、MILLISECONDS(毫秒)、SECONDS(秒)、MINUTES(分钟)、HOURS(小时)、DAYS(天)。TimeUnit代码如下
public enum TimeUnit {
NANOSECONDS {...},
MICROSECONDS {...},
MILLISECONDS {...},
SECONDS {...},
MINUTES {...},
HOURS {...},
DAYS {...};
}
创建线程的工厂threadFactory
创建线程的工厂,一般都是采用Executors.defaultThreadFactory()方法返回的DefaultThreadFactory,当然也可以用其他的来设置更有意义的名称。
DefaultThreadFactory类如下
/**
* The default thread factory
*/
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
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;
}
}
队列
分为有界队列和无界队列,用于存放等待执行的任务的阻塞队列。有SynchronousQueue、ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue、PriorityBlockingQueue、LinkedTransferQueue、DelayedWorkQueue、LinkedBlockingDeque。下面将介绍有界和无界两种常用的队列。BlockingQueue类图如下

有界队列
当使用有界队列时,如果有新的任务需要添加进来时,如果线程池实际线程数小于corePoolSize(核心线程数),则优先创建线程,如果线程池实际线程数大于corePoolSize(核心线程数),则会将任务加入队列,若队列已满,则在中现场数不大于maximumPoolSize(最大线程数)的前提下,创建新的线程,若线程数大于maximumPoolSize(最大线程数),则执行拒绝策略。
无界队列
当使用无界队列时,maximumPoolSize(最大线程数)和拒绝策略便会失效,因为队列是没有限制的,所以就不存在队列满的情况。和有界队列相比,当有新的任务添加进来时,都会进入队列等待。但是这也会出现一些问题,例如线程的执行速度比任务提交速度慢,会导致无界队列快速增长,直到系统资源耗尽。
拒绝策略
当使用有界队列时,且队列任务被填满后,线程数也达到最大值时,拒绝策略开始发挥作用。ThreadPoolExecutor默认使用AbortPolicy拒绝策略。RejectedExecutionHandler类图如下

我们来看看ThreadPoolExecutor是如何调用RejectedExecutionHandler的,可以直接查看execute方法
public class ThreadPoolExecutor extends AbstractExecutorService {
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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);
}
}
可以看到经过一系列的操作,不符合条件的会调用reject方法,那我么接着来看看reject方法
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
可以看到调用了RejectedExecutionHandler接口的rejectedExecution方法。好了,现在来看看jdk提供的几个拒绝策略。
注:后续会写一篇ThreadPoolExecutor源码解析,专门介绍ThreadPoolExecutor各个流程
AbortPolicy
从下面代码可以看到直接抛出异常信息,但是线程池还是可以正常工作的。
public static class AbortPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
示例代码
线程类
public class Task implements Runnable{
private int id ;
public Task(int id){
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public void run() {
//
System.out.println(LocalTime.now()+" 当前线程id和名称为:" + this.id);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
public String toString(){
return "当前线程的内容为:{ id : " + this.id + "}";
}
}
测试代码
public class TestAbortPolicy {
public static void main(String[] args) {
//定义了1个核心线程数,最大线程数1个,队列长度2个
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1,
1,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2),
new ThreadPoolExecutor.AbortPolicy());
//直接提交4个线程
executor.submit(new Task(1));
executor.submit(new Task(2));
executor.submit(new Task(3));
//提交第四个抛异常
executor.submit(new Task(4));
}
}
执行结果
当前线程id和名称为:1
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@1540e19d rejected from java.util.concurrent.ThreadPoolExecutor@677327b6[Running, pool size = 1, active threads = 1, queued tasks = 2, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.learnConcurrency.executor.customThreadPool.testRejectedExecutionHandler.TestAbortPolicy.main(TestAbortPolicy.java:25)
当前线程id和名称为:2
当前线程id和名称为:3
可以看到添加第四个线程是抛出异常
CallerRunsPolicy
首先判断线程池是否关闭,如果未关闭,则直接执行该线程。关闭则不做任何事情。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
代码和上面的差不多就不贴了,想要查看的可以到github上查看TestCallerRunsPolicy,执行结果如下
14:58:19.462 当前线程id和名称为:4
14:58:19.462 当前线程id和名称为:1
14:58:20.464 当前线程id和名称为:5
14:58:20.464 当前线程id和名称为:2
14:58:21.464 当前线程id和名称为:3
14:58:22.464 当前线程id和名称为:6
DiscardPolicy
可以看到里面没有任何代码,也就是这个被拒绝的线程任务被丢弃了,不作任何处理。
public static class DiscardPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
DiscardOldestPolicy
首先判断线程池是否关闭,如果未关闭,丢弃最老的一个请求,尝试再次提交当前任务。 关闭则不做任何事情。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
代码和上面的差不多就不贴了,想要查看的可以到github上查看TestDiscardOldestPolicy,执行结果如下
15:02:28.484 当前线程id和名称为:1
15:02:29.486 当前线程id和名称为:5
15:02:30.487 当前线程id和名称为:6
可以看到线程2、3、4都被替换了
自定义拒绝策略
实现RejectedExecutionHandle接口即可,如下MyRejected
public class MyRejected implements RejectedExecutionHandler{
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("自定义处理:开始记录日志");
System.out.println(r.toString());
System.out.println("自定义处理:记录日志完成");
}
}
测试代码
public class TestCustomeRejectedPolicy {
public static void main(String[] args) {
//定义了1个核心线程数,最大线程数1个,队列长度2个
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1,
1,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2),
new MyRejected());
executor.execute(new Task(1));
executor.execute(new Task(2));
executor.execute(new Task(3));
executor.execute(new Task(4));
executor.execute(new Task(5));
executor.execute(new Task(6));
executor.shutdown();
}
}
输出结果
自定义处理:开始记录日志
当前线程的内容为:{ id : 4}
自定义处理:记录日志完成
自定义处理:开始记录日志
当前线程的内容为:{ id : 5}
自定义处理:记录日志完成
自定义处理:开始记录日志
当前线程的内容为:{ id : 6}
自定义处理:记录日志完成
15:12:39.267 当前线程id和名称为:1
15:12:40.268 当前线程id和名称为:2
15:12:41.268 当前线程id和名称为:3
Process finished with exit code 0
这里如果有仔细观察的你可能会有所好奇,为什么这里用execute方法而不是用submit?
这时因为用submit方法后,传入的线程会被封装成RunnableFuture,而我写的MyRejected有调用到toString方法,Task类有重写toString方法,但是被封装成RunnableFuture会输入如下内容
自定义处理:开始记录日志
java.util.concurrent.FutureTask@1540e19d
自定义处理:记录日志完成
自定义处理:开始记录日志
java.util.concurrent.FutureTask@677327b6
自定义处理:记录日志完成
自定义处理:开始记录日志
java.util.concurrent.FutureTask@14ae5a5
自定义处理:记录日志完成
15:18:17.262 当前线程id和名称为:1
15:18:18.263 当前线程id和名称为:2
15:18:19.264 当前线程id和名称为:3
Process finished with exit code 0
线程池扩展
ThreadPoolExecutor类中有三个方法是空方法,可以通过继承来重写这三个方法对线程进行监控。通过重写beforeExecute和afterExecute方法,可以添加日志、计时、监控等等功能。terminated方法是在线程关闭时调用的,可以在这里面进行通知、日志等操作。
//任务执行前
protected void beforeExecute(Thread t, Runnable r) { }
//任务执行后
protected void afterExecute(Runnable r, Throwable t) { }
//线程池关闭
protected void terminated() { }
示例代码
public class Main {
public static void main(String[] args) {
ThreadPoolExecutor pool = new MyThreadPoolExecutor(
2, //coreSize
4, //MaxSize
60, //60
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(4));
for (int i = 0; i < 8; i++) {
int finalI = i + 1;
pool.submit(() -> {
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
pool.shutdown();
}
static class MyThreadPoolExecutor extends ThreadPoolExecutor{
private final AtomicInteger tastNum = new AtomicInteger();
private final ThreadLocal<Long> startTime = new ThreadLocal<>();
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
startTime.set(System.nanoTime());
System.out.println(LocalTime.now()+" 执行之前-任务:"+r.toString());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
long endTime = System.nanoTime();
long time = endTime - startTime.get();
tastNum.incrementAndGet();
System.out.println(LocalTime.now()+" 执行之后-任务:"+r.toString()+",花费时间(纳秒):"+time);
super.afterExecute(r, t);
}
@Override
protected void terminated() {
System.out.println("线程关闭,总共执行线程数:"+tastNum.get());
super.terminated();
}
}
}
执行结果
15:43:23.329 执行之前-任务:java.util.concurrent.FutureTask@469dad33
15:43:23.329 执行之前-任务:java.util.concurrent.FutureTask@1446b68c
15:43:23.329 执行之前-任务:java.util.concurrent.FutureTask@5eefc31e
15:43:23.329 执行之前-任务:java.util.concurrent.FutureTask@33606b2
15:43:23.513 执行之后-任务:java.util.concurrent.FutureTask@33606b2,花费时间(纳秒):216399556
15:43:23.513 执行之前-任务:java.util.concurrent.FutureTask@236e71ad
15:43:23.601 执行之后-任务:java.util.concurrent.FutureTask@1446b68c,花费时间(纳秒):304505594
15:43:23.601 执行之前-任务:java.util.concurrent.FutureTask@107920dc
15:43:23.733 执行之后-任务:java.util.concurrent.FutureTask@5eefc31e,花费时间(纳秒):436283680
15:43:23.733 执行之前-任务:java.util.concurrent.FutureTask@502826b3
15:43:23.808 执行之后-任务:java.util.concurrent.FutureTask@469dad33,花费时间(纳秒):512242583
15:43:23.808 执行之前-任务:java.util.concurrent.FutureTask@96741ab
15:43:23.924 执行之后-任务:java.util.concurrent.FutureTask@107920dc,花费时间(纳秒):322900976
15:43:24.059 执行之后-任务:java.util.concurrent.FutureTask@236e71ad,花费时间(纳秒):546324680
15:43:24.498 执行之后-任务:java.util.concurrent.FutureTask@502826b3,花费时间(纳秒):765309335
15:43:24.594 执行之后-任务:java.util.concurrent.FutureTask@96741ab,花费时间(纳秒):785868205
线程关闭,总共执行线程数:8
GitHub地址
觉得不错的点个star
参考资料
[1] Java 并发编程的艺术
[2] Java 并发编程实战
java多线程系列:ThreadPoolExecutor的更多相关文章
- Java多线程系列--“JUC线程池”01之 线程池架构
概要 前面分别介绍了"Java多线程基础"."JUC原子类"和"JUC锁".本章介绍JUC的最后一部分的内容——线程池.内容包括:线程池架构 ...
- Java多线程系列--“JUC线程池”02之 线程池原理(一)
概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- Java多线程系列--“JUC线程池”04之 线程池原理(三)
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...
- Java多线程系列--“JUC线程池”05之 线程池原理(四)
概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...
- java多线程系列(六)---线程池原理及其使用
线程池 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知 ...
- java多线程系列(七)---Callable、Future和FutureTask
Callable.Future和FutureTask 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量 ...
- Java多线程系列——从菜鸟到入门
持续更新系列. 参考自Java多线程系列目录(共43篇).<Java并发编程实战>.<实战Java高并发程序设计>.<Java并发编程的艺术>. 基础 Java多线 ...
- Java多线程系列--“JUC锁”03之 公平锁(一)
概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...
- Java多线程系列--“JUC锁”04之 公平锁(二)
概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...
随机推荐
- codeforces 676B 模拟 递推
题意:每秒从最高处的杯子倒一杯酒下来,酒流的方式如图,问t秒装满酒的杯子的数目. 思路:把第一杯的值设为t,glass[i][j]=(glass[i-1][j-1]-1)/2+(glass[i-1][ ...
- Luogu-4048 [JSOI2010]冷冻波
考虑网络流,二分时间,源点向巫妖连流量为攻击次数的边,把每个巫妖向他能打的小精灵连一条流量为一的边,每个小精灵向汇点连一条边. 预处理每个小精灵能被那些巫妖打,这道题好像视线与树相切也算能打(雾. # ...
- Linux下查看Python安装了哪些脚本模块
Linux下查看Python安装了哪些脚本模块 1.什么是rpm ? rpm 即RedHat Package Management,是RedHat的发明之一 .现在包括OpenLinux.fedora ...
- html转义字符及css清除
1. [代码][Java]代码 import java.util.HashMap;import java.util.Map; import org.apache.commons.lang3. ...
- vue2.0+wechat
首先遇到的问题就是使用npm下载JSSDK 下载正确的JSSDK 正确的名称是:'weixin-js-sdk' 其实有好几个相似的名称都可以下载,只有这一个能用 支付问题使用Vue的路由跳转到支付页面 ...
- Ueditor基础使用
感谢大家对我这个菜鸟的帮助,这是我第一次用.NET做网站.在这里向大家推荐个百度免费的文本编辑器Ueditor,是.NET版的,在http://ueditor.baidu.com/website/in ...
- Django-02
知识预览 Ajax前戏:json Ajax简介 jquery实现的ajax JS实现的ajax 回到顶部 Ajax前戏:json 什么是json? 定义: JSON(JavaScript Object ...
- C#实现文件拖放并打开文件(使用ListBox)
1.C#实现文件拖放并打开文件 (http://www.cnblogs.com/GaoHuhu/archive/2012/10/10/2717954.html)
- MySQL丨删除一张表小常识
在MySQL里面 ,对于删除一张表里面的所有内容有两种语句: 1.delete from table_name; 2.truncate table table_name; 解析:以上两种方式都可以删除 ...
- hdu 3998 Sequence
There is a sequence X (i.e. x[1], x[2], ..., x[n]). We define increasing subsequence of X as x[i1], ...