java线程池学习(四) —— Executors类

来源:互联网 发布:mac图片怎么保存 编辑:程序博客网 时间:2024/06/01 12:33

上一章我们介绍了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>(){@Overridepublic 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>(){@Overridepublic String call() throws Exception {Thread.sleep(3000);return "result1";}});Future<String> future2 = executor.submit(new Callable<String>(){@Overridepublic String call() throws Exception {Thread.sleep(3000);return "result2";}});Future<String> future3 = executor.submit(new Callable<String>(){@Overridepublic 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>(){@Overridepublic String call() throws Exception {return "result1";}});completionService.submit(new Callable<String>(){@Overridepublic String call() throws Exception {return "result2";}});completionService.submit(new Callable<String>(){@Overridepublic 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类做一下更深入的研究。

1 0
原创粉丝点击