【并发编程系列13】Future/Callable/FutureTask:线程池也是可以有返回值的

756 阅读6分钟

前言

之前我们介绍线程的基础知识以及线程池的时候,所有的线程都有一个共同的特点,那就是只管执行,我们不知道是否执行成功,也拿不到线程执行后的返回值信息,那么有没有办法获得线程执行的返回值呢?这就是今天我们要介绍的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<String> callable = () -> {
            return "Hello World1";
        };

        System.out.println(callable.call());

        Callable<String> callable1 = new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "Hello World2";
            }
        };
        System.out.println(callable1.call());
    }
}

可以看到这个和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<String> 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的源码:
在这里插入图片描述
这个和Runnable接口一样都只有一个方法,区别就是Callable接口中的方法有返回值,且可以抛出异常,而Runnable接口没有返回值也不能抛出异常。

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

Future原理分析

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

package java.util.concurrent;

public interface Future<V> {
    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中提供了两个构造器:
在这里插入图片描述
第一个构造器的用法我们下面会有演示,第二个构造器是用的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()方法:
在这里插入图片描述
可以看到run()方法最终调用了Callable中的call方法,并获得返回值调用set方法设置到FutureTask的成员属性outcome。
在这里插入图片描述
finishComletion方法其实就是去管理内部的一个简单的链表WaitNode:
在这里插入图片描述
这里面是如何管理的在这里我们不深入介绍,这个在旧版本jdk中用的就是AQS同步队列来实现的,所以可以看到很多地方的介绍都是说内部用的是AQS来保证线程同步,但是jdk1.8中用的就是一个简单的WaitNode来维护线程,但是原理和AQS同步队列是一致的

最后我们再看看get()方法是如何取值的:
在这里插入图片描述
这个方法很简单就是如果任务没完成那就阻塞,最后通过report方法拿到结果。
在这里插入图片描述

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方法区别

在这里我们主要分析一下submit方法:
在这里插入图片描述
可以看到这里会调用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,线程可以有返回值,而且线程池也是可以有返回值的。

**请关注我,一起学习进步**