所有的线程都有一个共同的特点,那就是只管执行,我们不知道是否执行成功,也拿不到线程执行后的返回值信息,那么有没有办法获得线程执行的返回值呢?这就是今天我们要介绍的Future和Callable,以及Future的实现类FutureTask,有了Future和Callable之后,最终我们就可以知道线程池也是可以有返回值的

Future/Callable初体验

Callable用法

我们先看一个Callable的使用例子:

package com.zwx.thread.futureCallable;import java.util.Random;import java.util.concurrent.*;public class TestCallable {    public static void main(String[] args) throws Exception {        Callable callable = () -> {            return "Hello World1";        };        System.out.println(callable.call());        Callable callable1 = new Callable() {            @Override            public String call() throws Exception {                return "Hello World2";            }        };        System.out.println(callable1.call());    }12345678910111213141516171819202122

可以看到这个和Runnable接口非常类似,不同的是多了一个返回值。

Future用法

Future一般和Callable一起使用,用于对任务执行结果进行取消、查询是否完成、获取结果等。
下面是一个简单的用法示例:

package com.zwx.thread.futureCallable;import java.util.concurrent.*;public class TestFuture {    public static void main(String[] args) throws Exception{        ExecutorService executorService = Executors.newFixedThreadPool(1);        Future future = executorService.submit(new MyCallable());        System.out.println("任务是否完成:" + future.isDone());        System.out.println(future.get());//阻塞直到返回结果        System.out.println("任务是否完成:" + future.isDone());        System.out.println("============end============");                executorService.shutdown();    }}class MyCallable implements Callable{    @Override    public Object call() throws Exception {        Thread.sleep(2000);        return "Hello World";    }}

Callable和Future原理分析

Callable原理分析

我们先看看Callable的源码:




java线程池执行future获取返回结果 线程池获取返回值_java


这个和Runnable接口一样都只有一个方法,区别就是Callable接口中的方法有返回值,且可以抛出异常,而Runnable接口没有返回值也不能抛出异常。

既然这个和Runnable这么像,那么我们是不是可以通过Callable来创建线程呢?这个问题我们放在后面回答。

Future原理分析

接下来我们再来看看Future类,Future也是一个接口,总共定义了5个方法:

package java.util.concurrent;public interface Future {    boolean cancel(boolean mayInterruptIfRunning);//取消任务(true-表示允许取消执行中的任务)    boolean isCancelled();//任务是否在完成前被取消    V get() throws InterruptedException, ExecutionException;//获取返回值,会被阻塞    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;//在指定时间内获取返回值}
  • cancle(boolean mayInterruptIfRunning)方法:尝试取消正在执行的任务。取消成功则返回true,取消失败则返回false,如果任务已经完成,那么一定返回false,如果任务已经开始,则如果参数设置为false,那么一定返回false,如果参数设置为true(表示允许取消进行中的任务)则会尝试去取消任务,成功则返回true,失败返回false。
  • isCancelled()方法:判断当前任务在完成前是否被取消了
  • get()方法:获取任务返回值,此方法会阻塞直到成功获取到返回值。
  • get(long timeout, TimeUnit unit) :在指定时间内阻塞等待返回值,如果达到指定时间还没获取到返回值,则返回null。

FutureTask分析

FutureTask实现了RunnableFuture接口,而RunnableFuture接口又同时继承了Runnable和Future接口,所以我们就可以利用FutureTask来进行线程的创建了。
FutureTask中提供了两个构造器:


java线程池执行future获取返回结果 线程池获取返回值_java_02


第一个构造器的用法我们下面会有演示,第二个构造器是用的Runnable接口,并且需要传入一个结果,执行成功之后拿到的是我们传入的result,并不是线程的执行结果

如何利用FutureTask/Callable创建线程

下面就是利用FutureTask来创建线程的例子:

package com.zwx.thread.futureCallable;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class TestFutureTask {    public static void main(String[] args) throws ExecutionException, InterruptedException {        FutureTask futureTask = new FutureTask(new MyCallableTask());        Thread t1 = new Thread(futureTask);        t1.start();        System.out.println(futureTask.get());    }}class MyCallableTask implements Callable {    @Override    public Object call() throws Exception {        return "Hello World";    }}

线程调用start那么最后肯定是执行的run()方法,我们看看FutureTask的run()方法:


java线程池执行future获取返回结果 线程池获取返回值_return两个返回值_03


可以看到run()方法最终调用了Callable中的call方法,并获得返回值调用set方法设置到FutureTask的成员属性outcome。


java线程池执行future获取返回结果 线程池获取返回值_赋值_04


finishComletion方法其实就是去管理内部的一个简单的链表WaitNode:


java线程池执行future获取返回结果 线程池获取返回值_return两个返回值_05


这里面是如何管理的在这里我们不深入介绍,这个在旧版本jdk中用的就是AQS同步队列来实现的,所以可以看到很多地方的介绍都是说内部用的是AQS来保证线程同步,但是jdk1.8中用的就是一个简单的WaitNode来维护线程,但是原理和AQS同步队列是一致的

最后我们再看看get()方法是如何取值的:


java线程池执行future获取返回结果 线程池获取返回值_System_06


这个方法很简单就是如果任务没完成那就阻塞,最后通过report方法拿到结果。


java线程池执行future获取返回结果 线程池获取返回值_java_07


FutureTask状态分析

FutureTask中总共有7种状态:

  • NEW:初始化任务后的状态
  • COMPLETING:任务正常完成但是返回值还没有赋值给outcome属性。
  • NORMAL:任务正常完成且返回值已经赋值给outcome属性。
  • EXCEPTIONAL:任务出现异常且将异常信息赋值给了outcome属性(可以参见setException方法)
  • CANCELLED:调用cancle(false)方法取消NEW状态任务
  • INTERRUPTING:调用cancle(true)方法取消运行中任务,但是任务还没有中断
  • INTERRUPTED:调用cancle(true)方法取消运行中任务,且任务已经被中断。

FutureTask的状态流转可能有以下四种流转方式:

  • 1、正常完成且正常赋值:NEW -> COMPLETING -> NORMAL
  • 2、正常完成但是赋值异常:NEW -> COMPLETING -> EXCEPTIONAL
  • 3、开始执行任务之前就被取消:NEW -> CANCELLED
  • 4、开始执行任务之后被中断:NEW -> INTERRUPTING -> INTERRUPTED

线程池的submit方法和execute方法区别

上一篇我们分析线程池的时候详细分析过execute方法的执行流程了,如果想了解的可以点击这里。
在这里我们主要分析一下submit方法:


java线程池执行future获取返回结果 线程池获取返回值_System_08


可以看到这里会调用newTaskFor方法将我们的Callable任务封装成一个FutureTask再调用execute方法执行任务,而execute方法内最终会调用runWorker方法,最终又是调用了task.run()方法,所以最终普通的Runnable任务就会调用Runnable接口的run()方法,而Callable任务最终就会调用了Callable中的call()方法,并且将返回值设置到FutureTask的outcome属性,最终我们可以通过get()方法获取到返回值。

总结

本文主要介绍了Future和Callable的用法,并且介绍了Future的实现类FutureTask的应用,最后我们介绍了如何利用Future和Callable来创建一个有返回值的线程,并且分析了线程池当中的execute和submit方法的区别,相信通过本文的学习,大家可以知道了,利用Future和Callable,线程可以有返回值,而且线程池也是可以有返回值的。

请关注我,一起学习进步