iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >java中怎么理解Callable接口
  • 432
分享到

java中怎么理解Callable接口

2023-06-16 08:06:55 432人浏览 独家记忆
摘要

本篇内容介绍了“java中怎么理解Callable接口”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Callable、Executor 与

本篇内容介绍了“java中怎么理解Callable接口”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

Callable、Executor 与 Future

既然是一个任务被执行并返回结果,那么我们先来看看具体的任务,也就是 Callable 接口。

任务:Callable

非常简单,只包含一个有泛型「返回值」的 call() 方法,需要在最后返回定义类型的结果。如果任务没有需要返回的结果,那么将泛型 V 设为 void  并return null;就可以了。对比的是 Runnable,另一个明显的区别则是 Callable可以抛出异常。

public interface Callable<V> {     V call() throws Exception; }   public interface Runnable {     public abstract void run(); }

执行:ExecutorService

说到线程就少不了线程池,而说到线程池肯定离不开 Executor 接口。下面这幅图是 Executor 的框架,我们常用的是其中的两个具体实现类  ThreadPoolExecutor 以及 ScheduledThreadPoolExecutor,在 Executors  类中通过静态方法获取。Executors 中包含了线程池以及线程工厂的构造,与 Executor 接口的关系类似于 Collection 接口和  Collections 类的关系。

java中怎么理解Callable接口

那么我们自顶向下,从源码上了解一下 Executor 框架,学习学习任务是如何被执行的。首先是 Executor 接口,其中只定义了 execute()  方法。

public interface Executor {     void execute(Runnable command); }

ExecutorService 接口继承了 Executor 接口,主要扩展了一系列的 submit() 方法以及对 executor  的终止和判断状态。以第一个 Future submit(Callable task);为例,其中 task 为用户定义的执行的异步任务,Future  表示了任务的执行结果,泛型 T 代表任务结果的类型。

