5.2探究执行器(Executors)

来源:互联网 发布:cmd telnet 端口号 编辑:程序博客网 时间:2024/06/07 08:49

 你可以通过java提供的线程API来执行任务,像创建一个java.lang.Thread(newRunnableTask()).start();这个应用在可以执行多任务的机器表现更为明显(运行当前线程,创建线程和在线程池中随意选择执行线程)。

       Note  一个任务就是一个对象,它会继承java.lang.Runnable的接口(执行的任务)或者继承java.util.concurrent.Callable的接口(立刻执行的任务)。

     并发工具包括高水平的执行器(executors),取代低水平的线程任务执行应用的工具。一个执行对象如果直接或间接继承java.util.concurrent.Executor接口,那么它就会从任务执行的器中解耦。

       Note  这个执行器框架的运用接口,使其从任务执行中解耦任务,就像是集合框架运用接口从list、sets、queues和maps的接口中解耦是一样的。解耦的好处是代码灵活的容易维护。

     使用Excutor时,我们需要声明它的唯一方法execute(Runnable runnable),那么就会执行runnable命名的任务。如果runnable为nul将会报错:java.lang.NullPointExcetion,如果不能执行runnable的任务,那么会报错:java.util.concurrent.RejectedExecutionException。

       Note   如果一个线程被强迫停止或不想接收新有任务时,将会抛出RejectedExecutionException.当然,如果执行器没有足够的空间去储存任务时,也会报出这个错误。(可能执行器使用一个有界的阻塞队列去储存任务和为个队列满了。)

     下面例子使用Executor,与前面讲的new Thread(new RunnableTask()).start()的方法是一样的。如:

      Executor executor = ···;//···代表着相同的执行器,创建executor.execute(new //RunnableTask());

      尽管Executor是很容易被使用的,但是这个接口存如下的限制:

        Executor的焦点是在Runnable上。因为Runnable的run()方法不能够返回任何值,这里也没有任务方式让一个执行的任务返回值给它的请求者。

        Executor没有提供任何的方法,让一个正在执行任务的线程取消或终止。

        Executor不能够执行一个集合的执行任务。

        Executor不能够让一个应用强迫停止应用。

     以上的这些限制可以使用java.util.concurrent.ExcutorService的接口来解决,这个接口继承Executor和它的应用是一个线程池。

 

表5-1 ExecutorService的方法

方法

说明

boolean awaitTermination(long timeout, TimeUnit unit)

阻塞直到所有任务的线程都完成。如果线程超时或当前线程强迫停止,那么线程将会停止请求。当executor正常结束,那么将会返回true,否则在线程执行期间超时,将返回false。当线程被打断,那么将会报错:java.lang.InterruptedException.

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)

执行每一个即时响应的任务集合时,将会返回java.util.concurrent.Future的java.util.List。List的集合中包含着任务正常结束状态或由于错误而报出异常的状态。Futures的List是在相同的有序队列中的,这些任务队列是通过任务的迭代器(iterator)返回的。如果线程在等待时被打断或任务任务没有完成就被取消,那么这个方法将会抛出InterruptedException的错误。如果tasks为空或任务一个元素为空,那么将会抛出NullPointException。如果一个任务不能被排序来执行,那么将会抛出RejectedExecutionException。

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)

执行每一个即时响应的任务集合时,将会返回java.util.concurrent.Future的java.util.List。List的集合中包含着任务正常结束状态或由于错误而报出异常的状态,或超时终止。任务如果取消了,那么将会终止。Futures的List是在相同的有序队列中的,这些任务队列是通过任务的迭代器(iterator)返回的。如果线程在等待时被打断或任务任务没有完成就被取消,那么这个方法将会抛出InterruptedException的错误。如果tasks为空或任务一个元素为空,或unit为空,那么将会抛出NullPointException。如果一个任务不能被排序来执行,那么将会抛出RejectedExecutionException。

<T> T invokeAny(Collection<? extends Callable<T>> tasks)

