Callable

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 的执行过程大致如下:

  1. 任务提交 :将 Callable 任务提交给一个线程池(例如,ExecutorService)。线程池会创建一个新的线程(或复用现有的空闲线程)来执行该任务。
  2. 任务执行 :线程池中的线程会调用 Callable.call() 方法执行任务的具体操作。
  3. 返回结果 :任务执行完成后,call() 方法会返回一个结果值(类型为 V)。如果 call() 方法抛出了异常,异常会被捕获并封装在 Future 中。
  4. 获取结果 :使用 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 被多个线程共享执行,它的结果也 只会被计算一次 ,之后所有的线程都会获得相同的结果。

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

相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码2 天前
别再前后端各写一套表单校验了
java·后端
大勇前进2 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu2 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile2 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白802 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端
二月龙2 天前
PHP 接口返回统一响应封装,让前后端对接更省心
后端
盖伦发发2 天前
软件工程SOLID 五大设计原则
后端·软件工程