天天看点

Java Callable接口、Runable接口、Future接口

1. callable与runable区别

java从发布的第一个版本开始就可以很方便地编写多线程的应用程序,并在设计中引入异步处理。thread类、runnable接口和java内存管理模型使得多线程编程简单直接。

但thread类和runnable接口都不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。不能声明抛出检查型异常则更麻烦一些。

public void run()方法契约意味着你必须捕获并处理检查型异常。即使你小心地保存了异常信息(在捕获异常时)以便稍后检查,但也不能保证这个类(runnable对象)的所有使用者都读取异常信息。

你也可以修改runnable实现的getter,让它们都能抛出任务执行中的异常。但这种方法除了繁琐也不是十分安全可靠,你不能强迫使用者调用这些方法,程序员很可能会调用join()方法等待线程结束然后就不管了。

但是现在不用担心了,以上的问题终于在1.5中解决了。callable接口和future接口的引入以及他们对线程池的支持优雅地解决了这两个问题。

不管用哪种方式创建线程,其本质都是callable接口与runable接口。两者都是可被其它线程执行的任务!!区别是:

Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口

2.future

如上所说,callable任务返回future对象。即:callable和future一个产生结果,一个拿到结果。

future 表示异步计算的结果。future接口中有如下方法:

    boolean cancel(boolean mayinterruptifrunning)

取消任务的执行。参数指定是否立即中断任务执行,或者等等任务结束

    boolean iscancelled() 

任务是否已经取消,任务正常完成前将其取消,则返回 true

    boolean isdone()

任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true

    v get()

等待任务执行结束,然后获得v类型的结果。interruptedexception 线程被中断异常, executionexception任务执行异常,如果任务被取消,还会抛出cancellationexception

    v get(long timeout, timeunit unit) 

同上面的get功能一样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类timeunit中有相关的定义。如果计算超时,将抛出timeoutexception

future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果。也可以设置任务执行的超时时间,这个设置超时的方法就是实现java程序执行超时的关键。

所以,如果需要设定代码执行的最长时间,即超时,可以用java线程池executorservice类配合future接口来实现。

三个简单的小例子,体会一下:

Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口

3. future实现类

Java Callable接口、Runable接口、Future接口

3.1 futuretask

futuretask是一个runnablefuture<v>,而runnablefuture实现了runnbale又实现了futrue<v>这两个接口,

Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口

另外它还可以包装runnable和callable<v>

Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口

可以看到,runnable会被executors.callable()函数转换为callable类型,即futuretask最终都是执行callable类型的任务。该适配函数的实现如下 :

Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口

由于futuretask实现了runnable,因此它既可以通过thread包装来直接执行,也可以提交给executeservice来执行。见下面两个例子:

Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口
Java Callable接口、Runable接口、Future接口

如果要执行多个带返回值的任务,并取得多个返回值,两种方法:

1.先创建一个装future类型的集合,用executor提交的任务返回值添加到集合中,最后便利集合取出数据。

这时候,submit的task不一定是按照加入自己维护的list顺序完成的。从list中遍历的每个future对象并不一定处于完成状态,这时调用get()方法就会被阻塞住。

如果系统是设计成每个线程完成后就能根据其结果继续做后面的事,这样对于处于list后面的但是先完成的线程就会增加了额外的等待时间。

所以jdk1.8增加了future接口的另外一个实现类completionservice

2.completionservice相当于executor加上blockingqueue,使用场景为当子线程并发了一系列的任务以后,主线程需要实时地取回子线程任务的返回值并同时顺序地处理这些返回值,谁先返回就先处理谁。

而completionservice的实现是维护一个保存future对象的blockingqueue。只有当这个future对象状态是结束的时候,才会加入到这个queue中,take()方法其实就是producer-consumer中的consumer。它会从queue中取出future对象,如果queue是空的,就会阻塞在那里,直到有完成的future对象加入到queue中。

所以,先完成的必定先被取出。这样就减少了不必要的等待时间。