Callable

Callable

Callable接口的定义:

  • 泛型类型 V:指定了 call() 方法返回值的类型。Callablecall() 方法会返回一个类型为 V 的值。
  • call() 方法 :与 Runnablerun() 方法类似,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() 是一个阻塞方法,直到任务执行完毕并返回结果。

CallableFuture 的配合

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

这就需要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.结果会被缓存

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 被多个线程共享执行,它的结果也 只会被计算一次 ,之后所有的线程都会获得相同的结果。

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

相关推荐
longxibo11 小时前
【第1章 环境搭建与项目结构解析】
java·后端·流程图
程序员老邢11 小时前
【产品底稿 11】架构规整收官:从混乱到清晰,工程结构、表命名、模块分层一次性定型
后端·架构·springboot·产品底稿·架构规整·模块分层·数据库规范
IT_陈寒12 小时前
React的useEffect把我坑惨了,这些闭包陷阱真要命
前端·人工智能·后端
薪火铺子12 小时前
SpringMVC请求处理流程源码解析(第1篇):请求入口与处理器映射
java·后端·spring
_Evan_Yao13 小时前
从 IP 路由到 Agent 路由:最长前缀匹配如何帮你分发任务?
java·网络·后端·网络协议·tcp/ip
.柒宇.13 小时前
AI掘金头条项目 Docker Compose 部署完整教程(附踩坑记录)
运维·后端·python·docker·容器·fastapi
Victor35614 小时前
MongoDB(118)如何在升级过程中进行数据备份?
后端
手握风云-14 小时前
Spring AI:让大模型住进 Spring 生态(三)
java·后端·spring
Victor35614 小时前
MongoDB(117)如何从旧版本迁移到新版本?
后端
pe7er17 小时前
window管理开发环境篇 - 持续更新
前端·后端