上一章我们介绍了ExecutorService接口,以及它的实现类ThreadPoolExecutor。
那么这里我们将介绍Executors类,它可以更进一步的简化我们的工作,直接创建一些预定义过的线程池
这个类也在java.util.concurrent包下。它有如下的几个比较常用的创建线程池的方法:
一:newFixedThreadPool
创建一个线程可重用的并且线程数固定的线程池。
nThreads - 池中的线程数
threadFactory - 创建新线程时使用的工厂
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads,ThreadFactory threadFactory)
二:newCachedThreadPool
创建一个可根据实际情况动态维持线程数的线程池,当任务到来时,如果有已经构造好的空闲线程将重用它们,不创建新的线程。
如果没有可用的空闲线程,则创建一个新线程并添加到池中。并且会终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因
此,长时间保持空闲的线程池不会使用任何资源。
threadFactory - 创建新线程时使用的工厂
public static ExecutorService newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
三:newSingleThreadExecutor
创建一个使用单个线程的 ExecutorService,以无界队列方式来运行该线程。
threadFactory - 创建新线程时使用的工厂
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
关于这3类的线程池我引用一下《thinking in java》对它们的描述:
使用FixedThreadPool,你可以一次性的预先执行代价高昂的线程分配,因而也就可以限制线程数的数量,这可以节省时间,因为你不用为每一个任务都固定的付出创建线程的开销。在事件驱动的系统中,需要线程的事件处理器,通过直接从池中获取线程,也可以如你所愿地尽快得到服务。你不会滥用可获得的资源,因为FixedThreadPool使用的Thread对象的数量是有界的。
对于CachedThreadPool,它在程序的执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的ExecutorService的首选。只有当这种方式会引发问题时,你才需要切换到FixedThreadPool。
SingleThreadExecutor就是线程数量为1的FixedThreadPool。这对于你希望在另一个线程中连续运行的任何事物(长期存活的任务)来说,都是非常有用的,例如监听进入套接字连接的任务。
=======================================================================
我们再来看一下它们的源码,比如FixedThreadPool:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
所以在本质上,就是创建了一个我们上一篇文章介绍过的ThreadPoolExecutor类。
=======================================================================
接下来我们写一个完整的,使用它们的例子:
下面给出了一个网络服务的简单结构,这里线程池中的线程作为传入的请求。它使用了预先配置的 Executors.newFixedThreadPool(int) 方法创建线程池:
class NetworkService implements Runnable {
private final ServerSocket serverSocket;
private final ExecutorService pool;
public NetworkService(int port, int poolSize)
throws IOException {
serverSocket = new ServerSocket(port);
pool = Executors.newFixedThreadPool(poolSize);
}
public void run() { // run the service
try {
for (;;) {
pool.execute(new Handler(serverSocket.accept()));
}
} catch (IOException ex) {
pool.shutdown();
}
}
}
class Handler implements Runnable {
private final Socket socket;
Handler(Socket socket) { this.socket = socket; }
public void run() {
// read and service request on socket
}
}
============================
可以返回结果的“Runnable”
我们知道ExecutorService框架使用Runnble作为其基本的任务表示形式。但是它有很大的局限性就是它不能返回一个值,或者抛出一个受检查的异常
实际上许多需要知道执行结果的任务都需要一定的执行时间的,比如执行数据库的查询,或者从网络上获取一些资源,更或者进行一些比较复杂的计算。对于这些任务Callable是一种更好的抽象。你可以把它当成有返回值的“Runnable”,它的call()方法就相当于Runnable的run()方法。但关键是它的call()方法将返回一个值,并可能抛出一个异常。
那么怎么在ExecutorService框架中很好的使用Callable呢。这里就需要使用到Feture接口。
ExecutorService中的所有submit方法都将返回一个Future对象,从而可以将Callable提交给ExecutorService,并得到一个Future用来获得任务的执行结果或者取消任务。
public class Test {
private final ExecutorService executor = Executors.newFixedThreadPool(3);
public void runTheTask(){
Future<String> future = executor.submit(new Callable<String>(){
@Override
public String call() throws Exception {
Thread.sleep(3000);
return "result";
}
});
try {
System.out.println( future.get());
} catch (InterruptedException | ExecutionException e) {
}
}
public static void main(String args[]){
Test t = new Test();
t.runTheTask();
}
}
运行后在future.get()步会阻塞 直到3秒后 返回结果“result”
上面的线程只执行了一个Callable任务。
但有某些情景下需要我们执行好几个Callable任务,并且要获得它们的返回结果,代码就变得很不好控制了
public class Test {
private final ExecutorService executor = Executors.newFixedThreadPool(3);
public void runTheTask(){
Future<String> future1 = executor.submit(new Callable<String>(){
@Override
public String call() throws Exception {
Thread.sleep(3000);
return "result1";
}
});
Future<String> future2 = executor.submit(new Callable<String>(){
@Override
public String call() throws Exception {
Thread.sleep(3000);
return "result2";
}
});
Future<String> future3 = executor.submit(new Callable<String>(){
@Override
public String call() throws Exception {
Thread.sleep(3000);
return "result3";
}
});
try {
System.out.println( future1.get());
System.out.println( future2.get());
System.out.println( future3.get());
} catch (InterruptedException | ExecutionException e) {
}
}
public static void main(String args[]){
Test t = new Test();
t.runTheTask();
}
}
可以看到 上面的代码非常难看而且不好控制。
幸运的是,在这种情况下我们可以使用CompletionService来实现
CompletionService将ExecutorService和BlockingQueue功能融合在了一起。
你可以将Callable任务提交给它来执行,它执行完返回的Future结果会放进BlockingQueue中。
你再用类似于队列操作的take和poll方法来获得已完成的结果。
public class Test2 {
private final ExecutorService executor = Executors.newFixedThreadPool(3);
public void runTasks(){
CompletionService<String> completionService = new ExecutorCompletionService<String>(executor);
completionService.submit(new Callable<String>(){
@Override
public String call() throws Exception {
return "result1";
}
});
completionService.submit(new Callable<String>(){
@Override
public String call() throws Exception {
return "result2";
}
});
completionService.submit(new Callable<String>(){
@Override
public String call() throws Exception {
return "result3";
}
});
for(int i=0;i<3;i++){
try {
Future<String> f = completionService.take();
System.out.println(f.get());
} catch (InterruptedException e) {
} catch (ExecutionException e) {
}
}
}
public static void main(String[] args) {
Test2 t = new Test2();
t.runTasks();
}
}
我们可以看到我们使用了一个ExecutorService作为参数来初始化 CompletionService。多个 CompletionService可以共享一个ExecutorService。因此可以创建一个对于特定计算私有,又能共享一个ExecutorService的应用。
============================
这里我们再顺带介绍一下线程池的关闭——shutdown()和shutdownNow()方法。
我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,但是它们的实现原理不同,shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。对shutdown()方法的调用可以防止新的任务被提交给这个线程池,当前线程将继续运行在shutdown()被调用之前提交的所有任务
shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。
我们加入shutdown()和shutdownNow()方法来完善一下上面这个例子:
void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // 防止新的任务被提交上来
try {
// 等待当前已经存在的任务执行完
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // 如果过了指定时间还有任务没有完成,立马停止它们
// 等待任务响应取消命令
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();
}
}
至此,对Executors的简单了解结束。下一篇文章我们将对上一篇文章介绍的ThreadPoolExecutor类做一下更深入的研究。