Java中 FutureTask 的使用

在这里插入图片描述在这里插入图片描述

在前面 通过Callable和Future创建线程 已经学习了 Callable 和 Future 两个接口,以及 FutureTask 的简单使用,节约篇幅,这里就不重复介绍了。

介绍

我觉得 FutureTask 其实就是实实在在的工具类,我们把具体的任务详情在 Callable 接口的实现类中实现,然后将实现类的实例传给 FutureTask,让他来创建任务,它还需要调度者来调度执行。

FutureTask 的使用很简单,通过源码可以看出,FutureTask实现了Runnable和Future接口。
因为 FutureTask 实现了 Runnable 接口,所以可以将 FutureTask 对象作为任务提交给 ThreadPoolExecutor 去执行,也可以直接被 hread 执行。
又因为实现了Future接口,所以也能用来获得任务的执行结果。

下面直接使用 FutureTask 完成一个有趣的程序

示例

我在学习《Java并发编程实战》的时候,看到一个叫做“烧水泡茶”程序。下面就用 FutureTask 来模拟一下这个工序。

烧水泡茶需要下面这些工序:洗水壶,烧开水,洗茶壶,洗茶杯,拿茶叶,泡茶。他们每一步耗时不同。

并发编程可以总结为三个核心问题:分工、同步和互斥。

对于烧水泡茶这个程序,一种最优的分工方案可以是下图所示的这样: 用两个线程T1和T2来完成烧水泡茶程序

  • T1负责洗水壶、烧开水、泡茶这三道工序。
  • T2负责洗茶壶、洗茶杯、拿茶叶三道工序,其中T1在执行泡茶这道工序时需要等待T2完成拿茶叶的工序。

在这里插入图片描述

首先,我们创建了两个FutureTask——FutureTask_1 和FutureTask_2,FutureTask_1 完成洗水壶、烧开水、泡茶的任务,FutureTask_2 完成洗茶壶、洗茶杯、拿茶叶的任务。

这里需要注意的是 FutureTask_1 这个任务在执行泡茶任务前,需要等待 FutureTask_2 把茶叶拿来,所以 FutureTask_1 内部需要引用 FutureTask_2,并在执行泡茶之前,我们可以充分利用 Future 的 get 方法的阻塞性质来调用 FutureTask_2 的get()方法实现等待。

FutureTask_1 需要执行的任务:洗水壶、烧开水、泡茶:

/**
 * FutureTask_1 需要执行的任务:洗水壶、烧开水、泡茶
 */
public class FutureTask_1 implements Callable<String> {

    // FutureTask_1 中持有 FutureTask_2 的引用
    FutureTask<String> futureTask_2;

    // 通过构造器初始化 成员变量
    public FutureTask_1(FutureTask<String> futureTask_2) {
        this.futureTask_2 = futureTask_2;
    }

    // 重写的 Callable 接口中的 call 方法。
    @Override
    public String call() throws Exception {

        System.out.println("T1:洗水壶");
        TimeUnit.SECONDS.sleep(1);

        System.out.println("T1:烧开水");
        TimeUnit.SECONDS.sleep(15);

        // 获取 T2 线程的茶叶
        String teas = futureTask_2.get();
        System.out.println("拿到茶叶:" + teas);

        System.out.println("T1:开始泡茶...");

        return "上茶:" + teas;
    }
}

FutureTask_2 需要执行的任务:洗茶壶、洗茶杯、拿茶叶:

/**
 * FutureTask_2 需要执行的任务:洗茶壶、洗茶杯、拿茶叶
 */
public class FutureTask_2 implements Callable<String> {

    @Override
    public String call() throws Exception {

        System.out.println("T2:洗茶壶");
        TimeUnit.SECONDS.sleep(1);

        System.out.println("T2:洗茶杯");
        TimeUnit.SECONDS.sleep(2);

        System.out.println("T2:拿茶叶");
        TimeUnit.SECONDS.sleep(1);

        return "峨眉雪尖儿";
    }
}

测试方法:

    @Test
    public void makeTea(){

        // 创建 FutureTask_2 的任务
        FutureTask<String> futureTask_2 = new FutureTask<>(new FutureTask_2());
        // 创建 FutureTask_1 的任务,并将 FutureTask_2 任务的引用传入
        FutureTask<String> futureTask_1 = new FutureTask<>(new FutureTask_1(futureTask_2));

        // 创建线程 T1,来执行任务 FutureTask_1
        Thread t1 = new Thread(futureTask_1);
        t1.start();

        // 创建线程 T2,来执行任务 FutureTask_2
        Thread t2 = new Thread(futureTask_2);
        t2.start();

        try {
            // 获取任务 FutureTask_1 的最后一步的结果
            System.out.println(futureTask_1.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

执行结果:

T2:洗茶壶
T1:洗水壶
T1:烧开水
T2:洗茶杯
T2:拿茶叶
拿到茶叶:峨眉雪尖儿
T1:开始泡茶...
上茶:峨眉雪尖儿

如果多执行几次会发现,在拿到茶叶前的执行结果有可能都不不一样的,这就说明,我们 T1 和 T2 两个线程在并行执行。

利用多线程可以快速将一些串行的任务并行化,从而提高性能。
如果任务之间有依赖关系,比如当前任务依赖前一个任务的执行结果,这种问题基本上都可以用 Future 来解决。在分析这种问题的过程中,建议先用图描述一下任务之间的依赖关系,同时将线程的分工也做好,类似于烧水泡茶最优分工方案那幅图。对照图来写代码,好处是更形象,且不易出错。


技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值