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

前言

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

请关注我,和孤狼一起学习进步

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351