Future和FutureTask

Callable 和 Future 的关系

Callable 的可以用 Future 类的 get 方法来获取

  • Future 相当于一个存储器,存储了 Callable 的 call 方法的任务结果
  • 通过 Future 的 isDone 方法来判断任务是否已经执行完毕了
  • 通过 cancel 方法取消这个任务,或限时获取任务的结果等

Future作用

Future 最主要的作用是,通过 Future 去控制子线程执行的计算过程,最后获取到计算结果。

比如当做一定运算的时候,运算过程可能比较耗时,有时会去查数据库,或是繁重的计算,比如压缩、加密等,在这种情况下,如果我们一直在原地等待方法返回,显然是不明智的,整体程序的运行效率会大大降低。我们可以把运算的过程放到子线程去执行,再通过 Future 去控制子线程执行的计算过程,最后获取到计算结果。这样一来就可以把整个程序的运行效率提高,是一种异步的思想。

Future 的方法和用法

五个方法

package java.util.concurrent;

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
get方法

获取任务执行的结果,该方法在执行时的行为取决于 Callable 任务的状态。

可能会发生以下 5 种情况。

  • 任务执行完毕,立刻返回,获取到任务执行的结果。
  • 任务还没有结果,直到任务完成再把结果返回回来
    ① 任务还没开始:往线程池中放一个任务,线程池中积压了很多任务,还没轮到执行的时候,就去 get 了,
    ② 任务正在执行中,但是执行过程比较长,所以去 get 的时候,它依然在执行的过程中。
  • 任务执行过程中抛出异常,一旦这样,我们再去调用 get 的时候,就会抛出 ExecutionException 异常,不管我们执行 call 方法时里面抛出的异常类型是什么,在执行 get 方法时所获得的异常都是 ExecutionException。
  • 任务被取消了,如果任务被取消,用 get 方法去获取结果时则会抛出 CancellationException。
  • 任务超时, get 方法有一个带延迟参数的重载方法,调用了这个带延迟参数的 get 方法之后,
    call 方法在规定时间内正常顺利完成了任务, get 会正常返回;
    到达了指定时间依然没有完成任务,get 方法则会抛出 TimeoutException,代表超时了。
isDone() 方法

判断当前这个任务是否执行完毕了。

  • 如果返回 true 则代表执行完成了;不代表这个任务是成功执行的,比任务执行到一半抛出了异常也返回true
  • 如果返回 false 则代表还没完成。
import java.util.concurrent.*;
public class GetException {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(20);
        Future<Integer> future = service.submit(new CallableTask());
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println(i);
                Thread.sleep(500);
            }
            System.out.println(future.isDone());
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    static class CallableTask implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            throw new IllegalArgumentException("Callable抛出异常");
        }
    }
}

在这里插入图片描述
这个异常在任务刚被执行的时候就抛出了,因为计算任务中没有其他逻辑的,只有抛出异常。我们再来看,控制台是在 true 打印完毕后才打印出异常信息的,也就是说,在调用 get 方法时打印出的异常

说明

  • 即便任务抛出异常,isDone 方法依然会返回 true;
  • 虽然抛出的异常是 IllegalArgumentException,但是对于 get 而言,它抛出的异常依然是 ExecutionException;
  • 虽然在任务执行一开始时就抛出了异常,但是真正要等到执行 get 的时候,才看到了异常。
cancel 方法:

取消任务的执行

三种情况:

  • 任务还没有开始执行时,一旦调用 cancel,这个任务就会被正常取消,未来也不会被执行,那么 cancel 方法返回 true。
  • 任务已经完成,或者之前已经被取消过了,那么执行 cancel 方法则代表取消失败,返回 false。因为任务无论是已完成还是已经被取消过了,都不能再被取消了。
  • 任务正在执行,这个时候执行 cancel 方法是不会直接取消这个任务的,会根据传入的参数做判断。cancel 方法是必须传入一个参数,该参数叫作 mayInterruptIfRunning,
    ① 传入的参数是 true,执行任务的线程就会收到一个中断的信号,正在执行的任务可能会有一些处理中断的逻辑,进而停止,即明确知道这个任务能够处理中断传入。
    ② 传入的是 false 则就代表不中断正在运行的任务,也即本次 cancel 不会有任何效果,同时 cancel 方法会返回 false。明确知道这个线程不能处理中断,不知道这个任务是否支持取消(是否能响应中断),这个任务一旦开始运行,就希望它完全的执行完毕,那应该传入 false。
