重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
本篇内容介绍了“java中怎么理解Callable接口”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
企业建站必须是能够以充分展现企业形象为主要目的,是企业文化与产品对外扩展宣传的重要窗口,一个合格的网站不仅仅能为公司带来巨大的互联网上的收集和信息发布平台,创新互联公司面向各种领域:成都木包装箱等成都网站设计、成都全网营销推广解决方案、网站设计等建站排名服务。
Callable、Executor 与 Future
既然是一个任务被执行并返回结果,那么我们先来看看具体的任务,也就是 Callable 接口。
任务:Callable
非常简单,只包含一个有泛型「返回值」的 call() 方法,需要在最后返回定义类型的结果。如果任务没有需要返回的结果,那么将泛型 V 设为 void 并return null;就可以了。对比的是 Runnable,另一个明显的区别则是 Callable可以抛出异常。
public interface Callable{ V call() throws Exception; } public interface Runnable { public abstract void run(); }
执行:ExecutorService
说到线程就少不了线程池,而说到线程池肯定离不开 Executor 接口。下面这幅图是 Executor 的框架,我们常用的是其中的两个具体实现类 ThreadPoolExecutor 以及 ScheduledThreadPoolExecutor,在 Executors 类中通过静态方法获取。Executors 中包含了线程池以及线程工厂的构造,与 Executor 接口的关系类似于 Collection 接口和 Collections 类的关系。
那么我们自顶向下,从源码上了解一下 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(); // 现有任务完成后停止线程池 ListshutdownNow(); // 立即停止线程池 boolean isShutdown(); // 判断是否已停止 boolean isTerminated(); Future submit(Callable task); // 提交Callale任务 Future submit(Runnable task, T result); Future> submit(Runnable task); // 针对Callable集合的invokeAll()等方法 }
抽象类AbstractExecutorService 是 ThreadPoolExecutor 的基类,在下面的代码中,它实现了ExecutorService 接口中的 submit() 方法。注释中是对应的 newTaskFor() 方法的代码,非常简单,就是将传入的Callable 或 Runnable 参数封装成一个 FutureTask 对象。
// 1.第一个重载方法,参数为Callable publicFuture submit(Callable task) { if (task == null) throw new NullPointerException(); RunnableFuture ftask = newTaskFor(task); // return new FutureTask (callable); execute(ftask); return ftask; } // 2.第二个重载方法,参数为Runnable public Future> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture ftask = newTaskFor(task, null); // return new FutureTask (task, null); execute(ftask); return ftask; } // 3.第三个重载方法,参数为Runnable + 返回对象 public Future submit(Runnable task, T result) { if (task == null) throw new NullPointerException(); RunnableFuture ftask = newTaskFor(task, result); // return new FutureTask (task, result); execute(ftask); return ftask; }
那么也就是说,无论传入的是 Callable 还是 Runnable,submit() 方法其实就做了三件事
具体来说,submit() 中首先生成了一个 RunnableFuture 引用的 FutureTask 实例,然后调用 execute() 方法来执行它,那么我们可以推测 FutureTask 继承自 RunnableFuture,而 RunnableFuture 又实现了 Runnable,因为execute() 的参数应为 Runnable 类型。上面还涉及到了 FutureTask 的构造函数,也来看一下。
public FutureTask(Callablecallable) { 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 RunnableAdapterimplements Callable { 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() 方法有返回值的要求。
那么总结一下 submit() 方法执行的流程,就是:「Callable 被封装在 Runnable 的子类中传入 execute() 得以执行」。
结果:Future
要说 Future 就是异步任务的执行结果其实并不准确,因为它代表了一个任务的执行过程,有状态、可以被取消,而 get() 方法的返回值才是任务的结果。
public interface Future{ 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 RunnableFutureextends Runnable, Future { /** * Sets this Future to the result of its computation unless it has been cancelled. */ 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 FutureTaskimplements RunnableFuture { private Callable callable; // target,待执行的任务 /** 保存执行结果或异常,在get()方法中返回/抛出 */ private Object outcome; // 非volatile,通过CAS保证线程安全 public void run() { ...... Callable 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() 获取。这似乎就能回答开头的两个问题,并且浑然天成,就好像是一个问题,除非发生异常的时候返回的不是任务的结果而是异常对象。
总结一下继承关系:
二、使用举例
文章的标题有点唬人,说到底还是讲 Callable 的用法。现在我们知道了 Future 代表了任务执行的过程和结果,作为 call() 方法的返回值来获取执行结果;而 FutureTask 是一个 Runnable 的 Future,既是任务执行的过程和结果,又是 call 方法最终执行的载体。下面通过一个例子看看他们在使用上的区别。
首先创建一个任务,即定义一个任务类实现 Callable 接口,在 call() 方法里添加我们的操作,这里用耗时三秒然后返回 100 模拟计算过程。
class MyTask implements Callable{ @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("主线程启动线程池"); Futurefuture = 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("主线程启动线程池"); FutureTaskfutureTask = 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接口”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!