Callable

190 阅读3分钟

Callable

Callable接口的定义:

  • 泛型类型 V:指定了 call() 方法返回值的类型。Callablecall() 方法会返回一个类型为 V 的值。
  • call() 方法:与 Runnablerun() 方法类似,call() 方法包含了任务的执行逻辑。不同的是,call() 方法可以返回一个结果(通过返回值 V),并且它可以抛出异常

Runnable 的对比

特性RunnableCallable
返回值run() 方法没有返回值call() 方法有返回值
异常处理run() 方法不能抛出任何异常call() 方法可以抛出异常
使用场景用于不需要返回值或异常处理的场景用于需要返回值或可能抛出异常的场景

Callable 的工作原理

Callable 的原理是基于 线程池任务提交机制,尤其是 ExecutorService,可以通过 ExecutorService.submit() 方法提交任务,并获取 Future 对象。Callable 的执行过程大致如下:

  1. 任务提交:将 Callable 任务提交给一个线程池(例如,ExecutorService)。线程池会创建一个新的线程(或复用现有的空闲线程)来执行该任务。
  2. 任务执行:线程池中的线程会调用 Callable.call() 方法执行任务的具体操作。
  3. 返回结果:任务执行完成后,call() 方法会返回一个结果值(类型为 V)。如果 call() 方法抛出了异常,异常会被捕获并封装在 Future
  4. 获取结果:使用 Future.get() 方法可以获取 Callable 执行的返回值。Future.get() 是一个阻塞方法,直到任务执行完毕并返回结果。

CallableFuture 的配合

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask<>(new MyThread());
        new Thread(futureTask).start();
        Object o = futureTask.get();
        System.out.println(o);
    }
}

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("callable");
        return 123;
    }
}

如何在Thread中使用Callable

这就需要callableFuture的配合使用了,我们查看源码可以看到

Callable原本与Thread没有联系的,是通过RunnableFuture接口建立了联系;

为了使 Callable 可以与 Thread 配合使用,java.util.concurrent 包中引入了 RunnableFuture 接口,它继承了 RunnableFuture 接口:

  • Runnable 接口使得 RunnableFuture 可以在 Thread 中被执行。
  • Future 接口提供了获取结果和取消任务的能力。

于是在new Thread()里边就可以使用Callable

FutureTask 实现

RunnableFuture 接口的实现类是 FutureTask,它实现了 CallableRunnableFuture,这就是 CallableThread 之间的桥梁。FutureTask 可以包装一个 Callable 实例并通过 run() 方法在 Thread 中执行。

但是有两个问题

1.Future.get()

异常处理

  • 如果 Callable 中的 call() 方法抛出了异常,Future.get()包装 这个异常并重新抛出。
  • 异常类型为 ExecutionException,可以通过 getCause() 获取原始的异常。

不过这个get是阻塞的,需要等待获取到里边的内容才会往下继续进行;所以一般会将它放到代码的最后一步执行,防止阻塞线程;或是异步处理;

2.结果会被缓存

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask<>(new MyThread());//适配类
        new Thread(futureTask, "A").start();
        new Thread(futureTask, "B").start();
        Object o = futureTask.get();
        System.out.println(o);
    }
}

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("callable");
        return 123;
    }
}

在这个代码中 你觉得"callable" 会打印几次?

结果只是打印了一次,因为FutureTask 有一个非常重要的特性:它缓存任务的执行结果。即使同一个 FutureTask 被多个线程共享执行,它的结果也 只会被计算一次 ,之后所有的线程都会获得相同的结果。

通过源码可以得知,是否已经被缓存了。