isCancelled()

判断是否被取消,和 cancel 方法配合使用

用 FutureTask 来创建 Future

除了用线程池的 submit 方法会返回一个 future 对象之外,同样还可以用 FutureTask 来获取 Future 类和任务的结果。

FutureTask 是一个任务(Task),具有 Future 接口的语义,可以得到执行的结果。
FutureTask 既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

代码实现:

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

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

典型用法
把 Callable 实例当作 FutureTask 构造函数的参数,生成 FutureTask 的对象,然后把这个对象当作一个 Runnable 对象,放到线程池中或另起线程去执行,最后还可以通过 FutureTask 获取任务执行的结果。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class FutureTaskDemo {

    public static void main(String[] args) {
        Task task = new Task();
        FutureTask<Integer> integerFutureTask = new FutureTask<>(task);
        new Thread(integerFutureTask).start();
        try {
            System.out.println("task运行结果:"+integerFutureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class Task implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("子线程正在计算");
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        return sum;
    }
}

注意点

1. 当 for 循环批量获取 Future 的结果时容易 block,get 方法调用时应使用 timeout 限制

例:假设一共有四个任务需要执行,都把它放到线程池中,然后获取的时候按照从 1 到 4 的顺序,也就是执行 get() 方法来获取的,代码如下所示:

import java.util.ArrayList;
import java.util.concurrent.*;

public class FutureDemo {
    
    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //提交任务,并用 Future 接收返回结果
        ArrayList<Future> allFutures = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            Future<String> future;
            if (i == 0 || i == 1) {
                future = service.submit(new SlowTask());
            } else {
                future = service.submit(new FastTask());
            }
            allFutures.add(future);
        }
        for (int i = 0; i < 4; i++) {
            Future<String> future = allFutures.get(i);
            try {
                String result = future.get();
                System.out.println(result);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        service.shutdown();
    }
    static class SlowTask implements Callable<String> {
        @Override
        public String call() throws Exception {
            Thread.sleep(5000);
            return "速度慢的任务";
        }
    }

    static class FastTask implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "速度快的任务";
        }
    }
}

在这里插入图片描述
四个任务中,虽然第三个和第四个任务很早就得到结果了,但在此时使用这种 for 循环的方式去获取结果,无法及时获取到第三个和第四个任务的结果。直到 5 秒后,第一个任务出结果了,才能获取到,紧接着也可以获取到第二个任务的结果,然后才轮到第三、第四个任务。

假设由于网络原因,第一个任务可能长达 1 分钟都没办法返回结果,那么这个时候,主线程会一直卡着,影响了程序的运行效率。

此时可以用 Future 的带超时参数的 get(long timeout, TimeUnit unit) 方法来解决这个问题。这个方法的作用是,如果在限定的时间内没能返回结果的话,便会抛出一个 TimeoutException 异常,随后就可以把这个异常捕获住,或者是再往上抛出去,这样就不会一直卡着了。

2. Future 的生命周期不能后退
Future 的生命周期不能后退,一旦完成了任务,它就永久停在了“已完成”的状态,不能从头再来,也不能让一个已经完成计算的 Future 再次重新执行任务。

局限性

  • 并发执行多任务:Future只提供了get()方法来获取结果,并且是阻塞的。所以,除了等待你别无他法;
  • 无法对多个任务进行链式调用:如果你希望在计算任务完成后执行特定动作,比如发邮件,但Future却没有提供这样的能力;
  • 无法组合多个任务:如果你运行了10个任务,并期望在它们全部执行结束后执行特定动作,那么在Future中这是无能为力的
  • 没有异常处理:Future接口中没有关于异常处理的方法;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值