并发编程 | Future是如何优化程序性能

在初识Future一文中介绍了Future的核心方法。本文中接着介绍如何用Future优化我们的程序性能。 在此之前,有必要介绍Future接口的一个实现类FutureTask。

FutureTask介绍

FutureTask继承结构

首先我们看一下FutureTask的继承结构:

public class FutureTask<V> implements RunnableFuture<V>{
 ...
}

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

可以看出,FutureTask实现了RunnableFuture接口,而RunnableFuture继承了Runnable和Future,也就是说FutureTask既是Runnable,也是Future。所以 FutureTask 既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

下面的例子展示了FutureTask的 简单 用法 :

class FutureTaskCallable implements Callable{
    @Override
    public String call() throws Exception {
        return "Hello FutureTask";
    }
}

public class FutureTaskExample1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask =
                new FutureTask<>(new FutureTaskCallable());

        Thread thread = new Thread(futureTask);
        thread.start();

        System.out.println("子线程返回值:" + futureTask.get());
    }
}

//输出
子线程返回值:Hello FutureTask

这段程序中,可以看出FutureTask类的构造函数是Callable类型;在创建了FutureTask对象后,然后将这个对象当作一个 Runnable 放到 new Thread() 中去执行,最后再用 FutureTask 的 get 得到子线程 的 返回结果。简单介绍完FutureTask,现在可以进入文章的主题了:如何利用Future优化我们的程序性能。

利用Future优化程序

考虑这样一个场景,小码哥突然心血来潮想练练字了,但是他现在手头连一只钢笔都没有,书桌也是乱糟糟的,于是他打算在网购一支钢笔,然后整理书桌,就开始练字,用代码实现这个场景

普通多线程版本

我们先来看普通的多线程版本是如何实现。

//购买钢笔任务
class BuyPenRunnable implements Runnable{
    private String pen;

    @Override
    public void run() {
        System.out.println("【购买钢笔】:下单,等待送货上门");

        //模拟送货时间
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("【购买钢笔】:快递送到");
        pen = "神笔";
    }

    public String getPen() {
        return pen;
    }
}

public class CommomThreadExample {
    public static void main(String[] args) throws InterruptedException {
        long startTime = System.currentTimeMillis();

        BuyPenRunnable buyPenRunnable = new BuyPenRunnable();
        Thread buyPenThread = new Thread(buyPenRunnable);
        buyPenThread.start();



        //模拟整理书桌耗时3000ms
        System.out.println("【整理书桌】:开始整理");
        Thread.sleep(3000);
        System.out.println("【整理书桌】:整理完了");

        //保证笔送到
        buyPenThread.join();

        //所有准备好,开始练字
        write(buyPenRunnable.getPen());

        long endTime = System.currentTimeMillis();
        System.out.println("总共用时 " + (endTime - startTime) + " ms");

    }

    private static void write(String pen){
        System.out.println("【开始写字】:" + pen);
    }
}


//输出
【购买钢笔】:下单,等待送货上门
【购买钢笔】:快递送到
【整理书桌】:开始整理
【整理书桌】:整理完了
【开始写字】:神笔
总共用时 5020 ms

这里尽管将购买钢笔和整理桌子并行化了,节省了时间,但是这个版本有2个问题:

  1. 通过 Runnable 实现任务,但为了获取结果,我们需要定义共享变量 pen,并提供一个方法 getPen() 来获取这个变量。

  2. 任务的取消和状态检查不方便。

Future实现

利用FutureTask实现上面的功能,代码如下:

//购买钢笔任务
class BuyPenCallable implements Callable {

    @Override
    public String call() {
        System.out.println("【购买钢笔】:下单,等待送货上门");

        //模拟送货时间
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("【购买钢笔】:快递送到");
        return "神笔";
    }
}


public class FutureTaskExample {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();

        Callable<String> buyPenCallable = () -> {
            System.out.println("【购买钢笔】:下单,等待送货上门");

            // 模拟送货时间
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("【购买钢笔】:快递送到");
            return "神笔";
        };

        FutureTask<String> futureTask = new FutureTask<>(buyPenCallable);
        ExecutorService executor = Executors.newFixedThreadPool(1);
        executor.submit(futureTask);

        // 模拟整理书桌耗时3000ms
        System.out.println("【整理书桌】:开始整理");
        Thread.sleep(3000);
        System.out.println("【整理书桌】:整理完了");


        // 尝试取消任务,如果需要这么做
        //boolean cancelled = future.cancel(false); 
        
        // 等待获取笔的结果
        String pen = futureTask.get();

        // 所有准备好,开始练字
        write(pen);

        long endTime = System.currentTimeMillis();
        System.out.println("总共用时 " + (endTime - startTime) + " ms");

        executor.shutdown();
    }

    private static void write(String pen) {
        System.out.println("【开始写字】:" + pen);
    }
}

上面的代码中,可以看到,FutureTask实现的实现方式:

  • 获取结果更方便:通过 Callable 的 call() 方法直接返回结果,不需要显式地定义共享变量和同步机制。

  • 对线程的管理更方便:FutureTask 提供了 cancel() 方法,可以取消任务,并通过 isCancelled() 和 isDone() 方法检查任务状态。

总结

在我们的实际项目中,很多时候我们的接口需要通过rpc调用去调用其它的多个服务拿到结果聚合,如果采用同步调用的方式,假设一次远程调用的时间为 300ms,则一个 Client 同步对三个 Server 分别进行一次 RPC 调 用的总时间,需要耗费 900ms。

如果使用 Future 模式对其进行改造,将同步的 RPC 调用改为异步并发的 RPC 调用,一个 Client 异步并发对三个 Server 分别进行一次 RPC 调用,那么正常情况下,大约只需要 300ms就能拿到所有服务的数据了。Future的好处不言而喻。

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值