Future类之案例上手解析

之前在线程池的使用时,已经了解到当提交的任务需要获取返回值的时候,就需要用到submit这个方法进行任务的提交,而不是execute方法;

1. 线程池submit方法解析(Future接口)

Java通过ThreadPoolExecutor提供的3个submit()方法和一个FutureTask工具类来支持获得任务执行结果的需求,这三个方法如下:

    //1. 提交Callable任务,返回的Future,可以通过它的get方法获取Callable的返回值
	<T> Future<T> submit(Callable<T> task);

	//2. 提交Runnable任务,由于Runnable没有返回值,所以这个方法返回的Future仅可以用来断言任务已经结束		//了
    Future<?> submit(Runnable task);

	//3. 提交Runnable任务及结果引用
    <T> Future<T> submit(Runnable task, T result);

我们会发现上面几个方法的返回值都是Future接口,那么来看看Future这个接口里面有些啥:
(Future也是juc包下的一个接口)

  • boolean cancel(boolean mayInterruptIfRunning);  //取消任务
    
  • boolean isCancelled();   //判断任务是否取消
    
  • boolean isDone();    //判断任务是否结束
    
  • V get() throws InterruptedException, ExecutionException;   //获得任务执行结果
    
  • V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;   //获得任务执行结果,																		//支持超时
    

这里我们来简要分析一下那三个submit方法:

  1. 第一个方法提交Callable任务,返回的Future,可以通过它的get方法获取Callable的返回值;
  2. 第二个方法提交Runnable任务,由于Runnable没有返回值,所以这个方法返回的Future仅可以用来断言任务已经结束了;
  3. ⭐第三个就比较复杂点了,可以看到有两个参数,那么这个方法含义是什么?这里先把含义展示出来:
    假设这个方法返回的Future对象是f,那么调用f.get()的返回值就是传给submit()方法的参数result;

对于上面的方法三,我们知道了其含义,但它的使用场景一般是什么,下面先来看一个典型代码:

class Result {
    int data;

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }
}
class Task implements Runnable {
    Result result;
    public Task(Result result) {
        this.result = result;
    }
    @Override
    public void run() {
        int a = result.getData();
        result.setData(20);
    }
}
public class Test1 {
    public static void main(String[] args) throws Exception{
        ExecutorService executor = Executors.newFixedThreadPool(1);
        Result result = new Result();
        result.setData(10);
        System.out.println(result.getData());
        Future<Result> future = executor.submit(new Task(result), result);
        //get方法的返回值就是我们传入的result
        result = future.get();
        System.out.println(result.getData());
    }
}

输出:
10
20

🌞你或许会感到疑惑,上面这个代码的意图是干啥,这里我们做一下 解释: 🌞

  • 上面的Task这个任务类实现了一个构造方法,传入了一个Result对象,这一点很关键,保证了能在任务中对result对象进行相关操作,而第二个参数正是这个result对象,当我们调用get方法时,就能获取到这个result对象,这样做的意义就是:result相当于主线程和子线程之间的桥梁,通过它主子线程可以进行共享数据;

2. FutureTask类

1. API概览

首先得知道,FutureTask是一个实体类,而不像Future是一个接口,这个类也是juc包下的,下面首先来看看它的继承关系:

public class FutureTask<V> implements RunnableFuture<V> 

继续看看RunnableFuture这个接口:

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

看到这,其实就是FutureTask这个类同时实现了Runnable接口和Future接口,那么代表:

  • 实现了Runnable接口,所以可以将FutureTask对象作为任务提交给线程池去执行,也可以直接被Thread执行;
  • 实现了Future接口,所以也能用来获取任务的执行结果;(get()方法)