public interface ExecutorService extends Executor {      void shutdown();                // 现有任务完成后停止线程池       List<Runnable> shutdownNow();   // 立即停止线程池      boolean isshutdown();           // 判断是否已停止      boolean isTerminated();      <T> Future<T> submit(Callable<T> task);        // 提交Callale任务      <T> Future<T> submit(Runnable task, T result);      Future<?> submit(Runnable task);      // 针对Callable集合的invokeAll()等方法 }

抽象类AbstractExecutorService 是 ThreadPoolExecutor  的基类,在下面的代码中,它实现了ExecutorService 接口中的 submit() 方法。注释中是对应的 newTaskFor()  方法的代码,非常简单,就是将传入的Callable 或 Runnable 参数封装成一个 FutureTask 对象。

// 1.第一个重载方法,参数为Callable public <T> Future<T> submit(Callable<T> task) {   if (task == null) throw new NullPointerException();   RunnableFuture<T> ftask = newTaskFor(task);   // return new FutureTask<T>(callable);   execute(ftask);   return ftask; }  // 2.第二个重载方法,参数为Runnable public Future<?> submit(Runnable task) {   if (task == null) throw new NullPointerException();   RunnableFuture<Void> ftask = newTaskFor(task, null);   // return new FutureTask<T>(task, null);   execute(ftask);   return ftask; }  // 3.第三个重载方法,参数为Runnable + 返回对象 public <T> Future<T> submit(Runnable task, T result) {   if (task == null) throw new NullPointerException();   RunnableFuture<T> ftask = newTaskFor(task, result);   // return new FutureTask<T>(task, result);   execute(ftask);   return ftask; }

那么也就是说,无论传入的是 Callable 还是 Runnable,submit() 方法其实就做了三件事

java中怎么理解Callable接口

具体来说,submit() 中首先生成了一个 RunnableFuture 引用的 FutureTask 实例,然后调用 execute()  方法来执行它,那么我们可以推测 FutureTask 继承自 RunnableFuture,而 RunnableFuture 又实现了  Runnable,因为execute() 的参数应为 Runnable 类型。上面还涉及到了 FutureTask 的构造函数,也来看一下。

public FutureTask(Callable<V> callable) {   this.callable = callable;   this.state = NEW; }  public FutureTask(Runnable runnable, V result) {   this.callable = Executors.callable(runnable, result); // 通过适配器将runnable在call()中执行并返回result   this.state = NEW; }

FutureTask 共有两个构造方法。第一个构造方法比较简单,对应上面的第一个 submit(),采用组合的方式封装Callable  并将状态设为NEW;而第二个构造方法对应上面的后两个 submit() 重载,不同之处是首先使用了Executors.callable来将 Runnable 和  result 组合成 Callable,这里采用了适配器RunnableAdapter implements Callable,巧妙地在 call() 中执行  Runnable 并返回结果。

static final class RunnableAdapter<T> implements Callable<T> {   final Runnable task;   final T result;                // 返回的结果;显然:需要在run()中赋值    RunnableAdapter(Runnable task, T result) {     this.task = task;     this.result = result;   }   public T call() {     task.run();     return result;   } }

在适配器设计模式中,通常包含目标接口 Target、适配器 Adapter 和被适配者 Adaptee  三类角色,其中目标接口代表客户端(当前业务系统)所需要的功能,通常为借口或抽象类;被适配者为现存的不能满足使用需求的类;适配器是一个转换器,也称  wrapper,用于给被适配者添加目标功能,使得客户端可以按照目标接口的格式正确访问。对于 RunnableAdapter 来说,Callable  是其目标接口,而 Runnable 则是被适配者。RunnableAdapter 通过覆盖 call() 方法使其可按照 Callable  的要求来使用,同时其构造方法中接收被适配者和目标对象,满足了 call() 方法有返回值的要求。

java中怎么理解Callable接口

那么总结一下 submit() 方法执行的流程,就是:「Callable 被封装在 Runnable 的子类中传入 execute()  得以执行」。

结果:Future

要说 Future 就是异步任务的执行结果其实并不准确,因为它代表了一个任务的执行过程,有状态、可以被取消,而 get()  方法的返回值才是任务的结果。

public interface Future<V> {      boolean cancel(boolean mayInterruptIfRunning);      boolean isCancelled();      boolean isDone();      V get() throws InterruptedException, ExecutionException;      V get(long timeout, TimeUnit unit)         throws InterruptedException, ExecutionException, TimeoutException; }

我们在上面中还提到了 RuunableFuture 和 FutureTask。从官方的注释来看,RuunableFuture 就是一个可以 run的  future,实现了 Runnable 和 Future 两个接口,在 run() 方法中执行完计算时应该将结果保存起来以便通过 get()获取。

public interface RunnableFuture<V> extends Runnable, Future<V> {          void run(); }

FutureTask 直接实现了 RunnableFuture 接口,作为执行过程,共有下面这几种状态,其中 COMPLETING  为一个暂时状态,表示正在设置结果或异常,对应的,设置完成后状态变为 NORMAL 或 EXCEPTIONAL;CANCELLED、INTERRUPTED  表示任务被取消或中断。在上面的构造方法中,将 state 初始化为 NEW。

private volatile int state;  private static final int NEW          = 0;  private static final int COMPLETING   = 1;  private static final int NORMAL       = 2;  private static final int EXCEPTIONAL  = 3;  private static final int CANCELLED    = 4;  private static final int INTERRUPTING = 5;  private static final int INTERRUPTED  = 6;

然后是 FutureTask 的主要内容,主要是 run() 和 get()。注意 outcome 的注释,无论是否发生异常返回的都是这个  outcome,因为在执行中如果执行成功就将结果设置给了它(set()),而发生异常时将异常赋给了他(setException()),而在获取结果时也都返回了  outcome(通过report())。

public class FutureTask<V> implements RunnableFuture<V> {          private Callable<V> callable;         // target,待执行的任务               private Object outcome; // 非volatile,通过CAS保证线程安全               public void run() {         ......         Callable<V> c = callable;         if (c != null && state == NEW) {             V result;             boolean ran;             try {                 result = c.call();            // 调用call()执行用户任务并获取结果                 ran = true;                   // 执行完成,ran置为true             } catch (Throwable ex) {          // 调用call()出现异常,而run()方法继续执行                  result = null;                  ran = false;                  setException(ex);                              // setException(Throwable t): compareAndSwapint(NEW, COMPLETING);  outcome = t;                   }             if (ran)                 set(result);                                // set(V v): compareAndSwapInt(NEW, COMPLETING);  outcome = v;         }     }               public V get() throws InterruptedException, ExecutionException {         int s = state;         if (s <= COMPLETING)             s = awaitDone(false, 0L);         // 加入队列等待COMPLETING完成,可响应超时、中断         return report(s);     }      public V get(long timeout, TimeUnit unit)         throws InterruptedException, ExecutionException, TimeoutException {         // 超时等待     }          private V report(int s) throws ExecutionException {         Object x = outcome;         if (s == NORMAL)                              // 将outcome作为执行结果返回             return (V)x;         if (s >= CANCELLED)             throw new CancellationException();         throw new ExecutionException((Throwable)x);   // 将outcome作为捕获的返回     } }

FutureTask 实现了 RunnableFuture 接口,所以有两方面的作用。

第一,作为 Runnable 传入 execute() 方法来执行,同时封装 Callable 对象并在 run() 中调用其 call()  方法;

第二,作为 Future 管理任务的执行状态,将 call() 的返回值保存在 outcome 中以通过 get()  获取。这似乎就能回答开头的两个问题,并且浑然天成,就好像是一个问题,除非发生异常的时候返回的不是任务的结果而是异常对象。

总结一下继承关系:

java中怎么理解Callable接口

二、使用举例

文章的标题有点唬人,说到底还是讲 Callable 的用法。现在我们知道了 Future 代表了任务执行的过程和结果,作为 call()  方法的返回值来获取执行结果;而 FutureTask 是一个 Runnable 的 Future,既是任务执行的过程和结果,又是 call  方法最终执行的载体。下面通过一个例子看看他们在使用上的区别。

首先创建一个任务,即定义一个任务类实现 Callable 接口,在 call() 方法里添加我们的操作,这里用耗时三秒然后返回 100  模拟计算过程。

class MyTask implements Callable<Integer> {     @Override     public Integer call() throws Exception {         System.out.println("子线程开始计算...");         for (int i=0;i<3;++i){             Thread.sleep(1000);             System.out.println("子线程计算中,用时 "+(i+1)+" 秒");         }         System.out.println("子线程计算完成,返回:100");         return 100;     } }

然后呢,创建一个线程池,并实例化一个 MyTask 备用。

ExecutorService executor = Executors.newCachedThreadPool(); MyTask task = new MyTask();

现在,分别使用 Future 和 FutureTask 来获取执行结果,看看他们有什么区别。

使用Future

Future 一般作为 submit() 的返回值使用,并在主线程中以阻塞的方式获取异步任务的执行结果。

System.out.println("主线程启动线程池"); Future<Integer> future = executor.submit(task); System.out.println("主线程得到返回结果:"+future.get()); executor.shutdown();

看看输出结果:

主线程启动线程池 子线程开始计算... 子线程计算中,用时 1 秒 子线程计算中,用时 2 秒 子线程计算中,用时 3 秒 子线程计算完成,返回:100 主线程得到返回结果:100

主线程启动线程池子线程开始计算...子线程计算中,用时 1 秒子线程计算中,用时 2 秒子线程计算中,用时 3  秒子线程计算完成,返回:100主线程得到返回结果:100

由于 get() 方法阻塞获取结果,所以输出顺序为子线程计算完成后主线程输出结果。

使用FutureTask

由于 FutureTask 集「任务与结果」于一身,所以我们可以使用 FutureTask 自身而非返回值来管理任务,这需要首先利用 Callable  对象来构造 FutureTask,并调用不同的submit()重载方法。

System.out.println("主线程启动线程池"); FutureTask<Integer> futureTask = new FutureTask<>(task); executor.submit(futureTask);                                 // 作为Ruunable传入submit()中 System.out.println("主线程得到返回结果:"+futureTask.get());    // 作为Future获取结果 executor.shutdown();

这段程序的输出与上面中完全相同,其实两者在实际执行中的区别也不大,虽然前者调用了submit(Callable  task)而后者调用了submit(Runnable task),但最终都通过execute(futuretask)来把任务加入线程池中。

“java中怎么理解Callable接口”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

--结束END--

本文标题: java中怎么理解Callable接口

本文链接: https://www.lsjlt.com/news/282860.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • java中怎么理解Callable接口
    本篇内容介绍了“java中怎么理解Callable接口”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Callable、Executor 与 ...
    99+
    2023-06-16
  • java callable接口怎么调用
    要调用Java Callable接口,可以使用ExecutorService类的submit()方法来提交Callable对象,并返...
    99+
    2023-10-24
    java
  • java中Callable接口的作用是什么
    java中Callable接口的作用是什么?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Java的特点有哪些Java的特点有哪些1.Java语言作为静态面向对象...
    99+
    2023-06-14
  • Java中Runnable与Callable接口有什么区别
    这篇文章主要介绍“Java中Runnable与Callable接口有什么区别”,在日常操作中,相信很多人在Java中Runnable与Callable接口有什么区别问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答...
    99+
    2023-07-05
  • Java中如何使用Callable和Future接口详解
    本篇文章给大家分享的是有关Java中如何使用Callable和Future接口详解,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Runnable是一个接口,而Thread是Ru...
    99+
    2023-06-20
  • Java的Synchronized原理与Callable接口实例分析
    这篇“Java的Synchronized原理与Callable接口实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Ja...
    99+
    2023-06-29
  • Java深入浅出分析Synchronized原理与Callable接口
    目录一、基本特点二、加锁工作过程偏向锁轻量级锁重量级锁三、其他的优化操作锁消除锁粗化四、Callable 接口一、基本特点 1. 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁....
    99+
    2024-04-02
  • Java并发教程之Callable和Future接口详解
    刚把Thread 的知识理了一遍。 Runnable是一个接口,而Thread是Runnable的一个实现类。 所以也就有了之前创建线程的两种方法 继承Thread ...
    99+
    2024-04-02
  • Java多线程Callable接口实现代码示例
    对于多线程,大家并不陌生,对于如何创建线程也是轻车熟路,对于使用new thread和实现runable接口的方式,不再多说。这篇博文我们介绍第三种:实现Callable接口。Callable接口接口定义:@FunctionalInterf...
    99+
    2023-05-30
    java 多线程 接口
  • java如何实现Callable接口创建线程类
    小编给大家分享一下java如何实现Callable接口创建线程类,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!实现 Callab...
    99+
    2024-04-02
  • Java中的抽象类和接口怎么理解
    这篇文章主要介绍“Java中的抽象类和接口怎么理解”,在日常操作中,相信很多人在Java中的抽象类和接口怎么理解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中的抽象类和接口怎么理解”的疑惑有所帮助!...
    99+
    2023-06-30
  • 如何深入理解Java中的接口
    今天就跟大家聊聊有关如何深入理解Java中的接口,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。一、前言前面我们说了抽象类的概述,我们对抽象类也有个认识和理解了,现在我们学习十分重要的...
    99+
    2023-06-21
  • Java中Future接口详解
    目录一、背景二、Future接口1、入门案例2、Future接口三、CompletableFuture类1、基础说明2、核心方法2.1 实例方法2.2 计算方法2.3 结果获取方法2...
    99+
    2023-05-16
    Java中Future接口 Java Future
  • Java中怎么利用接口来创建代理
    Java中怎么利用接口来创建代理?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。首先创建工厂bean,就是用来返回代理的FactoryBeanimport org....
    99+
    2023-06-15
  • Java中runnable和callable有什么区别
    这篇文章给大家分享的是有关Java中runnable和callable有什么区别的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;...
    99+
    2023-06-14
  • java中Cloneable接口怎么调用
    在Java中,Cloneable接口是一个标记接口,它没有任何方法。要使用Cloneable接口,只需要在要克隆的类上实现该接口即可...
    99+
    2023-09-26
    java
  • Java中的接口怎么实现
    这篇文章主要介绍“Java中的接口怎么实现”,在日常操作中,相信很多人在Java中的接口怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中的接口怎么实现”的疑惑有所帮助!接下来,请跟着小编一起来...
    99+
    2023-06-03
  • 怎么在java中定义接口
    今天就跟大家聊聊有关怎么在java中定义接口,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。常用的java框架有哪些1.SpringMVC,Spring Web MVC是一种基于Jav...
    99+
    2023-06-14
  • java接口怎么用
    这篇文章主要介绍了java接口怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。接口功能介绍‘纯’抽象类的实现(参见JAVA编程思想P/153)// Interface1....
    99+
    2023-06-03
  • java在接口中怎么调用另外一个接口
    在Java中,接口之间可以通过继承或实现的方式进行调用。1. 继承方式:如果一个接口A继承自另一个接口B,那么A可以调用B中的所有方...
    99+
    2023-09-13
    java
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作