Executor(一)ExecutorService 线程池
一、ExecutorService 介绍
Java API 对 ExecutorService 接口的实现有两个,它们是:
- ThreadPoolExecutor
- ScheduledThreadPoolExecutor
二、ExecutorService 创建
创建一个什么样的 ExecutorService 的实例(即线程池)需要根据具体应用场景而定,不过 Java 给我们提供了一个 Executors 工厂类,它可以帮助我们很方便的创建各种类型 ExecutorService 线程池,Executors 一共可以创建下面这四类线程池:
- newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO)
备注:Executors 只是一个工厂类,它所有的方法返回的都是 ThreadPoolExecutor、ScheduledThreadPoolExecutor 这两个类的实例。
三、ExecutorService 使用
public interface ExecutorService extends Executor {
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<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;
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
}
public interface Executor {
void execute(Runnable command);
}
3.1 execute(Runnable task)
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
System.out.println("Asynchronous task");
});
executorService.shutdown();
这个方法有个问题,就是没有办法获知 task 的执行结果。如果我们想获得 task 的执行结果,我们可以传入一个 Callable 的实例(下面会介绍)。
3.2 submit(Runnable task)
submit(Runnable) 和 execute(Runnable) 区别是前者可以返回一个 Future 对象,通过返回的 Future 对象,我们可以检查提交的任务是否执行完毕,请看下面执行的例子:
Future future = executorService.submit(() -> {
System.out.println("Asynchronous task");
});
future.get(); //returns null if the task has finished correctly.
如果任务执行完成,future.get() 方法会返回一个 null。注意,future.get()方法会产生阻塞。
3.3 submit(Callable task)
submit(Callable) 和 submit(Runnable) 类似,也会返回一个 Future 对象,但是除此之外,submit(Callable) 接收的是一个 Callable 的实现,Callable 接口中的 call() 方法有一个返回值,可以返回任务的执行结果,而 Runnable 接口中的 run() 方法是 void 的,没有返回值。请看下面实例:
Future<?> future = executorService.submit(() -> {
System.out.println("Asynchronous task");
return "binarylei";
});
System.out.println("future.get() = " + future.get());
如果任务执行完成,future.get() 方法会返回 Callable 任务的执行结果。注意,future.get() 方法会产生阻塞。
3.4 invokeAny(Collection tasks)
invokeAny(...) 方法接收的是一个 Callable 的集合,执行这个方法不会返回 Future,但是会返回所有 Callable 任务中其中一个任务的执行结果。这个方法也无法保证返回的是哪个任务的执行结果,反正是其中的某一个。请看下面实例:
Set<Callable<String>> tasks = new HashSet<>();
for (int i = 0; i < 5; i++) {
int num = i;
tasks.add(new Callable<String>() {
@Override
public String call() throws Exception {
return String.valueOf(num);
}
});
}
System.out.println(executorService.invokeAny(tasks));
大家可以尝试执行上面代码,每次执行都会返回一个结果,并且返回的结果是变化的,可能会返回“Task2”也可是“Task1”或者其它。
3.5 invokeAll(Collection tasks)
invokeAll(...) 与 invokeAny(...) 类似也是接收一个 Callable 集合,但是前者执行之后会返回一个 Future 的 List,其中对应着每个 Callable 任务执行后的 Future 对象。情况下面这个实例:
Set<Callable<String>> tasks = new HashSet<>();
for (int i = 0; i < 5; i++) {
int num = i;
tasks.add(new Callable<String>() {
@Override
public String call() throws Exception {
return String.format("Task-%s", num);
}
});
}
List<Future<String>> futures = executorService.invokeAll(tasks);
for (int i = 0; i < futures.size(); i++) {
System.out.println(futures.get(i).get());
}
四、ExecutorService 关闭
当我们使用完成 ExecutorService 之后应该关闭它,否则它里面的线程会一直处于运行状态。
举个例子,如果的应用程序是通过 main() 方法启动的,在这个 main() 退出之后,如果应用程序中的 ExecutorService 没有关闭,这个应用将一直运行。之所以会出现这种情况,是因为 ExecutorService 中运行的线程会阻止 JVM 关闭。
-
shutdown() :在调用 shutdown() 方法之后,ExecutorService 不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在 shutdown() 执行之前提交的任务都会被执行。
-
shutdownNow() :立即关闭 ExecutorService,这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。
-
isShutdown():当调用 shutdown() 方法后返回为 true。
-
isTerminated():当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true。
-
awaitTermination(2, TimeUnit.SECONDS):当调用 shutdown() 方法后,2s 内执行完所有的任务返回 true,超时返回 false。
//1. ExecutorService 不会立即关闭,直到所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行。
executorService.shutdown();
//2. 关闭ExecutorService,并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。
//executorService.shutdownNow();
//3. 当调用shutdown()方法后返回为true
System.out.println(executorService.isShutdown());
//4. 当调用shutdown()方法后,并且所有提交的任务完成后返回为true
System.out.println(executorService.isTerminated());
//5. 2s 内执行守任务则返回true,否则超时返回false
System.out.println(executorService.awaitTermination(2, TimeUnit.SECONDS));
//6. 执行完所有任务后退出
/*while (!executorService.isTerminated()) {
System.out.println("正在执行任务");
}*/
System.out.println(executorService.isTerminated());
Executor(二)ThreadPoolExecutor、ScheduledThreadPoolExecutor 及 Executors 工厂类
Java 中的线程池类有两个,分别是:ThreadPoolExecutor 和 ScheduledThreadPoolExecutor,这两个类都继承自 ExecutorService。利用这两个类,可以创建各种不同的Java线程池,为了方便我们创建线程池,Java API提供了 Executors 工厂类来帮助我们创建各种各样的线程池。下面我们分别介绍一下这三个类。
一、ThreadPoolExecutor
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数,初始化的线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程最大存活时间
TimeUnit unit, // 单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, //
RejectedExecutionHandler handler // 拒绝任务时的操作(拒绝策略)
) {}
自定义线程池有两个重要的参数:BlockingQueue 和 RejectedExecutionHandler
-
BlockingQueue
任务队列:-
使用有界队列时,有新的任务需要执行时:
a. 若线程池实际线程数小于 corePoolSize ,则优先创建线程;
b. 若大于 corePoolSize 则会将任务加入队列;
c. 若队列已满,则在总线程数不大于 maximumPoolSize 的前提下,创建新的线程;
d. 若线程数大于 maximumPoolSize 则执行拒绝策略。
-
使用无界队列时,与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。当有新任务到来时:
a. 若线程数小于 corePoolSize 时,则创建新的线程;
b. 若等于 corePoolSize 则将任务加入队列中,但就不会继续增加线程,也就是说,使用有界队列时,线程池的最大线程数为 corePoolSize;
c. 若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存为至。
-
-
RejectedExecutionHandler
JDK拒绝策略:-
AbortPolicy
该策略直接抛出异常,阻止系统工作 -
CallerRunsPolicy
只要线程池未关闭,该策略直接在调用者线程中运行当前被丢弃的任务。显然这样不会真的丢弃任务,但是,调用者线程性能可能急剧下降。 -
DiscardOledestPolicy
丢弃最老的一个请求任务,也就是丢弃一个即将被执行的任务,并尝试再次提交当前任务。 -
DiscardPolicy
默默的丢弃无法处理的任务,不予任何处理。 -
RejectedExecutioHandler
自定义拒绝策略,接口如下:public interfaceRejectedExecutionHandler{ voidrejectedExecution(Runnable r,ThreadPoolExecutor executor); }
-
自定义线程池例子:
public class CustomedThreadPoolTest implements Runnable {
private int id;
private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public CustomedThreadPoolTest(int id) {
this.id = id;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
;
}
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() +
":任务-" + this.id);
}
public static void main(String[] args) throws InterruptedException {
LinkedBlockingQueue queue = new LinkedBlockingQueue(); // (1)
//ArrayBlockingQueue queue = new ArrayBlockingQueue(5); // (2)
ThreadPoolExecutor customPool = new ThreadPoolExecutor(
2, // 核心线程
5, // 最大线程,无界队列时无用
5, // 线程最大空闲时间
TimeUnit.SECONDS,
queue, // 任务缓存
new RejectedExecutionHandler() { // (3)
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("拒绝:任务-" + ((CustomedThreadPoolTest) r).getId());
}
}
);
for (int i = 1; i <= 10; i++) {
customPool.execute(new CustomedThreadPoolTest(i));
}
Thread.sleep(1000);
System.out.println("剩余的任务数量为:" + queue.size());
customPool.shutdown();
}
}
-
使用无界队列 LinkedBlockingQueue 时,
-
第一步:创建 corePoolSize=2 个线程分别执行"任务-1"、"任务-2";
-
第二步:其余任务全部加入到 queue 队列中,若有空闲的线程则依次取出任务执行, 有界队列的最大线程数是
corePoolSize
剩余的任务数量为:10 2017-12-09 03:51:32 pool-1-thread-2:任务-2 2017-12-09 03:51:32 pool-1-thread-1:任务-1 2017-12-09 03:51:33 pool-1-thread-1:任务-4 2017-12-09 03:51:33 pool-1-thread-2:任务-3 2017-12-09 03:51:34 pool-1-thread-1:任务-5 2017-12-09 03:51:34 pool-1-thread-2:任务-6 2017-12-09 03:51:35 pool-1-thread-1:任务-7 2017-12-09 03:51:35 pool-1-thread-2:任务-8 2017-12-09 03:51:36 pool-1-thread-1:任务-9 2017-12-09 03:51:36 pool-1-thread-2:任务-10 2017-12-09 03:51:37 pool-1-thread-1:任务-11 2017-12-09 03:51:37 pool-1-thread-2:任务-12
-
-
使用有界队列 ArrayBlockingQueue 时,
-
第一步:先创建 corePoolSize=2 个线程执行"任务-1"-"任务-2";
-
第二步:将其余的任务全部加入到 queue 中,直到 queue 被装满(5个 任务3-任务7);
-
第三步:创建新的线程执行剩余的任务,直到达到 maximumPoolSize=5 ,即执行"任务8"-"任务10";
-
第四步:对其余的任务执行拒绝策略,"任务-11"-"任务-12";
-
第五步:从队列中依次取出线程执行。
拒绝:任务-11 拒绝:任务-12 剩余的任务数量为:5 2017-12-09 03:46:32 pool-1-thread-2:任务-2 2017-12-09 03:46:32 pool-1-thread-3:任务-8 2017-12-09 03:46:32 pool-1-thread-4:任务-9 2017-12-09 03:46:32 pool-1-thread-5:任务-10 2017-12-09 03:46:32 pool-1-thread-1:任务-1 2017-12-09 03:46:33 pool-1-thread-2:任务-3 2017-12-09 03:46:33 pool-1-thread-3:任务-4 2017-12-09 03:46:33 pool-1-thread-4:任务-5 2017-12-09 03:46:33 pool-1-thread-5:任务-6 2017-12-09 03:46:33 pool-1-thread-1:任务-7
-
-
自定义拒绝策略
rejectedExecution
new RejectedExecutionHandler() { // (3) @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 记录日志,空闲再做处理 System.out.println("拒绝:任务-" + ((CustomedThreadPoolTest) r).getId()); } }
二、ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor 是 ExecutorService 的另一个实现类,从上面 Java 线程池 ExecutorService 继承树这幅图可以看出,ScheduledThreadPoolExecutor 直接继承自 ScheduledExecutorService,ScheduledThreadPoolExecutor 类的功能也主要体现在 ScheduledExecutorService 接口上,而所以在介绍 ScheduledThreadPoolExecutor 之前先介绍一下 ScheduledExecutorService 接口。
2.1 ScheduledExecutorService 接口介绍
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);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay, long period, TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay, long delay, TimeUnit unit);
}
从上面接口定义我们知道,提供了四个方法,下面我们就分别介绍:
(1) schedule(Runnable command, long delay, TimeUnit unit)
这个方法的意思是在指定延迟之后运行 task。这个方法有个问题,就是没有办法获知 task 的执行结果。如果我们想获得 task 的执行结果,我们可以传入一个 Callable 的实例。
(2) schedule(Callable callable, long delay, TimeUnit unit)
这个方法与 schedule(Runnable task) 类似,也是在指定延迟之后运行 task,不过它接收的是一个 Callable 实例,此方法会返回一个 ScheduleFuture 对象,通过 ScheduleFuture 我们可以取消一个未执行的 task,也可以获得这个 task 的执行结果。
(3) scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
这个方法的作用是周期性的调度 task 执行。task 第一次执行的延迟根据 initialDelay 参数确定,以后每一次执行都间隔 period 时长。
如果 task 的执行时间大于定义的 period,那么下一个线程将在当前线程完成之后再执行。整个调度保证不会出现一个以上任务同时执行。
(4) scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
scheduleWithFixedDelay 的参数和 scheduleAtFixedRate 参数完全一致,它们的不同之处在于对 period 调度周期的解释。
在 scheduleAtFixedRate 中,period 指的两个任务开始执行的时间间隔,也就是当前任务的开始执行时间和下个任务的开始执行时间之间的间隔。
而在 scheduleWithFixedDelay 中,period 指的当前任务的结束执行时间到下个任务的开始执行时间。
2.2 ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor,构造参数很简单,只有 3 个:
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
- int corePoolSize:线程池维护线程的最少数量
- ThreadFactory threadFactory:线程工程类,线程池用它来制造线程
- RejectedExecutionHandler handler:线程池对拒绝任务的处理策略
三、Executors
创建一个什么样的 ExecutorService 的实例(即线程池)需要我们的具体应用场景而定,不过 Java 给我们提供了一个 Executors 工厂类,它可以帮助我们很方便的创建各种类型 ExecutorService 线程池,Executors 一共可以创建下面这四类线程池:
(1) newFixedThreadPool
newFixedThreadPool
创建一个固定数量的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
(2) newSingleThreadExecutor
newSingleThreadExecutor
创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
(3) newCachedThreadPool
newCachedThreadPool
创建一个可根据实际情况调整线程个数的线程池,不限制最大线程个数。当有任务提交时,用空闲线程执行任务,没有则创建一个线程,并且每个空闲线程会在60秒后自动回收。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
(4) newScheduledThreadPool
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
备注:Executors 只是一个工厂类,它所有的方法返回的都是 ThreadPoolExecutor、ScheduledThreadPoolExecutor 这两个类的实例。