精彩的失败远胜于平庸的成功。
目录
前言
本文进行JavaSE基础内容:Executor执行器体系的整体介绍。该文是整体框架介绍,并非局限于某一个使用的细节。由于我不止一次的被咨询说ExecutorService
和ScheduledExecutorService
什么区别和联系,以及ThreadPoolExecutor
和ThreadPoolTaskExecutor
有什么不一样之类的问题,因此决定写此文科普一下。
本文内容不深,但比较全,因此我相信绝对是你值得阅读的一篇文章(即使你可能已经工作了5年+)。
说明:流行框架如Spring、MyBatis、Netflix等一般均对
Executor
体系有所扩展,本文只讲述JDK的内容,其它的举一反三即可。
正文
java.util.concurrent.Executor
它属于J.U.C体系里的重要一员,是在JDK 1.5之后新增的内容,由大名鼎鼎的Doug Lea
大神操刀。因为它的出现才使得我们大量的使用多线程开发程序成为了可能。
Executor
执行器体系整体的框架可描述为如图所示(只例举出重要的API,覆盖绝大部分场景):
膜拜Doug Lea大神
喝水不忘挖井人,首先来膜拜一下这位超级大神吧。
Doug Lea
中文一般翻译为:道格·利。正所谓编程不识道格·利,写尽Java也枉然
,杂志《程序员》甚至评价他为:世界上对Java影响力最大的个人
。足以见得他在Java领域的地位。
Doug Lea
是真大神,大名鼎鼎的Java并发包(J.U.C)作者,同时也是HashMap
作者之一…Java5为何能被称为有史以来最重磅的一次升级?J.U.C并发包功不可没,包括1.7新引入的ForkJoinPool
也是另一神作。
他对Java做的贡献是无量的,此人乃真大神也,一起膜拜下吧:
Executor 执行器
执行器,可执行任意一个Runnable
任务。该接口提供了一种将任务提交与如何运行每个任务的机制(包括线程使用、调度等细节)分离的方法。因此任务它自己并不需要关心线程的创建、调度细节。
画外音:可能直接执行,也可能把你交给线程池执行,总之我帮你执行就欧克了
public interface Executor {
// @since 1.5
void execute(Runnable command);
}
需要注意的是:该执行器并不规定是同步执行还是异步执行你提交上来的任务,下面分别举例。
画外音:你之前一直以为
Executor
一定是异步,那是错的。应该说:绝大部分情况下我们会去异步执行,且绝大部分情况下均会使用线程池。但绝大多数并不代表所有
同步执行任务
@Test
public void fun1() {
String mainThreadName = Thread.currentThread().getName();
System.out.println("----------主线程[" + mainThreadName + "]开始----------");
// 自己定义一个同步执行器
Executor syncExecutor = (Runnable command) -> command.run();
// 提交任务
syncExecutor.execute(() -> {
String currThreadName = Thread.currentThread().getName();
System.out.println("线程[" + currThreadName + "] 我是同步执行的...");
});
}
运行程序,控制台打印:
----------主线程[main]开始----------
线程[main] 我是同步执行的...
特点:自己直接显示调用Runnable#run
那便是简单的方法调用而已,属于同步。
异步执行任务
@Test
public void fun2() throws InterruptedException {
String mainThreadName = Thread.currentThread().getName();
System.out.println("----------主线程[" + mainThreadName + "]开始----------");
// 自己定义一个异步执行器
Executor asyncExecutor = (Runnable command) -> new Thread(command).start();
// 提交任务
asyncExecutor.execute(() -> {
String currThreadName = Thread.currentThread().getName();
System.out.println("线程[" + currThreadName + "] 我是异步执行的...");
});
TimeUnit.SECONDS.sleep(1);
}
运行程序,控制台打印:
----------主线程[main]开始----------
线程[Thread-0] 我是异步执行的...
特点:new了一个Thread
去执行command任务,调度交由系统去掌控,属于异步。
通过如上两个示例可以看到Executor
它并不代表线程池(线程池的英文是:ThreadPool好麽~),仅是任务执行器而已。抽象出这么一个接口是想屏蔽掉内部执行、调度的细节,方面使用者的使用而已。
ExecutorService
Executor
过于抽象,仅代表着任务的执行(甚至同步、异步都没规定),因此直接通过实现该顶层接口的类并没有,平时使用得最多还是这个子接口ExecutorService
:提供更多的服务。
// @since 1.5
public interface ExecutorService extends Executor {
// 关闭。执行已经提交的任务,但不接受新任务。
// 此方法用于关闭不需要使用的执行器,内部会做资源回收的操作,如回收线程池
void shutdown();
// 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表
// 此方法一般不使用
List<Runnable> shutdownNow();
// 执行器是否已经被关闭
boolean isShutdown();
// 只有当shutdown()或者shutdownNow()被调用,而且所有任务都执行完成后才会返回true
boolean isTerminated();
// 阻塞。直到所有的任务都被执行完,或者超时
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
// =====下面为任务提交方法(使用Future跟踪任务执行情况)=====
// 以下三个是最最最最最最最常用的方法:执行任务
// Callable任务有返回值。所以Future.get()的时候可以拿到此返回值
<T> Future<T> submit(Callable<T> task);
// Runnable任务。执行完后Future.get()永远返回的是result这个值
<T> Future<T> submit(Runnable task, T result);
// 执行完后Future.get()永远返回的是null
Future<?> submit(Runnable task);
// 这几个方法在**批量执行**或**多选一**的业务场景中非常方便。
// invokeAll:所有任务都完成(包括成功/被中断/超时)后才会返回,所以它会阻塞哦(时间由最长的决定)
// invokeAny()在任意一个任务成功(被中断/超时)后就会返回,只需要成功一个就返回(时间由最短的决定)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
其实提交的Runnable
任务最终都会通过Executors.callable(runnable, result)
适配为一个Callable<V>
去执行的,具体源码处有兴趣的可以看一看。
从类图中可以看出,ExecutorService
的实现分为两个分支:左边的AbstractExecutorService
(一般为基于线程池的实现),以及右边的ScheduledExecutorService
延迟/周期执行服务,下面也得分别作出解释。
画外音:submit/invokeAll等方法如何去执行任务,调用execute()放入线程池or周期性执行,或者结合在一起?这是它的两大方向
AbstractExecutorService
对ExecutorService
的抽象实现:它实现了接口的部分方法,但是它并没有存放任务或者线程的数组或者Collection,也就是说它依旧和线程池没有半毛钱关系。
// @since 1.5
public abstract class AbstractExecutorService implements ExecutorService {
@Override
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
... // 其它submit方法原理一样,底层执行调用的均是Executor#execute方法
}
关于invokeAll()/invokeAny()
等方法的执行源码此处就不铺开了,记住结论即可:批量执行时使用特别方便(注意全部成功or任意一个成功的区别)。
手写实现AbstractExecutorService
本文以一个非常简单手写实例来告诉你任务执行器的效果,进一步让你感受到它目前还和线程池木有半毛钱关系。
private static class MyExecutorService extends AbstractExecutorService {
@Override
public void shutdown() {
System.out.println("关闭执行器,释放资源");
}
@Override
public List<Runnable> shutdownNow() {
System.out.println("立刻关闭执行器,释放资源");
return Collections.emptyList();
}
@Override
public boolean isShutdown() {
return false;
}
@Override
public boolean isTerminated() {
return false;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return false;
}
// 执行任务(本处使用异步执行)
@Override
public void execute(Runnable command) {
new Thread(command).start();
}
}
准备一个方法,用于产生任务:
// period:任务执行耗时 单位s
private Runnable createTask(int period) {
return () -> {
try {
TimeUnit.SECONDS.sleep(period);
} catch (InterruptedException e) {
e.printStackTrace();
}
String currThreadName = Thread.currentThread().getName();
System.out.println("线程[" + currThreadName + "] 我是异步执行的,耗时" + period + "s");
};
}
书写测试方法:
@Test
public void fun3() throws InterruptedException, ExecutionException {
String mainThreadName = Thread.currentThread().getName();
System.out.println("----------主线程[" + mainThreadName + "]开始----------");
ExecutorService executorService = new MyExecutorService();
Instant start = Instant.now();
Future<?> submit = executorService.submit(createTask(3));
System.out.println("结果为:" + submit.get());
Instant end = Instant.now();
System.out.println("总耗时为:" + Duration.between(start, end).getSeconds());
executorService.shutdown();
}
运行程序,打印:
----------主线程[main]开始----------
线程[Thread-0] 我是异步执行的,耗时3s
结果为:null
总耗时为:0
关闭执行器,释放资源
下面仅需改一下,把get放在上面:
System.out.println("结果为:" + submit.get());
Instant end = Instant.now();
System.out.println("总耗时为:" + Duration.between(start, end).getSeconds());
再次运行打印:
----------主线程[main]开始----------
线程[Thread-0] 我是异步执行的,耗时3s
结果为:null
总耗时为:3
关闭执行器,释放资源
另外,关于其它submit
方法,以及批量执行的invokeAll/invokeAny
方法,各位可自行测试哈。下面介绍JDK
自带的,Doug Lea
大神给我们提供的实现类,也是最最最最最为重要的一个类:ThreadPoolExecutor
。
ThreadPoolExecutor 带线程池的执行器
顾名思义,它是一个内置线程池的执行器,也就是说:它会把Runnable
任务的执行均扔进线程池里面进行执行,效率最高。
注意:
ThreadPoolTaskExecutor
它是Spirng提供的,基于ThreadPoolExecutor
进行包装实现,请勿弄混了。
本文并不会解释为何需要线程池,以及构建线程池的七大参数都是什么意思,而只会站在使用以及基础原理的角度做出示例和说明。
public class ThreadPoolExecutor extends AbstractExecutorService {
// 核心线程数
private volatile int corePoolSize;
// 最大线程数
private volatile int maximumPoolSize;
// 任务队列(当任务太多了,就放在这里排队)
private final BlockingQueue<Runnable> workQueue;
// 空闲线程的超时时间,超时就回收它(非core线程)
private volatile long keepAliveTime;
// 不解释
private volatile ThreadFactory threadFactory;
// 线程池拒绝处理函数(如任务太多了,如何拒绝)
// 默认策略是:AbortPolicy。要决绝是抛出异常:RejectedExecutionException
private volatile RejectedExecutionHandler handler;
}
通过上里手工模拟可知道最重要的是对execute()
方法的实现:它决定了你最终如何去执行任务(同步or异步?用老的线程还是用新的线程等等)。
execute()方法执行分析
ThreadPoolExecutor:
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);
}
这段代码实际运作非常的复杂,但因为大神对其良好的封装,使得我们理解起来并不难。对提交来的新任务处理步骤用一张图描绘如下:
对于不需要返回值的任务,使用submit or execute效果一样,但一般情况下推荐统一使用更高层的submit
系列方法去提交你的任务。
代码示例
略(相信没有人不会用它吧)。
ScheduledExecutorService
它在ExecutorService
接口上再扩展,额外增加了定时、周期执行的能力。
public interface ScheduledExecutorService extends ExecutorService {
// ========这个两个方法提交的任务只会执行一次========
// 创建并执行启用的一次性操作在给定的延迟之后。
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
// 创建并执行启用的一次性操作在给定的延迟之后。
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
// ========这个两个方法提交的任务会周期性的执行多次========
// 在给定的初始延迟之后,以给定的时间间隔执行周期性动作。
// 即在 initialDelay 初始延迟后initialDelay + period 执行第一次
// initialDelay + 2 * period 执行第二次,依次类推
// 特点:下一次任务的执行并不管你上次任务是否执行完毕
// 所以它名叫FixedRate:固定的频率执行(每次都是独立事件)
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
// 给定的初始延迟之后首先启用的定期动作
// 随后**上一个执行的终止**和**下一个执行的开始之间**给定的延迟。
// 也就是说delay表示的是上一次的end和下一次start之间的时间,两次之间是有关系的,不同于上个方法
// 所以它名叫FixedDelay:两次执行间固定的延迟
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
针对此接口,非常有必要强调如下几点:
- 定时执行和周期性执行是两码事
- 通过
ScheduledExecutorService#schedule()
方法调度执行的任务有且仅会执行一次(当然你任务内部怎么去玩就不归ScheduledExecutorService
管喽)- 注意:据我了解这是很多小伙伴的误区,以为
ScheduledExecutorService
执行的任务都会周期性执行的,这是非常错误的理解哦
- 注意:据我了解这是很多小伙伴的误区,以为
还需要提示一点的是:该接口依旧和线程池木有关系,主要看子类如何去实现。而大神给我们提供为“唯一”实现:ScheduledThreadPoolExecutor
。
ScheduledThreadPoolExecutor 集大成者
它可谓线程池 + 执行器的集大成者,最强子类:在线程池里执行任务,并且还可以定时、周期性的执行。
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService { ... }
代码示例
略。
关于它有话说:如果你有定时、周期执行任务的需求,请使用ScheduledThreadPoolExecutor
来代替Java古老的Timer/TimerTask
API吧。
关于它俩的对比,可参考这篇文章:Java定时任务ScheduledThreadPoolExecutor详解以及与Timer、TimerTask的区别(执行指定次数停止任务)
关于Executors
通过命名可知它是用于创建Executor
执行器的工具类(均为静态方法):
- 可快捷创建带有线程池能力的执行器
ThreadPoolExecutor
(ExecutorService) - 可快速创建具有线程池,且定时、周期执行任务能力的
ScheduledThreadPoolExecutor
(ScheduledExecutorService) - …
另外,它还提供几个我认为比较好用的工具方法分享给大家:
Executors:
// 默认线程工厂:
// 线程名称为:pool-[poolNum]-thread-[threadNumber]
// 非守护线程(请注意:非守护线程,看看是否符合你的要求)
public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
// 很方便的把一个Runnable适配为一个Callable<T>
// result可以是个常量值。当然也可以是null
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
public static Callable<Object> callable(Runnable task) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<Object>(task, null);
}
关于Executors
最后我想说:在生产环境下禁用,禁用,禁用(原因请参照阿里编码规范),一般用于自己测试的时候快速构建方便而为之。
使用线程池,请务必对其七大参数烂熟于胸,否则不要使用,容易酿成大祸或,并且还是软病,很难排查,因此需要敬畏和谨慎。
总结
关于Java中的Executor执行器大体系,以及它和线程池是什么关心就介绍到这,我相信经过本文你应该能彻底了解该体系的框架了吧,不用每次都不知道使用哪个了。
ScheduledExecutorService
属于最强接口,它具有全部能力,不过一般若你并不需要定时/周期执行能力的时候,请使用ThreadPoolExecutor/ExecutorService
即可。
声明
原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭
。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
- 一文搞懂常用的网络概念:域名、静态IP和动态IP、域名解析DNS、动态域名解析DDNS
- Java中InetAddress的使用(一):域名解析【享学Java】
- Java中InetAddress的使用(二):获取本机IP地址的正确姿势【享学Java】
- [享学Jackson] 一、初识Jackson – 世界上最好的JSON库
- [享学Jackson] 二、jackson-core之流式API与JsonFactory、JsonGenerator、JsonParser
- [享学Jackson] 三、jackson-databind之ObjectMapper与数据绑定、树模型
- [享学Jackson] 四、控制Jackson行为的特征们之JsonFactory.Feature、JsonGenerator.Feature、JsonParser.Feature
- [享学Jackson] 五、控制Jackson行为的特征们之JsonWriteFeature、JsonReadFeature
- [享学Jackson] 六、控制Jackson行为的特征们之MapperFeature、SerializationFeature、DeserializationFeature
- [享学Jackson] 七、Jackson使用bit位运算来开启/禁用Feature的原理解析
- [享学Jackson] 八、jackson-databind数据绑定基础配置之BaseSettings、MapperConfig、MapperConfigBase
- [享学Jackson] 九、jackson-databind数据绑定序列化/反序列化配置之SerializationConfig、DeserializationConfig
- [享学Jackson] 十、jackson-databind序列化之ObjectMapper序列化原理、序列化器匹配原理
- [享学Jackson] 十一、jackson-databind之JsonSerializer序列化器全解析
- [享学Jackson] 十二、jackson-databind反序列化之ObjectMapper反序列化原理、JsonDeserializer反序列化器全解析
- [享学Jackson] 十三、jackson-annotation注解模块全解析及Jackson注解大全
- [享学Jackson] 十四、深入理解Jackson的Module模块化设计及原理分析
- [享学Jackson] 十五、第三方模块Module的深度实践:JavaTimeModule、JSR310Module、ParameterNamesModule、Jdk8Module
- [享学Jackson] 十六、Jackson在Spring MVC中的使用之Date、JSR310时间类型的处理
- [享学Jackson] 十七、spring-web整合Jackson源码解析之Jackson2ObjectMapperBuilder
- [享学Jackson] 十八、Spring容器深度整合Jackson的桥梁之SpringHandlerInstantiator
- [享学Jackson] 十九、Spring下使用ObjectMapper的正确姿势 — Jackson2ObjectMapperFactoryBean
- [享学Jackson] 二十、Spring MVC下的Jackson — MappingJackson2HttpMessageConverter
- [享学Jackson] 二十一、Spring Boot下的Jackson — JacksonAutoConfiguration自动配置
- [享学Jackson] 二十二、Jackson与Fastjson的恩怨情仇(完结篇)