# Executor 框架
Java 线程创建和销毁需要一定开销,池化技术避免了频繁的创建和销毁,通过向线程池传入任务重复利用线程。Java 的线程既是工作单元,也是执行机制。JDK1.5 开始将工作单元和执行机制分开,工作单元是 Runnable
和 Callable
,执行机制由 Executor
框架提供。
Executor
框架主要由三部分组成:
- 任务:任务需要实现
Runnable
或Callable
。 - 任务的执行:
Executor
和ExecutorService
接口。 - 异步计算的结果:接口
Future
和实现该接口的FutureTask
类
Executor
框架形成的两级调度模型,上层,Java 多线程程序将应用分解为多个任务,使用用户级的调度器( Executor
框架)将任务映射为固定数量的线程;底层,操作系统将线程映射到硬件处理器上。
整个执行流程如下:
主线程创建实现 Runnable
或 Callable
接口的任务对象。 Executors
工具类可以把 Runnable
封装成 Callable
对象( Executors.callable(Runnable task)
或 Executors.callable(Runnable task, Object resule)
)。然后将任务对象交给 ExecutorService
执行( ExecutorService.submit(Runnable task/Callable<T) task
)。 ExecutorService
会返回一个实现 Future
接口的对象(JDK8 是 FutureTask
对象)。最后主线程执行 FutureTask.get()
等待任务执行完成。
# Executor
public interface Executor { | |
void execute(Runnable command); | |
} |
Executor
接口提供一种将任务提交从任务运行分离开来的方法。通常使用 Executor
而不是显式地创建线程。
一般来说,任务提交给 Excutor
,任务不会在提交线程中运行,而是交给其他线程
class ThreadPerTaskExecutor implements Executor { | |
public void execute(Runnable r) { | |
new Thread(r).start(); | |
} | |
} |
但是 Excutor
也不是严格异步,最简单的情况下, Excutor
可以在调用者线程中立即运行提交的任务
class DirectExecutor implements Executor { | |
public void execute(Runnable r) { | |
r.run(); | |
} | |
} |
其实就是看实现类如何编写代码。Executor 的主要作用:提供了一种显示创建线程的方式。
JDK1.5
之前的方式:Thread
+Runnable
Executor
提供的的方式:Executor
+Runnable
相关类结构(理清结构非常重要)
# ExecutorService
ExecutorService
继承自 Executor
接口, ExecutorService
提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future
的方法。 可以关闭 ExecutorService
,这将导致其停止接受新任务。关闭后,执行程序将最后终止,这时没有任务在执行,也没有任务在等待执行,并且无法提交新任务。
详细讲解就是:
- ExecutorService 接口是一个执行器,它可以终止任务。
- ExecutorService 接口还提供了返回 Future 接口的方法,这个方法可以用于追踪一个或多个异步任务的执行情况。
- ExecutorService 可以手动关闭,这种操作会导致它拒绝新的任务。
// 不再接受新任务,允许之前已经提交的方法执行完毕,然后再关闭执行器 | |
shutdown(); | |
// 阻止正在等待的任务开启,并且会试图停止正在执行的任务,然后关闭执行器。 | |
shutdownNow(); | |
// 提交方法 | |
<T> Future<T> submit(Callable<T> task); | |
Future<?> submit(Runnable task); | |
<T> Future<T> submit(Runnable task, T result); |
从类关系图可以看出, ExecutorService
扩展了 Executor
的功能,提供的功能逐渐向线程池靠近。
public void f() { | |
//newCachedThreadPool 就是一个线程池 | |
ExecutorService executorService = Executors.newCachedThreadPool(); | |
// ExecutorService 通过 submit 提交任务 | |
executorService.submit(() -> { | |
System.out.println("--- ExecutorService begin."); | |
try { | |
Thread.sleep(1000); | |
System.out.println("--- ExecutorService end."); | |
} catch (InterruptedException e) { | |
System.out.println("--- ExecutorService is interrupted."); | |
} | |
}); | |
executorService.shutdown(); | |
} |
其他重要方法
// 在线程池服务执行 shutdown () 或者 shutdownNow () 方法之后,所有的任务是否已经完成 | |
// 如果没有执行 shutdown () 或者 shutdownNow () 方法,则永远返回 false | |
executorService.isTerminated(); | |
// 阻塞等待所有的任务终止 | |
// 如果等待时间超时,则返回 false | |
// 如果当前线程被 interrupt,则抛出 InterruptedException 异常 | |
// 如果线程池了执行 shutdown () 或者 shutdownNow () 方法,并且所有的任务都已经完成,则返回 true | |
// 如果线程池未执行 shutdown () 或者 shutdownNow () 方法,则永远返回 false | |
executorService.awaitTermination(1000, TimeUnit.MILLISECONDS); |
# AbstractExecutorService
AbstractExecutorService
继承自 ExecutorService
接口,其提供 ExecutorService
执行方法的默认实现。此类使用 newTaskFor()
返回的 RunnableFuture 实现 submit
、 invokeAny
和 invokeAll
方法,默认情况下, RunnableFuture
是此包中提供的 FutureTask
类。
invokeAny()
是调用 newTaskFor () 方法对任务进行了包装为 RunnableFuture 对象,然后调用了本对象的 execute () 方法提交任务,并返回异步计算结果对象。
# ScheduledExecutorService
从 Scheduled
可以看出与定时有关。可以在给定延时之后调度任务,也可以根据指定的周期调度任务。
- schedule () 方法可以创建含有延时 (delays) 变量的任务,然后返回一个可以用于取消或检查运行状态的 Future 对象。如果
delays<=0
,则会立即执行。 - scheduleAtFixedRate () 方法和 scheduleWithFixedDelay () 方法可以创建并运行定期运行的任务。
详细方法说明:
1. schedule(Runnable command,long delay, TimeUnit unit)
:
- 在一定延时 (delay) 之后,运行 Runnable 任务。
- 此任务只运行一次。
2. schedule(Callable callable,long delay, TimeUnit unit)
:
- 在一定延时 (delay) 之后,运行 Callable 任务。
- 此任务只运行一次。
3. scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)
:
- 在一定延时 (initialDelay) 之后,开始周期性的运行 Runnable 任务。
- 周期性:上一次任务执行完成之后,等待一段时间 (delay),然后开始下一次任务。
4. scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)
:
- 在一定延时 (initialDelay) 之后,开始周期性的运行 Runnable 任务。
- 周期性:每过一段时间 (period),就开始运行一次 Runnable 任务。
- 如果任务的执行时间大于等待周期 (period):上一次任务执行完成之后,立即开始下一次任务。也就是说:每过一段时间 (任务执行时间),就开始运行一次 Runnable 任务。
# 参考
https://pdai.tech/md/java/thread/java-thread-x-juc-overview.html
https://blog.csdn.net/hanchao5272/article/details/79829407
https://blog.csdn.net/hanchao5272/article/details/79830245
https://blog.csdn.net/hanchao5272/article/details/79834744
https://www.jianshu.com/p/7418bedd520f
《并发编程的艺术》第 10 章