Executor:任务执行
关系:Executor
<- ExecutorService
=> AbstractExecutorService
=> ThreadPoolExecutor
=> ScheduledThreadPoolExecutor
Executor
在Java中,任务的抽象不是Thread,而是Executor。当出现new Thread(new(RunnableTask())).start()
,尽量替换成:
Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
Executor接口只保证在某个时刻会执行这个任务。但是在哪儿执行,什么时候执行,都由其实现者自主决定。
比如,在本线程(caller)中执行(串行执行,没什么意义):
public class CallerExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
一般情况下都是另起线程去执行,毕竟并发才会更快:
public class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
还可以使用其他的方式,反正一切由实现者自主裁决。
ExecutorService
Executor是有限制的。毕竟Executor一般都是采用异步执行的方式,那么任务一旦提交,其可能在运行,也可能在队列中等待执行,还可能执行完毕了。而且,任务也应该是可以被关闭的,这些对任务状态进行查询或操作的方法Executor接口都没有提供。
因此,ExecutorService接口出现了。使用ExecutorService可以增加对任务状态的管理,并且可以生产Future作为结果,用于追踪异步执行的任务。
任务生命周期管理
可以使用showdown()
不再接受和执行新任务,也可以使用showdownNow()
,除了不接受新任务,还尝试关停正在执行的任务。
所以在关停一个ExecutorService的时候,先使用shotdown()
拒绝接受新任务,然后awaitTermination(timeout)
等待正在执行的任务结束,如果超过了timeout时间还未结束,尝试强行关停shutdownNow()
正在执行的任务。
void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
pool.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}
对任务提交方法的拓展
另外新增加的submit(Runnable)
/submit(Runnable, T)
/submit(Callable<T>)
可以返回Future<T>
类型的结果,用于异步任务的追踪。这是对Executor.execute(Runnable)
的拓展,毕竟很多任务不是说只要执行了就完事儿了,还是要返回一些东西的。Callable是对Runnable的一种更好的抽象,它认为任务执行可能返回一个值,或者抛出一个异常。而使用Runnable则比较受限,当需要返回值的时候,只能做到将值写到一个文件或者共享数据域中。
AbstractExecutorService,是对ExecutorService的抽象实现,其submit
方法将Runnable/Callable任务封装成为RunnableFuture
类型的任务(使用newTaskFor()
方法实现,默认返回一个FutureTask<T>
),并调用execute()
方法执行该任务,最终将RunnableFuture返回给调用者,调用者可以使用get()
获取其结果。
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
RunnableFuture的默认实现是FutureTask,是通过newTaskFor(Runnable/Callable)
方法完成转换的。
1. Callable转换过程:Callable -> FutureTask;
2. Runnable转换过程:Runnable -> Callable -> FutureTask。
具体的Runnable转FutureTask过程:
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
In class Executors.java:
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
Future:未来对任务结果的获取
关系:Future
<- RunnableFuture
=> FutureTask
FutureTask是对Callable和Runnable的封装。其中维护的有Callable callable
和Object outcome
,当然还有一大堆代表callable的状态。可能的状态转换为:
NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTED
当使用get()
方法获取Future的结果时,其实就是获取outcome。
获取结果是,任务的执行状态可能有三种情况:
1. 如果此时状态为NORMAL,说明正常完成,返回outcome;
2. 如果状态为CANCELLED/INTERRUPTING/INTERRUPTED,说明任务被撤销了,返回CancellationException
;
3. 否则状态就是EXCEPTIONAL,说明任务执行过程中出现了异常,返回ExecutionException
。
Future接口代表异步计算的结果,可以通过isDone()
/isCancelled()
检查计算状态,可以通过cancel(boolean)
撤销任务,也可以通过get()
/get(long, TimeUnit)
阻塞地去获取计算结果。
FutureTask实现了RunnableFuture接口,所以既实现了Future,又实现了Runnable。因此可以使用FutureTask代表一个可以提交到Executor的任务,并在未来获取其结果,比如:
ExecutorService executor = ...;
FutureTask<String> future = new FutureTask<String>(
new Callable<String>() {
public String call() {
return xxx;
}
});
Future<String> future = executor.execute(future);
String result = future.get();
CompletionService:及时获取已完成任务的结果
关系:CompletionService
=> ExecutorCompletionService
如果向Executor提交了一组任务,并希望每一个任务完成之后我们都能立刻获取其结果。
我们可以这么实现:使用ExecutorService的invokeAll(Collection<? extends Callable<T>> tasks)
获取所有的Future对象List<Future<T>>
。但是此时并不知道哪一个Future对象是已完成的,哪一个是未完成的,因此需要不断使用get(timeout=0)
去轮询该列表,判断任务是否已经成功返回。当然,使用轮询,这种实现并不好。
可以使用CompletionService接口完成该功能。CompletionService结合了Executor和BlockingQueue的功能。
BlockingQueue可以很方便的实现生产者-消费者模式。而CompletionService就是将生产者(新的异步线程生产的Future)和消费者(获取生产出来的Future,并进行处理)进行解耦,生产者只需要submit(Callable/Runnable)
将新的任务提交,就可以生产Future,而消费者只要take()
即可获取消费对象。
其默认具体实现类为ExecutorCompletionService。具体实现也很简单:
1. 首先构造ExecutorCompletionService对象的时候需要传入一个Executor,该executor为具体的任务提交者;
2. 然后在任务完成的时候,将结果Future加入BlockingQueue;
3. 最后使用take()
获取结果,其实就是直接使用BlockingQueue的take()
去获取结果Future,然后对结果调用get()
获取返回结果就行了。
所以CompletionService其实就是BlockingQueue + FutureTask(在ExecutorCompletionService中是QueueingFuture)的组合。
值得一提的是submit(Callable/Runnable)
方法:
public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
executor.execute(new QueueingFuture(f));
return f;
}
先使用newTaskFor(Callable)
将Callable对象封装成了RunnableFuture(FutureTask的接口),然后进一步包装成QueueingFuture,交给Executor执行即可。
QueueingFuture是私有静态嵌套类,其继承FutureTask并覆写了方法done()
:
/**
* FutureTask extension to enqueue upon completion
*/
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
为什么覆写done()
?因为在FutureTask中,一个任务执行完毕之后(无论成功还是抛异常,都叫完成),会调用done()
方法进行后续操作。FutureTask给的默认操作是什么都不做:protected void done() { }
。所以这里QueueingFuture通过覆写done()
方法protected void done() { completionQueue.add(task); }
,将其语义变为“一旦执行完成,就将结果加入到BlockingQueue队列中”,从而使得submit(Callable/Runnable)
会将任务结果RunnableFuture<T>
加入到阻塞队列中。
示例:
package example.concurrency.executor;
import org.apache.commons.lang3.RandomUtils;
import java.util.concurrent.*;
/**
* 提交一大堆并行任务,然后使用{@link CompletionService}及时获取{@link Future}结果。
* 避免了轮询。
*
* @author liuhaibo on 2018/06/13
*/
public class CompletionServiceDemo {
private static final int TASK_NUM = 20;
private static Callable<String> createCallableTask(String str) {
return () -> {
int time = RandomUtils.nextInt(10, 1000);
TimeUnit.MILLISECONDS.sleep(time);
System.out.println("Finished: " + Thread.currentThread().getName() + " time=" + time);
return str;
};
}
private static void doAnotherJob() throws InterruptedException {
Thread.sleep(1000);
}
public static void main(String... args) throws InterruptedException {
Executor executor = new ThreadPoolExecutor(
4,
8,
20L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
new ThreadPoolExecutor.AbortPolicy());
CompletionService<String> getStringService = new ExecutorCompletionService<>(executor);
// 提交一堆并行任务,最好是和CPU无关的,比如I/O密集型的下载任务
for (int i = 0; i < TASK_NUM; i++) {
getStringService.submit(createCallableTask("Hello => " + i));
}
// 同时并行做一些耗时的任务
doAnotherJob();
// 使用CompletionService,去BlockingQueue中取一些已完成的任务
// 另一种低级实现方法:保留每个任务关联的Future,并反复调用future.get(timeout=0)去轮询,获取任务完成情况
// 或者使用invokeAll,获取返回的List<Future<T>>,然后反复调用future.get(timeout=0)去轮询
try {
for (int i = 0; i < TASK_NUM; i++) {
Future<String> future = getStringService.take();
String s = future.get();
System.out.println(s);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(executor);
}
}
一个可能的结果:
Finished: pool-1-thread-1 time=27
Finished: pool-1-thread-4 time=235
Finished: pool-1-thread-3 time=345
Finished: pool-1-thread-4 time=282
Finished: pool-1-thread-2 time=770
Finished: pool-1-thread-2 time=160
Finished: pool-1-thread-1 time=948
Hello => 0
Hello => 3
Hello => 2
Hello => 5
Hello => 1
Hello => 8
Hello => 4
Finished: pool-1-thread-2 time=104
Hello => 9
Finished: pool-1-thread-3 time=740
Hello => 6
Finished: pool-1-thread-2 time=228
Hello => 11
Finished: pool-1-thread-4 time=890
Hello => 7
Finished: pool-1-thread-4 time=30
Hello => 14
Finished: pool-1-thread-2 time=189
Hello => 13
Finished: pool-1-thread-3 time=375
Hello => 12
Finished: pool-1-thread-2 time=228
Hello => 16
Finished: pool-1-thread-1 time=826
Hello => 10
Finished: pool-1-thread-3 time=446
Hello => 17
Finished: pool-1-thread-2 time=487
Hello => 18
Finished: pool-1-thread-4 time=757
Hello => 15
Finished: pool-1-thread-1 time=406
Hello => 19
java.util.concurrent.ThreadPoolExecutor@1f17ae12[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 20]
可以看到先完成的任务先放入BlockingQueue,然后在take()
的时候被立刻消费了,输出“Hello => i”。