执行线程所给的任务,返回任何任务的成功执行。也就是说执行的任务不能报错。如果执行的任务不正常或报错,那么任务(tasks)将不成功或不能正常结束。如果线程在等待时候被打断,那么将会抛出InterruptedException;如果tasks或任何元素为空,那么将会抛出NullPointerException;当tasks中没有元素,那么将会抛出java.lang.IllegalArgumentException.当没有任务成功执行,将抛出ExecutionException; 如果一个任务不能被排序来执行,那么将会抛出RejectedExecutionException。

<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)

执行线程所给的任务,返回任何任务的成功执行。也就是说执行的任务不能报错。如果执行的任务不正常或报错或超时,那么任务(tasks)将不成功或不能正常结束。如果线程在等待时候被打断,那么将会抛出InterruptedException;如果tasks或任何元素为null或unit为null,那么将会抛出NullPointerException;当tasks中没有元素,那么将会抛出java.lang.IllegalArgumentException.当没有任务成功执行,将抛出ExecutionException; 如果一个任务不能被排序来执行,那么将会抛出RejectedExecutionException。当任务成功执行之前却超时了,那么将会抛出TimeoutException。

boolean isShutdown()

当执行器被关闭,那么将返回true,其它情况将返回false.

boolean isTerminated()

当所有任务都执行成功并且关闭,返回true;其它情况返回false。如果执行shutdown()或shutdownNow()方法,那么这个方法将不会返回true.

void shutdown()

在所有的任务都提交之后,就开始的序的关闭任务,这期间将不会接受任何新有的任务。如果执行器(excutors)被关闭了,那么执行这个方法是没有任何作用的。如果之间的任务已经成功执行并提交了,那么这个方法将不会等待,而是立即执行。如果需要等待,那么就使用awaitTermination()的方法。

List<Runnable> shutdownNow()

尝试去停止正在执行的任务,或停止正在等待的任务的线程,那么将会返回的是等待执行任务的集合。这里不能够保证可以顺利停止每一个任务,比如,应用被Thread.interrupt()取消了,那么任务请求去停止,是没有作用的。

<T> Future<T> submit(Callable<T> task)

提交一个即时响应的任务,将会返回Future的实例,代表着等待任务的处理结果返回。Future的实例中的get()方法,返回任务的成功执行情况。当任务(task)不能有序执行时,将会抛出RejectedExecutionException.如果你想更快速的阻塞一个等待的任务时,那么你可以用这个方法result = exec.submit(aCallable).get()

Future<?> submit(Runnable task)

提交一个运行的任务去执行,直到有结果返回。Future 的实例中的get()方法返回任务成功执行与否。如果这个任务不能有序的执行,那么将会抛出RejectedExecutionException;如果参数task为null,那么将会抛出NullPointerException.

<T> Future<T> submit(Runnable task, T result)

提交一个运行的任务去执行,直到有结果返回。Future 的实例中的get()方法返回任务成功执行与否,通过result来表现。如果这个任务不能有序的执行,那么将会抛出RejectedExecutionException;如果参数task为null,那么将会抛出NullPointerException.

 

表格5-1涉及到java.util.concurrent.TimeUnit,这个工具类中包含执行时间期间的粒度,其中包含的枚举有:DAYS、HOURS、MICROSECONDS、MILLISECONDS、MINUTES、NANOSECONDS、和SECONDS。与此同时,TimeUnit声明时间转换的工具(如:long toHours(long duration)),和运行和延迟的操作(如:voidsleep(long timeout))。

表格5-1涉及到即时响应的任务(callabletasks)。它不像Runnable中的run()方法,不能够返回值和不能够抛出异常。Callable<V> 的 V call()方法将会返回值和抛出异常的,因为它声明了throws Exception。

最后,表格5-1涉及到了Future的接口,这个代表着同步计算的结果。它返回的结果是当作一个future,通常它是无效的,除非的相同的时刻的future.Future的属性是Future<V>,提供方法去取消一个任务,因为它可以返回值和确保任务是否完成。表格5-2描述了Future的方法。

 

表格5-2 Future的方法

方法

说明

boolean cancel(boolean mayInterrupIfRunning)

