体系化深入学习并发编程(十)Future和Callable

本文深入解析Callable接口与Future接口在Java并发编程中的应用,对比Runnable接口,Callable支持返回值及异常处理,Future用于获取异步计算结果。文章涵盖Future主要方法、线程池submit()使用、Future对象集合存储、call方法异常处理、get()与cancel()超时情况及FutureTask的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Callable接口

Callable接口是对Runnable接口的一种补充
为什么这么说呢,因为我们之前调用Runnable接口时就发现,其中的run()方法是没有返回值的,同时也不能抛出异常。

public abstract void run();

因为所以我们都是在run()方法内部处理异常的。

而Callable接口则正是应对这两种情况的接口。

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

可以看到,Callable接口的call方法不仅提供了泛型返回值,同时能够向上抛出异常。

Future接口

Future接口是用来表示异步计算的结果。
同步计算和异步计算
Future的主要方法:

  • get() 返回计算结果
  • get(long timeout, TimeUnit unit) 在给定时间内完成则返回计算结果
  • cancel(boolean mayInterruptIfRunning) 是否中断正在执行中的任务
  • isCancelled() 如果正常完成之前被取消,返回true
  • isDone() 任务执行完毕返回true

get()的几种情况:

  1. 任务已完成,返回结果
  2. 任务未开始或未完成,阻塞等待结果
  3. call方法产生异常,get()抛出ExecutionException
  4. 任务被取消,get()抛出CancellationException
  5. get(long timeout, TimeUnit unit)任务超时,抛出TimeoutException

线程池submit()获取future对象

提交任务给线程池,调用submit()方法后,会立即返回一个空的Future容器,等待任务执行完成后,再向容器填充计算结果。

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService threadPool = Executors.newFixedThreadPool(10);
    Future<Integer> future = threadPool.submit(() -> {
        TimeUnit.SECONDS.sleep(2);
        return new Random().nextInt();
    });
    System.out.println(future.get());
    threadPool.shutdown();
}

运行后可以看到调用get方法会阻塞2秒,等待submit执行完毕返回结果后,get方法才生效。

使用集合存储多个Future对象

同样,当有多个计算结果返回时,我们可以使用集合存储多个Future对象来存储返回结果。

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ArrayList<Future> futures = new ArrayList<>(10);
    ExecutorService threadPool = Executors.newFixedThreadPool(2);
    for (int i = 0; i <10; i++) {
        Future<Integer> future = threadPool.submit(() -> {
            TimeUnit.SECONDS.sleep(2);
            return new Random().nextInt();
        });
        futures.add(future);
    }
    for (Future future : futures) {
        System.out.println(future.get());
    }
    threadPool.shutdown();
}

这里用ArrayList存储Future对象,并且由于线程池只有两个线程,所以每次get方法调用时,会等待2秒后输出两个整型,然后继续阻塞。

call方法发生异常时

这段代码逻辑是:判断异常会被哪个catch代码块捕获住,同时使用打点计数,验证当调用get方法时才会捕获到异常,同样也验证了isDone()方法并不仅仅是任务正常完成会返回true,任务异常也会返回true。

public static void main(String[] args) {
    ExecutorService threadPool = Executors.newFixedThreadPool(2);
    Future<Integer> future = threadPool.submit(() -> {
        throw new RuntimeException("Callable发生异常");
    });

    try {
        for (int i = 0; i <5; i++) {
            System.out.println(i+1);
            TimeUnit.SECONDS.sleep(1);
        }
        System.out.println(future.isDone());
        future.get();
    } catch (InterruptedException e) {
        System.out.println("InterruptedException");
    } catch (ExecutionException e) {
        System.out.println("ExecutionException");
    }
    threadPool.shutdown();
}
1
2
3
4
5
true
ExecutionException

get()、cancel()超时的情况

下面就演示一下超时的get()方法和cancel方法的使用

这段代码逻辑:任务:休眠2秒,休眠完成后返回字符串
调用不同超时时间的get方法进行判断,最后输出的字符串来自哪行命令。

public static void main(String[] args) throws InterruptedException {
    ExecutorService threadPool = Executors.newFixedThreadPool(10);
    Future<String> future = threadPool.submit(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            System.out.println("sleep被中断了");
            return "sleep被中断后返回的String";
        }
        return "2秒后正常运行结束";
    });
    String s = null;
    try {
        s = future.get(10, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        System.out.println("get被中断了");
        s="get被中断后返回的String";
    } catch (ExecutionException e) {
        System.out.println("内部发生异常了");
        s="call方法内部出现异常时的String";
    } catch (TimeoutException e) {
        System.out.println("超时异常了");
        s="发生超时异常时的String";
    }
    System.out.println(s);
    threadPool.shutdown();
}
2秒后正常运行结束

首先是get(10秒),没超时的情况下。10秒内,任务结束返回字符串,get方法就将该值赋值给s,最后输出。

再来看看如果get()超时时间为1秒

s = future.get(1, TimeUnit.SECONDS);
超时异常了
发生超时异常时的String

会跳转到catch (TimeoutException e)代码块

那么我们在这个代码块中,调用cancel方法会如何。

catch (TimeoutException e) {
        System.out.println("超时异常了");
        s="发生超时异常时的String";
        boolean cancel = future.cancel(false);
        System.out.println(cancel);
}

cancel方法会传入一个boolean类型,判断是否中断当前任务。
先传入false。
按照逻辑,超时跳转到这里时,先输出一句话,然后s赋值,我们调用cancel方法,输出中断任务是否成功。

超时异常了
true
发生超时异常时的String

可以看到,都是按照预期输出的。
那么如果中断当前任务,会进入这个catch代码块

catch (InterruptedException e) {
        System.out.println("sleep被中断了");
        return "sleep被中断后返回的String";
}

那么返回结果会不会给s赋值?

超时异常了
sleep被中断了
true
发生超时异常时的String

答案是不会,超时之后,任务的return全都失效了。
即使是发生了中断异常,执行了代码,但是不会将结果return回来。

就像老师说收个作业,这周之内发到他的邮箱,否则没有成绩。如果下周发过去,已经超时了,获取不到这个作业的成绩了。

cancel()方法也有以下几种情况:

  • 任务还没启动,调用cancel(),直接取消不启动,返回true
  • 任务已完成,返回false
  • 任务正在运行中:根据传入参数判断是否中断线程

通常我们在确定任务能正确响应interrupt的时候,才会选择传入true

FutureTask

通过Callable来获取Future对象是有一定限制的,因为只能通过线程池的submit()方法来获取,无法直接交给一个线程或者Executor实现类来执行。
所以JUC又提供了一个类:FutureTask
这个类实现了RunnableFuture接口,而该接口同时实现了Future接口也实现了Runnable接口

public class FutureTask<V> implements RunnableFuture<V> 
public interface RunnableFuture<V> extends Runnable, Future<V>

所以它既可以被当做任务执行,也可以获得callable的返回值。

通过该类的构造方法,传入一个Callable对象,生成的FutureTask对象可以作为Runnable对象传入Thread或者Executor执行。

public FutureTask(Callable<V> callable)

下面就尝试一下FutureTask的使用

public static void main(String[] args) {
    Callable<String> callable = () -> {
        TimeUnit.SECONDS.sleep(2);
        return "FutureTask";
    };
    FutureTask<String> integerFutureTask = new FutureTask<>(callable);
    //作为runnable传入
    new Thread(integerFutureTask).start();
    try {
        //作为Future获取
        System.out.println(integerFutureTask.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值