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

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

相关推荐
宫水三叶的刷题日记几秒前
工商银行今年的年终奖。。
后端
大黄评测4 分钟前
双库协同,各取所长:.NET Core 中 PostgreSQL 与 SQLite 的优雅融合实战
后端
Java编程爱好者5 分钟前
Java 后端定时任务怎么选:@Scheduled、Quartz 还是 XXL-Job?
后端
Java编程爱好者5 分钟前
线程池用完不Shutdown,CPU和内存都快哭了
后端
神奇小汤圆21 分钟前
Unsafe魔法类深度解析:Java底层操作的终极指南
后端
神奇小汤圆1 小时前
浅析二叉树、B树、B+树和MySQL索引底层原理
后端
文艺理科生1 小时前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
前端·后端·架构
千寻girling1 小时前
主管:”人家 Node 框架都用 Nest.js 了 , 你怎么还在用 Express ?“
前端·后端·面试
南极企鹅1 小时前
springBoot项目有几个端口
java·spring boot·后端
Luke君607971 小时前
Spring Flux方法总结
后端