尝试取消执行的任务,和取消一个任务返回true;其它情况将返回false(这个任务在cancel()之前不能够正常取消)。当一个任务已经完成、取消或其它原因不能被取消,那么调用这个方法将会失败。如果已经成功或任务尚未开始,那么这个任务将不会执行。如果任务已经开始,那么可以通过mayInterruptIfRunning声明true或false,设置是否将任务停止或打断。在运行之后,调用isDone()方法返回true,当调用cancel()方法返回true,那么isCancelled()总是返回true。

V get()

如果需要任务成功执行然后返回结果,那么可以调用此方法。当这个方法在取消之前,任务先取消了,那么将会抛出CancellaException.当任务的异常时,就会抛出ExecutionException.如果当前的线程在等待期间被打断,那么将会抛出InterruptedException

V get(long timeout, TimeUnit nuit)

等待timeout的时间,等待任务成功执行或有需要的话返回结果。当这个方法在取消之前,任务先取消了,那么将会抛出CancellaException.当任务的异常时,就会抛出ExecutionException.如果当前的线程在等待期间被打断,那么将会抛出InterruptedException如果方法超时了,那么将要抛出TimeoutException.

boolean isCancelled()

如果任务正常执行之前被取消了,那么这个方法将会返回true,其它情况将会返回false。

boolean isDone()

如果任务结束,那么返回true,其它情况返回false。结束情况,可能是非正常结束,异常或取消。这些情况,方法将返回true.

 

想像一下,你试图写这样的一个应用,提供图形表格让用户输入单词。用户输入单词之后,就可以通过在线的词典来查找这个单词,并将查询的结果显示给用户。

因为在线的应用可能会因为网速而比较慢,而且用户的交互需要请求和响应。你需要将请求的单词(这个任务)放到执行器中执行,而且返回这个线程执行的结果。下面的例子将会使用ExecutorService、Callable和Future来实现这个功能。

ExecutorService executor = ...; // ... represents some executor creationFuture<String[]> taskFuture =executor.submit(new Callable<String[]>(){@Overridepublic String[] call(){String[] entries = ...;// Access online dictionaries// with search word and populate// entries with their resulting// entries.return entries;}});// Do stuff.String entries = taskFuture.get();


之后在相同的方法中包含一个执行器(executor),这个例子线程提交一个即时响应的任务给执行器(executor)。为了去控制任务的执行和获取结果,submit()方法将会更快速地返回一个接口给Future对象。 

       Note   java.util.concurrent.ScheduleExecutorService的接口继承ExecutorService。和描述一个执行器让你的任务列表执行,或线程被延迟了,那么将会一直循环执行。

尽管你可以创建自己的Excutor、ExecutorService和ScheduledExcutorService的实现工具(例如,DirectExcutorimplements Excutor{@override public void execute(Runnable r){r.run();}} ----运行这个执行器(executor)将会调用线程)。这个简单的例子也可以用java.concurrent.Executors来替换。

       Note  如果你想要创建自己的ExcutorService的应用工具,那你java.util.concurrent.AbstractExecutorService和java.util.concurrent.FutureTask的类是非常有用的。

这个Executors的作用声明了几个类的方法可以返回多个的实例,如ExecutorService和ScheduledExecutorService的应用工具(或者其它的实例应用)。这个类的方法可以有如下的功效:

        创建和返回一个ExecutorService的实例,这个构造一个普通的应用或构造设置。

        创建和返回一个ScheduledExecutorSevice的实例,这个构造一个普通的应用或构造设置。

        创建和返回一个“擦除(wrapped)”的ExcutorService 或ScheduledExecutorService的实例;这个对于具体应用方法是难以见效的。

        创建和返回一个java.util.concurrent.ThreadFactory的实例(这个类是继承ThreadFactory的接口),为了创建一个线程对象。

        创建和返回一个Callable的实例而不是一个关闭的对象,以至于执行这个方法可以请求Callable的参数(例如,ExecutorService 的submit(Callable)方法)。

例如,staticExecutorService newFixedThreadPool(int nThreads)创建一个线程池,这个线程池会在没的边界的队列中使用固定的参数。在大多数情况下,nThread线程是活跃的进程任务。当所有线程都在运行时,如果有新的线程提交任务,那么将处于等待状态。

