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

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

相关推荐
踏浪无痕1 分钟前
从 Guava ListenableFuture 学习生产级并发调用实践
后端·面试·架构
❀͜͡傀儡师4 分钟前
SpringBoot 扫码登录全流程:UUID 生成、状态轮询、授权回调详解
java·spring boot·后端
可观测性用观测云13 分钟前
观测云在企业应用性能故障分析场景中的最佳实践
后端
一 乐18 分钟前
酒店预约|基于springboot + vue酒店预约系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端
我是谁的程序员1 小时前
iOS CPU 占用率在性能问题中的表现形式
后端
一灰灰1 小时前
开发调试与生产分析的利器:MyBatis SQL日志合并插件,让复杂日志秒变可执行SQL
chrome·后端·mybatis
Code blocks1 小时前
SpringBoot从0-1集成Minio对象存储
java·spring boot·后端
疯狂的程序猴1 小时前
iOS 上架需要哪些准备,围绕证书、描述文件和上传方式等关键环节展开分析
后端
无限大62 小时前
为什么"API"很重要?——从封闭系统到开放生态
后端