在Java多线程学习笔记(一)(二)(三)中,我们已经对多线程有了基本的了解,但是Runnable接口还不够完善,如果想得到线程的执行结果,可能要绕个弯路。为了弥补不足,Java引入了Callable接口,相当于Runnable接口的增强版。但是如果我想取消任务,Java引入了Future类。但是Future还不够完善,所以可以加强:CompletionService.Future和CallableCallable是Runnable的加强版,可以使用ThreadPoolExecutor的另外一个submit方法来提交任务。提交方法的声明如下:publicFuturesubmit(Callabletask)Callable方法只有一个方法:Vcall()throwsException;call方法的返回值表示对应任务的处理结果,其类型V由Callable接口的类型参数指定;call方法表示任务在执行期间可以抛出异常。Runnable接口中的run方法既不返回值也不抛出异常。Executors.callable(Runnabletask,Tresult)可以将Runnable接口转换为Callable接口实例。接下来,让我们将注意力转移到Future接口的返回值上。Future接口实例可以看作是提交给线程池执行的任务的句柄。有了Future实例,我们就可以获得线程执行的返回结果。Future.get()方法可以用来获取task参数指定的任务的处理结果。该方法的声明如下:Vget()throwsInterruptedException,ExecutionException;调用Future.get()方法时,如果对应的任务还没有执行完,Future.get()会导致当前线程挂起,直到对应的任务执行完毕终止(包括正常终止和抛出异常终止).所以Future.get()方法是一个阻塞方法,可以抛出InterruptedException表示可以响应中断。还假设在执行响应任务期间抛出任意异常。调用该异常(ExecutionException)的getCause方法返回任务执行过程中抛出的异常。因此客户端代码可以通过捕获Future.get()调用抛出的异常来获知执行过程中抛出的异常。因为在任务执行完成之前调用Future.get()方法获取任务的处理结果会导致上下文切换等待,所以如果需要获取执行结果的任务应该提交给线程池越早越好,越晚调用Future.get()方法来获取任务的处理结果,线程池正好利用这个时间来执行提交的任务。Future接口也支持取消任务:booleancancel(booleanmayInterruptIfRunning);如果任务已经执行或已经取消,或其他原因无法取消,该方法将返回false。如果取消成功,任务将不会被执行,任务的执行状态分为两种,一种是尚未执行,一种是已经在执行中。对于执行中的任务,参数mayInterruptIfRunning表示是否通过中断来终止线程的执行。此方法执行完成后,如果返回true,则关联的调用isDone和isCancelled将始终返回true。Future.isDone()方法可以检测相应的任务是否已经执行。该方法在任务完成、执行过程中抛出异常或取消时返回true。Future.get()会导致其执行线程无限期地等待,直到相应任务的执行结束。但有时处理不当可能会导致无限等待。为了避免这种无限等待,此时我们可以使用另一个版本的get方法:Vget(longtimeout,TimeUnitunit)throwsInterruptedException,ExecutionException,TimeoutException;指定等待时间,如果等待时间未完成,该方法将抛出TimeoutException。注意方法参数指定的超时时间只是用来控制等待对应任务处理结果的时间,并不是限制任务本身的执行时间。因此,推荐的做法是客户端通常需要在捕获到TimeoutException后取消任务的执行。下面是Callable和Future的一个例子:publicclassFutureDemo{publicstaticvoidmain(String[]args){Futurefuture=threadPool.submit(()->{TimeUnit.SECONDS.sleep(1);inti=1/0;return"helloworld";});字符串结果=空;尝试{结果=future.get();}catch(InterruptedExceptione){e.printStackTrace();}catch(ExecutionExceptione){//打印出来的是被0除的异常System.out.println(e.getCause());}System.out.println(结果);//直到Future任务执行结束,线程返回Execution结果,这一行才会被执行threadPool.shutdownNow();}}异步执行框架ExecutorRunnable和Callable接口是对任务执行逻辑的抽象,我们可以在相应的方法中编写相应的任务执行逻辑。和爪哇。util.concurretn.Executor接口是任务执行的抽象。任务的提交者只需要调用待执行的Executor的execute方法即可,无需关心具体的执行细节。例如,任务执行前要做什么,任务是使用专用线程还是线程池执行,多个任务的执行顺序。可见Executor可以将任务的提交与任务执行的具体细节解耦(Decoupling)。与任务处理逻辑的抽象类似,任务执行的抽象也可以给我们带来信息隐藏和关注点分离的好处。那么这种解耦有什么好处呢?一个明显的例子就是能够在一定程度上屏蔽同步执行和异步执行的区别。对于同一个任务(Runnable或Callable的实例),如果我们将这个任务提交给ThreadPoolExecutor(它实现了Executor)执行,那么它就是异步执行的;如果是Executor执行如下所示:publicclassSynchronousExecutorimplementsExecutor{@Overridepublicvoidexecute(Runnablecommand){}}那么这个任务就是同步执行的。那么任务是同步执行还是异步执行,对于调用者来说并没有太大区别。Executor接口比较简单,功能也比较有限:不能将任务的执行结果返回给任务提交方。Executor的实现类往往会维护一定数量的工作线程。当我们不再需要Executor实例时,往往需要停止其内部维护的工作线程来释放相应的资源,但是Executor接口并没有定义相应的方法。目前的需求是我们需要加强Executor接口,但是不能直接修改Executor。这里的方法是做一个子接口,即ExecutorService。ExecutorService继承了Executor,内部定义了几个submit方法,可以返回任务的执行结果。.ExecutorService接口还定义了shutdown方法和shutdownNow来关闭对应的服务,释放资源。增强功能:CompletionService虽然Future接口可以让我们轻松获取异步任务的处理结果,但是如果我们需要一次性提交一批任务的处理结果,只使用Future接口编写的代码会相当繁琐。CompletionService接口就是为了解决这个问题而诞生的。简单看一下CompletionService提供的方法:submit方法和ThreadPoolExecutor的一个submit方法一样:Futuresubmit(Callabletask);如果我们想获取批量提交的异步任务的执行结果,那么我们可以使用CompletionService接口专门为此定义的方法:Futuretake()throwsInterruptedException;该方法类似于BlockingQueue的take(),是一个阻塞方法,其返回值是一个在批量提交异步任务实例中已经完成一个已执行任务的Future。我们可以通过Future实例获取异步任务的执行结果。如果在调用take方法时没有任务已经执行,调用take方法的线程将被阻塞,直到异步任务执行结束,调用take方法的线程不会被恢复。如果你想获取异步任务的结果,但是如果没有完成的异步任务,那么CompletionService也提供了一个非阻塞的方法来获取异步任务处理的结果:Futurepoll();//如果没有完成的异步任务task,则返回nullFuturepoll(longtimeout,TimeUnitunit)throwsInterruptedException;//如果在指定时间内没有完成异步任务,则返回null。Java标准库提供的CompletionService的实现类是ExecutorCompletionService。联系上面提到的Executor,我们可以大致推断出这个类是Executor和CompletionService的组合。我们随便找一个ExecutorCompletionService的构造函数来看一下:publicExecutorCompletionService(Executorexecutor,BlockingQueue>completionQueue)executor就是任务的执行者。任务执行完成后,将执行结果的Future实例放入BlockingQueueCallable补充:FutureTask概述无论是Runnable实例还是Callable实例代表的任务,只要我们提交到线程池中进行执行,那么我可以认为这个任务是一个异步任务。使用Runnable实例来表示异步任务的好处是可以将任务交给专门的工作线程执行,或者提交给线程池执行。缺点是我们不能直接获取任务的执行结果。使用Callable实例来表示异步任务的好处是我们可以通过ThreadPoolExecutor的返回值获取任务的执行结果,但不能直接交给专门的工作线程执行。因此,使用Callable来表示异步任务会极大地限制任务的执行方式。java.util.concurrent的FutureTask类结合了Runnable和Callable接口的优点:F??utureTask是RunnableFuture的实现类,Runnable是Runable和Future的子接口。所以FutureTask既是Runable的实现类,又是Future接口的实现。FutureTask提供了一个构造函数,可以将Callable实例转换为Runnable实例。构造函数的声明如下:publicFutureTask(Callablecallable)这弥补了Callable实例只能交给线程池执行,不能交给指定的工作。还是线程执行的缺点,而FutureTask实例本身就代表了我们要执行的任务,我们可以通过FutureTask实例获取到之前的Runnable实例无法获取到的线程执行结果。Executor.execute(Runnabletask)在只识别Runnable实例执行任务的情况下,仍然可以得到执行结果。这是一个例子:publicclassFutureTaskDemo{publicstaticvoidmain(String[]args)throwsException{FutureTaskfutureTask=newFutureTask(()->{try{TimeUnit.SECONDS.sleep(3);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("helloworld");return"helloworld";});新线程(futureTask).start();while(futureTask.isDone()){System.out.println(futureTask.get());}}}FutureTask也支持以回调的方式处理任务的执行结果。当执行FutureTask实例所代表的任务时,将执行Future.done。FutureTask.done的执行线程和FutureTask的run的执行线程是同一个线程。FutureTask是一个空的受保护方法。我们可以在子类中使用重写这个方法来实现后续的回调处理。由于任务执行后会触发done方法,所以不会阻塞在done方法中调用get方法获取任务执行结果。但是,由于任务的执行结束包括正常终止、异常终止和任务取消导致的终止,因此FutureTask.done方法中的代码可能需要先调用FutureTask.isCancelled判断任务是否在调用FutureTask.get()之前被取消,这样FutureTask.get调用就不会抛出CancellationException。FutureTask的简单示例:publicclassFutureTaskDemoextendsFutureTask{publicFutureTaskDemo(Callablecallable){super(callable);}publicFutureTaskDemo(Runnablerunnable,Stringresult){super(runnable,result);}/***这个方法会在任务执行结束时自动调用*我们通过get方法获取线程的执行结果,*由于这个方法是在任务执行结束时调用的,所以方法调用get不会被阻塞*/@Overrideprotectedvoiddone(){try{StringthreadResult=get();System.out.println(threadResult);}catch(InterruptedExceptione){e.printStackTrace();}catch(ExecutionExceptione){e.printStackTrace();}}publicstaticvoidmain(String[]args){FutureTaskDemofutureTaskDemo=newFutureTaskDemo(()->{System.out.println("helloworld");return"helloworld";});新线程(futureTaskDemo).start();}}FutureTask还是有一些不足,它基本上是设计来代表一个一次性执行的任务,其内部维护一个状态变量,表示任务的运行状态(包括未开始运行、运行完成等)。抛出异常,无法重复执行FutureTask实例代表的任务。这意味着同一个FutureTask实例不能多次提交执行,尽管不会抛出异常。FutureTask.runAndReset可以打破这个限制,让一个FutureTask实例代表的任务被执行多次,但不记录任务的执行结果。因此,如果同一个对象表示的任务需要执行多次,而我们需要对任务的执行结果进行处理,那么FutureTask就不适用了,但是我们可以设计一个满足我们需求的futureTask,这也是我们看源码的意思,重点理解和学习背后的设计思想,由此引出AsyncTask。示例:publicclassFutureTaskDemoextendsFutureTask{publicFutureTaskDemo(Callablecallable){super(callable);}publicFutureTaskDemo(Runnablerunnable,Stringresult){super(runnable,result);}@Overridepublicvoidrun(){//以runAndReset启动任务,启用super.runAndReset();/***这个方法会在任务执行结束时自动调用*我们通过get方法获取线程的执行结果,*因为这个方法是在任务执行结束时调用的,所以方法调用get不会被阻塞*/@Overrideprotectedvoiddone(){}publicstaticvoidmain(String[]args)throwsExecutionException,InterruptedException{FutureTaskDemofutureTaskDemo=newFutureTaskDemo(()->{System.out.println("helloworld");返回"你好世界";});新线程(futureTaskDemo).start();新线程(futureTaskDemo).start();}}可重复异步任务:AsyncTaskpublicabstractclassAsyncTaskimplementsRunnable,Callable{protectedfinalExecutor执行者;publicAsyncTask(Executorexecutor){this.executor=executor;}publicAsyncTask(){this(newExecutor(){@Overridepublicvoidexecute(Runnablecommand){command.run();}});}@Overridepublicvoidrun(){Exceptionexp=null;Vr=空;try{//调用逻辑可以自己传递或者自己实现r=call();}catch(异常e){exp=e;}最终V结果=r;if(null==exp){executor.execute(newRunnable(){@Overridepublicvoidrun(){onResult(result);}});}else{executor.execute(newRunnable(){@Overridepublicvoidrun(){onError(result);}});}}/***保留给子类实现任务执行结果的处理逻辑*@paramresult*/protectedabstractvoidonError(Vresult);/***任务执行结果的处理逻辑交给子类实现*@paramresult*/protectedabstractvoidonResult(Vresult);}参考资料《Java多线程编程实战指南》黄文海