如果一个执行器(executor)关闭,那么当前线程在执行期间就会失败,所以当前执行的线程也就会终止。这个的话,线程池就会让出这个空间给其它需要的线程来执行。线程明确停止了,但是线程池还是存在的。这个时候就会抛出IllegalArgumentException,因为nThreads为‘0’或无效的值。

       Note   创建一个线程而且要经常提交任务,那么线程池作用就是用来消除这样的高开销。线程的创建不是低开销的,因为创建多个线程会影响到系统的性能。

     你可能经常运用executors、runnable、callables和futures在文件和网络的输入/输出的应用中。执行一个长的计算,你可能也会用到。下面的例子给出了运用executor、callable、future在欧拉公式(Euler)的计算。

package com.owen.thread.chapter5;import java.math.BigDecimal;import java.math.MathContext;import java.math.RoundingMode;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class CalculateE{final static int LASTITER = 17;public static void main(String[] args){ExecutorService executor = Executors.newFixedThreadPool(1);Callable<BigDecimal> callable;callable = new Callable<BigDecimal>(){@Overridepublic BigDecimal call(){MathContext mc = new MathContext(100, RoundingMode.HALF_UP);BigDecimal result = BigDecimal.ZERO;for (int i = 0; i <= LASTITER; i++){BigDecimal factorial = factorial(new BigDecimal(i));BigDecimal res = BigDecimal.ONE.divide(factorial, mc);result = result.add(res);}return result;}public BigDecimal factorial(BigDecimal n){if (n.equals(BigDecimal.ZERO))return BigDecimal.ONE;elsereturn n.multiply(factorial(n.subtract(BigDecimal.ONE)));}};Future<BigDecimal> taskFuture = executor.submit(callable);try{while (!taskFuture.isDone())System.out.println("waiting");System.out.println(taskFuture.get());} catch (ExecutionException ee){System.err.println("task threw an exception");System.err.println(ee);} catch (InterruptedException ie){System.err.println("interrupted while waiting");}executor.shutdownNow();}}


     默认的主线程执行main()方法,这个方法包含一个执行器(executor),通过Excutors的newFixedThreadPool()方法来调用。在实例化Callable的接口时,它包含了一个匿名内部类,和通过提交任务给执行器(executor),接收一个Future实例的请求。 

     提交任务之后,一个线程将会执行相同的工作直到它的请求等到任务的结果返回。     我模拟这样的工作,是主线程会一直处于等待的状态,直到Future的实例属性isDone()方法返回true.(在实际的应用中,我们应该避免这个循环。)这里最关键的是,主线程调用get()的方法去获取结果,然后输出。之后,主线程会停止执行器(executor)。

       Caution  当一个应用执行完之后,停止这个执行器是非常关键的。否则这个应用可能不能终止。在上面的例子中调用shutdownNow()方法(你也可以用shutdown()方法)。

     callable的call()方法计算的公式是e = 1/0! + 1/1! + 1/2!...这个一系列的计算可以写为1/n!,n的范围是从0到无穷大。

     在call()的方法中,我们实例化java.math.MathContext,这个封装了一个计算的精确度和舍入的方式。我选择了100为最高的限制 e的精确度,和我选择HALF_UP为舍入方式。

       Tip   提高精确度,最终的结果将会起来起接近于e的值。

     call()方法中实例化一个java.math.BigDecimal的局部变量result的实例值是BigDecimal.ZERO。它将放入到一个计算阶梯因子的循环中,通过阶梯因子分隔BigDecimal.ONE,和添加分隔结果给result。

     divide()方法将MathContext实例作为第二个参数去明确舍入的信息。(如果我指定作为精确度,那么将会出现nonterminating decimal expansion,也就是说商数分隔的结果是不正确的,如0.33333…。实际会抛出java.lang.ArithmeticException。执行器(executor)还会再次抛出ExecutionException)。

     执行上面的代码,输入的结果可能是:

waiting

waiting

waiting

waiting

waiting

waiting

waiting

waiting

2.718281828459045070516047795848605061178979635251032698900735004065225042504843314055887974344245741730039454062711

原创粉丝点击