下面来看看它的构造方法:(两个

//传入一个Callable类,即支持返回值;
public FutureTask(Callable<V> callable)
//这个构造方法是不是跟上面的那个submit的一个很像啊
public FutureTask(Runnable runnable, V result)

可以看到,这个类没有无参构造,必须调用上面两种的一种;

2. 简单使用栗子

方式一:将FutureTask对象提交给线程池去执行;

public class Test1 {
    public static void main(String[] args) throws Exception{
        //构造传入一个Callable,这里直接传入Lambda表达式,注意只有一行时return是可以省略的;
        FutureTask<Integer> futureTask = new FutureTask<>(()->1+2);
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        //用submit方式提交任务,submit方法有一个重载是支持传入一个Runnable;
        executorService.submit(futureTask);
        int res = futureTask.get();
        System.out.println(res);
    }
}

输出:3

方式二:直接被Thread执行:

public class Test1 {
    public static void main(String[] args) throws Exception{
        //构造传入一个Callable,这里直接传入Lambda表达式,注意只有一行时return是可以省略的;
        FutureTask<Integer> futureTask = new FutureTask<>(()->1+2);
        Thread thread = new Thread(futureTask);
        thread.start();
        int res = futureTask.get();
        System.out.println(res);
    }
}

可以看到,使用FutureTask类可以很轻松的拿到子线程的执行结果;

3. 实现最优版的烧水泡茶

著名数学家华罗庚的一篇文章《统筹方法》中,提到了最优的工序应该是下面这个样子:
在这里插入图片描述
对于上面这个图,我们用程序来模拟实现一下这个过程;

对于这个烧水泡茶程序,最重要的部分就是分工问题,这里我们可以用两个线程来完成上面的烧水泡茶,线程T1负责洗水壶、烧开水、泡茶,而线程T2实现洗茶壶、洗茶杯、拿茶叶;这里我们应当注意到,T1必须等到T2拿到茶叶后,才能执行泡茶这一步骤,这一块涉及的就是线程之间的同步(通信)问题啦;
对于这个等待操作,可以有很多种方式去实现,例如:

  • Thread.join()
  • CountDownLatch(闭锁)
  • 阻塞队列

本次就来使用刚刚学的FutureTask类来实现这个功能:

class T1 implements Callable<String> {
    FutureTask futureTask ;
    public T1(FutureTask futureTask) {
        this.futureTask = futureTask;
    }
    public void kettle() throws Exception{
        System.out.println("T1:洗水壶......(1s)");
        Thread.sleep(1000);
    }

    public void boilWater() throws Exception{
        System.out.println("T1:烧开水......(5s)");
        Thread.sleep(3000);
    }

    public void makeTea() throws Exception{
        //泡茶前必须先拿到茶叶
        System.out.println("T1:等待T2的茶叶......");
        //重点在这⭐⭐,这里将会阻塞,直到取到结果;
        String tea = (String) futureTask.get();
        System.out.println("T1:拿到了:"+tea);
        System.out.println("T1:开始泡茶啦!!");
        System.out.println("T1:上茶!!!");
    }
    @Override
    public String call() throws Exception {
        kettle();
        boilWater();
        makeTea();
        return null;
    }
}

class T2 implements Callable<String> {
    public void washTeaPot() throws Exception{
        System.out.println("T2:洗茶壶......(1s)");
        Thread.sleep(1000);
    }
    public void washTeabottle() throws Exception{
        System.out.println("T2:洗茶杯......(1s)");
        Thread.sleep(1000);
    }
    public String takeTea() throws Exception{
        System.out.println("T2:开始取茶叶啦,准备给T1......(4s)");
        Thread.sleep(4000);
        System.out.println("T2:取到啦,给T1了");
        return "上好的龙井!!";
    }
    @Override
    public String call() throws Exception {
        washTeaPot();
        washTeabottle();
        return takeTea();
    }
}
public class Test1 {
    public static void main(String[] args) throws Exception{
        //T2实现洗茶壶、洗茶杯、拿茶叶
        FutureTask<String> futureTaskT2 = new FutureTask<>(new T2());
	   //T1负责洗水壶、烧开水、泡茶
        FutureTask futureTaskT1 = new FutureTask(new T1(futureTaskT2));

        new Thread(futureTaskT1).start();
        new Thread(futureTaskT2).start();

    }
}

输出:
T1:洗水壶…(1s)
T2:洗茶壶…(1s)
T1:烧开水…(5s)
T2:洗茶杯…(1s)
T2:开始取茶叶啦,准备给T1…(4s)
T1:等待T2的茶叶…
T2:取到啦,给T1了
T1:拿到了:上好的龙井!!
T1:开始泡茶啦!!
T1:上茶!!!

Process finished with exit code 0

这里可以简单的总结一下:
利用多线程可以快速将一些串行的任务并行化,从而提高性能;如果任务之间有依赖关系,比如当前任务依赖前一个任务的结果,这种问题就可以利用Future来解决;

3. CompletableFuture类

1. 异步化

利用多线程来优化性能,其实不过就是将串行操作变成并行操作;如果仔细观察,你还会发现在串行转换成并行的过程中,一定会涉及到异步化;

例如下面两个方法,我们如何将它并行化达到性能的提升:

//这是两个耗时操作
fun1();
fun2();

很轻松想到,开两个线程分别去执行它们就行了;
当两个线程分别去执行这两个方法时,这两个方法的执行就不再相互依赖,此时这两个方法就已经被异步化 了;

2. API概览

先来看看这个类的定义,可以看到继承了Future类;

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> 

3. 如何创建CompletableFuture对象

创建CompletableFuture对象主要依靠下面的这四个静态方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值