本文翻译自http://tutorials.jenkov.com/java-util-concurrent/executorservice.html,人工翻译,仅供学习交流。
ExecutorService
java.util.concurrent.ExecutorService接口是一种能够在后台并发执行任务的异步执行机制。本文中,我将会介绍如何创建ExecutorService,如何向它提交要执行的任务,如何查看这些任务的结果以及当需要时,如何再次关闭ExecutorService。
任务委派
下面的图表演示了一个线程如何将一个任务委托给Java ExecutorService进行异步
一旦线程将任务委托给ExecutorService,线程继续它自己的执行,独立于该任务的执行。ExecutorService并发地执行任务,独立于提交的线程。
Java ExecutorService示例
在我们深入到ExecutorService之前,先看个样例。下面是一个简单的Java ExecutorService示例:
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
executorService.shutdown();
首先,使用Executors newFixedThreadPool()工厂方法创建ExecutorService,这将创建一个线程池,其中有10个线程在执行任务。
其次,Runnable接口的匿名实现被传递给execute()方法。这将导致ExecutorService中的一个线程执行Runnable。
在本教程中,您将看到更多关于如何使用ExecutorService的示例。这个例子只是提供给你一个如何使用一个ExecutorService在后台执行任务的快速概述。
Java ExecutorService实现类
Java ExecutorService非常类似于线程池,concurrent包中ExecutorService接口的实现也是一个线程池实现。如果您想了解如何在内部实现ExecutorService接口,阅读上面的教程。
因为ExecutorService是一个接口,为了使用它,你需要它的实现类。ExecutorService在java.util.concurrent包中有以下实现:
- ThreadPoolExecutor
- ScheduledThreadPoolExecutor
创建ExecutorService
如何创建ExecutorService取决于您使用的实现类。你也可以使用Executors工厂类来创建ExecutorService实例。下面是一些创建ExecutorService的示例:
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
ExecutorService executorService2 = Executors.newFixedThreadPool(10);
ExecutorService executorService3 = Executors.newScheduledThreadPool(10);
ExecutorService用法
有几种不同的方法可以将任务委托给ExecutorService执行:
- execute(Runnable)
- submit(Runnable)
- submit(Callable)
- invokeAny(…)
- invokeAll(…)
我将在下面的部分中介绍这些方法。
Execute Runnable
ExecutorService execute(Runnable) 方法获取一个runnable对象,然后异步执行它。下面是一个使用ExecutorService执行Runnable的例子:
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
executorService.shutdown();
这种操作没有办法获得执行的Runnable的结果。如果需要结果,必须使用Callable(将在下面的部分中进行解释)。
Submit Runnable
ExecutorService submit(Runnable)方法也是获取Runnable实现,但会返回一个Future对象。这个Future对象可以用来检查Runnable是否已经完成执行。下面是一个Java ExecutorService submit()的例子:
Future future = executorService.submit(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
future.get(); //returns null if the task has finished correctly.
submit()方法返回一个Java Future对象,该对象可用于检查Runnable是否完成。
Submit Callable
ExecutorService submit(Callable) 和 submit(Runnable)是相似的,除了用Callable对象代替Runnable对象。稍后将解释可调用对象和可运行对象之间的确切区别。Callable的结果可以通过submit(Callable) 方法返回的Java Future对象获得。下面是ExecutorService调用的示例:
Future future = executorService.submit(new Callable(){
public Object call() throws Exception {
System.out.println("Asynchronous Callable");
return "Callable Result";
}
});
System.out.println("future.get() = " + future.get());
上面的代码示例将输出如下:
Asynchronous Callable
future.get() = Callable Result
invokeAny()
invokeAny()方法接受一组Callable对象,调用此方法不会返回Future,但会返回一个Callable对象的结果。您无法保证得到哪个Callable结果,只有一个能完成的。
如果一个Callable完成,那么从invokeAny()返回一个结果,然后其余的Callable实例会被取消。
如果其中一个任务完成(或抛出异常),其他的Callable都被取消了。下面是一个代码示例:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 3";
}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();
这个代码示例将打印出给定集合中的一个Callable对象返回的对象,我试着运行了几次,结果发生了变化。有时是“任务1”,有时是“任务2”。
invokeAll()
invokeAll()方法调用作为参数传递给它的集合中的所有Callable对象。invokeAll()返回一个Future对象列表,通过该列表可以获得每个Callable的执行结果。
请记住,任务可能会由于异常而结束,所以它可能没有成功。在Future上没有办法区分这两者。下面是一个代码示例:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 3";
}
});
List<Future<String>> futures = executorService.invokeAll(callables);
for(Future<String> future : futures){
System.out.println("future.get = " + future.get());
}
executorService.shutdown();
Runnable vs. Callable
Runnable接口与Callable接口非常相似。Runnable接口是可以由线程或 ExecutorService并发执行的任务,Callable只能由ExecutorService执行。两个接口都只有一个方法。不过,Callable和Runnable接口之间有一个微小的区别,在接口声明里更容易看到。首先是Runnable接口声明:
public interface Runnable {
public void run();
}
下面是Callable接口声明:
public interface Callable{
public Object call() throws Exception;
}
Runnable run()方法和Callable call()方法之间的主要区别是call()方法可以从方法调用中返回一个Object。call()和run()之间的另一个区别是call()可以抛出异常,,而run()不能(除了未检查的异常- RuntimeException的子类)。
如果需要将任务提交给Java ExecutorService,并且需要任务的结果,然后你需要让你的任务实现Callable接口。否则任务也可以只实现Runnable接口。
Cancel Task
当任务已经提交时,可以通过调用返回的Future的cancel()方法来取消提交给Java ExecutorService的任务。如果任务还没有开始,才可能取消任务。这里有一个样例:
future.cancel();
ExecutorService 关闭
当你使用完Java ExecutorService时,你应该关闭它,因此线程不会继续运行。如果你的应用程序是通过main()方法启动的,你的主线程退出你的应用程序,如果您的应用程序中有一个活动的ExexutorService,应用程序将继续运行。这个ExecutorService中的活动线程会阻止JVM关闭。
shutdown()
要终止ExecutorService内部的线程,可以调用它的shutdown()方法。ExecutorService不会立即关闭,但它将不再接受新的任务,一旦所有线程都完成了当前任务,ExecutorService就会关闭。调用shutdown()之前提交给ExecutorService的所有任务都是执行完成的。下面是一个执行Java ExecutorService关闭的示例:
executorService.shutdown();
shutdownNow()
如果您想立即关闭ExecutorService,可以调用shutdownNow()方法,这将尝试立即停止所有正在执行的任务,并跳过所有提交但未处理的任务。对于正在执行的任务没有任何保证,也许他们会停止,也许会执行到最后。这是最好的尝试。下面是调用ExecutorService shutdownNow的示例:
executorService.shutdownNow();
awaitTermination()
直到ExecutorService已经完全关闭,或者直到发生给定的超时,ExecutorService awaitterminate()方法将阻塞调用它的线程。通常在调用shutdown()或shutdownNow()之后调用awaitterminate()方法。下面是调用ExecutorService awaitTermination()的示例:
executorService.shutdown();
executorService.awaitTermination(10_000L, TimeUnit.MILLISECONDS );