Callable
Callable接口的定义:

- 泛型类型
V:指定了call()方法返回值的类型。Callable的call()方法会返回一个类型为V的值。 call()方法 :与Runnable的run()方法类似,call()方法包含了任务的执行逻辑。不同的是,call()方法可以返回一个结果 (通过返回值V),并且它可以抛出异常
与 Runnable 的对比
| 特性 | Runnable |
Callable |
|---|---|---|
| 返回值 | run() 方法没有返回值 |
call() 方法有返回值 |
| 异常处理 | run() 方法不能抛出任何异常 |
call() 方法可以抛出异常 |
| 使用场景 | 用于不需要返回值或异常处理的场景 | 用于需要返回值或可能抛出异常的场景 |
Callable 的工作原理
Callable 的原理是基于 线程池 和 任务提交机制 ,尤其是 ExecutorService,可以通过 ExecutorService.submit() 方法提交任务,并获取 Future 对象。Callable 的执行过程大致如下:
- 任务提交 :将
Callable任务提交给一个线程池(例如,ExecutorService)。线程池会创建一个新的线程(或复用现有的空闲线程)来执行该任务。 - 任务执行 :线程池中的线程会调用
Callable.call()方法执行任务的具体操作。 - 返回结果 :任务执行完成后,
call()方法会返回一个结果值(类型为V)。如果call()方法抛出了异常,异常会被捕获并封装在Future中。 - 获取结果 :使用
Future.get()方法可以获取Callable执行的返回值。Future.get()是一个阻塞方法,直到任务执行完毕并返回结果。
Callable 与 Future 的配合
java
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?
这就需要callable与Future的配合使用了,我们查看源码可以看到

Callable原本与Thread没有联系的,是通过RunnableFuture接口建立了联系;
为了使 Callable 可以与 Thread 配合使用,java.util.concurrent 包中引入了 RunnableFuture 接口,它继承了 Runnable 和 Future 接口:

Runnable接口使得RunnableFuture可以在Thread中被执行。Future接口提供了获取结果和取消任务的能力。
于是在new Thread()里边就可以使用Callable。
FutureTask 实现
RunnableFuture 接口的实现类是 FutureTask,它实现了 Callable、Runnable 和 Future,这就是 Callable 和 Thread 之间的桥梁。FutureTask 可以包装一个 Callable 实例并通过 run() 方法在 Thread 中执行。

但是有两个问题
1.Future.get()
异常处理
- 如果
Callable中的call()方法抛出了异常,Future.get()会 包装 这个异常并重新抛出。 - 异常类型为
ExecutionException,可以通过getCause()获取原始的异常。
不过这个 get是阻塞的,需要等待获取到里边的内容才会往下继续进行;所以一般会将它放到代码的最后一步执行,防止阻塞线程;或是异步处理;
2.结果会被缓存
java
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 被多个线程共享执行,它的结果也 只会被计算一次 ,之后所有的线程都会获得相同的结果。

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