Java多线程学习(六)FutureTask,Future,Callable学习

时间:2021-09-20 18:02:54

Callable

通常来说,创建多线程的方式有两种,继承Thread或者实现Runnable接口。但是这两种方式中的run方法返回值都是void,当我们希望在线程执行完的时候返回一个结果时,我们可以使用实现Callable接口来创建线程。Callable接口的源码如下:

public interface Callable<V> {   
      V   call()   throws Exception;   
}   

接口中的call方法相当于Runnable中的run方法,只不过call会返回一个泛型值。


Future

Future是一个接口,用来获取一个异步处理的结果,源码如下:

public interface Future<V> {  
    boolean cancel(boolean mayInterruptIfRunning);  
    boolean isCancelled();  
    boolean isDone();  
    V get() throws InterruptedException, ExecutionException;  
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;  
}  

其中,

cancel方法是用来取消任务的执行,mayInterruptIfRunning参数是用来判断是否终止正在运行的任务,如果为true,就代表可以取消正在运行的任务,取消成功了返回true,如果为false,则表示不能终止正在运行的任务,必须等待其运行结束,返回false。如果任务已经结束,返回false。如果任务还没有开始,返回false。

isCancelled方法用来判断任务在完成前是否被取消,如果被取消了,则返回true,如果没有被取消,返回false。

isDone方法用来判断任务是否执行结束,无论是执行完成或者中途取消或者发生异常,都返回true,没有结束则返回false。

get方法用来阻塞式的获取任务执行的结果,没有执行结束就一直阻塞,一直到执行结束返回结果。

get方法带了timeout参数用来指定时间限制,如果超过了时间还没有返回结果则抛出异常。


FutureTask

FutureTask是一个类,因为Future只是一个接口,并不能拿来创建对象。FutureTask就是Future的实体类。

不过FutureTask是实现的RunnableFuture接口,而RunnableFuture接口是实现了Runnable接口和Future接口。

public class FutureTask<V> implements RunnableFuture<V> {  
public interface RunnableFuture<V> extends Runnable, Future<V> {  
    void run();  
} 

可以看到FutureTask中也有run方法,所以可以使用线程池的execute方法让其执行,也可以使用Thread的start方法让其执行。

FutureTask有一个state属性,state的值有如下几种:

private volatile int state;
private static final int NEW          = 0;
private static final int COMPLETING   = 1;
private static final int NORMAL       = 2;
private static final int EXCEPTIONAL  = 3;
private static final int CANCELLED    = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED  = 6;

NEW表示任务刚刚被创建或者还没有被执行完的任务,是一个初始状态。在FutureTask被构造方法实例化后,会将state属性置为NEW。

COMPLETING表示任务已经执行完成或者执行过程中发生异常,但是执行结果或者异常报告还没有被保存到outcome字段中,outcome字段用来保存执行的结果或者异常的报告。COMPLETING是一个中间态,持续时间很短,最终将跳到NORMAL或EXCEPTIONAL或CANCELLED或INTERRUPTING或INTERRUPTED状态。

NORMAL表示任务已经执行完成,并且执行结果已经保存到outcome字段中,是一个最终态。

EXCEPTIONAL表示任务在执行过程发生异常,并且异常报告已经保存到outcome字段中,是一个最终态。

CANCELLED表示任务还没有执行时调用了cancel(false)方法使任务取消了,是一个最终态。

INTERRUPTING表示任务在执行还没有执行或者在执行过程中调用了cancel(false)方法要取消任务但是还没有取消完成,当取消完成了就跳到INTERRUPTED,是一个中间态。

INTERRUPTED表示任务在执行还没有执行或者在执行过程中调用了cancel(false)方法取消任务完成,是一个最终态。

其中,所有值大于COMPLETING的状态都表示任务已经执行完成(任务正常执行完成,任务执行异常或者任务被取消)。


代码应用:

public class FuturetaskTest {
    
    public static void main(String[] args) {
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Integer call() throws Exception {
                Thread.sleep(1000);//模拟任务处理时间
                System.out.println("任务已经启动!!!");
                Integer result = 0;
                for (int i = 0; i < 5000000; i++) {
                    result += i;
                }
                return result;
            }
        });
        System.out.println("主线程在执行!");
        //第一种方式,使用线程池调用
        ExecutorService executor = Executors.newFixedThreadPool(10);
        executor.execute(task);
        try {
            System.out.println("任务是否完成:"+task.isDone());
            Thread.sleep(2000);//让任务先执行
            System.out.println("取消任务:"+task.cancel(false));
            System.out.println("任务是否被取消:"+task.isCancelled());
            System.out.println("任务执行结果:"+task.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("主线程执行完毕!");
        System.out.println("任务是否完成:"+task.isDone());
        executor.shutdown();
        //第二种方式,使用Thread调用
        Thread thread = new Thread(task);
        thread.start();
        try {
            System.out.println("任务执行结果:"+task.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

执行结果:

Java多线程学习(六)FutureTask,Future,Callable学习

可以看到,在任务已经执行的时候调用cancel(false)方法并没有取消任务。而且在第二次尝试启动任务时,任务并没有启动,而是直接返回了第一次执行的结果。所以FutureTask的任务只会执行一次,在返回结果是会判断当前任务的state是否等于NEW,如果不为NEW则说明任务或者已经执行过,或者已经被取消,直接返回。


FutureTask使用场景

FutureTask可以用来异步获取处理结果,创建一个FutureTask来处理运算,在主线程的合适位置调用get获取处理结果,去除主线程的等待时间,将等待时间可以去处理其他复杂的业务逻辑。

还有在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。