解析Java异步之call future

一、概述

我们大家都知道,在 Java 中创建线程主要有三种方式:

  • 继承 Thread 类;
  • 实现 Runnable 接口;
  • 实现 Callable 接口。

而后两者的区别在于 Callable 接口中的 call() 方法可以异步地返回一个计算结果 Future,并且一般需要配合ExecutorService 来执行。这一套操作在代码实现上似乎也并不难,可是对于call()方法具体怎么(被ExecutorService)执行的,以及 Future 这个结果是怎么获取的,却又不是很清楚了。

那么本篇文章,我们就一起来学习下 Callable 接口以及 Future 的使用,主要面向两个问题:

  • 承载着具体任务的 call() 方法如何被执行的?
  • 任务的执行结果如何得到?

你可能会说,这两个难道不是一个问题吗?任务执行了就会有返回结果,而返回结果也一定是任务执行了才返回的,难道还能返回一个其他任务的结果么??不要着急,耐心的看下去,你就会发现,这两个还真的就是一个问题。

本文将分为两个部分,第一部分分别介绍 任务执行、以及结果这三个概念在 Java API 中的实体和各自的继承关系,第二部分通过一个简单的例子回顾他们的用法,再理解下这两个问题的答案。

二、Callable、Executor 与 Future

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

2.1、任务:Callable

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

public interface Callable<V> {
    V call() throws Exception;
}

public interface Runnable {
    public abstract void run();
}

2.2、执行:ExecutorService

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

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

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

ExecutorService 接口继承了 Executor 接口,主要扩展了一系列的 submit() 方法以及对 executor 的终止和判断状态。以第一个<T> Future<T> submit(Callable<T> 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()等方法
}

抽象类AbstractExecutorServiceThreadPoolExecutor 的基类,在下面的代码中,它实现了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() 方法其实就做了三件事

具体来说,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() 方法有返回值的要求。

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

2.3、结果: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> {
    /**
     * 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 FutureTask<V> implements RunnableFuture<V> {

    private Callable<V> callable;         // target,待执行的任务

    /** 保存执行结果或异常,在get()方法中返回/抛出 */
    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() 获取。这似乎就能回答开头的两个问题,并且浑然天成,就好像是一个问题,除非发生异常的时候返回的不是任务的结果而是异常对象。

总结一下继承关系:

三、使用举例

文章的标题有点唬人,说到底还是讲 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 来获取执行结果,看看他们有什么区别。

3.1、使用Future

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

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

看看输出结果:

主线程启动线程池

子线程开始计算...

子线程计算中,用时 1 秒

子线程计算中,用时 2 秒

子线程计算中,用时 3 秒

子线程计算完成,返回:100

主线程得到返回结果:100

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

3.2、使用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<T> task)而后者调用了submit(Runnable task),但最终都通过execute(futuretask)来把任务加入线程池中。

四、总结

上面大费周章其实只是尽可能细致地讲清楚了 Callable 中的任务是如何执行的,总结起来就是:

线程池中,submit() 方法实际上将 Callable 封装在 FutureTask 中,将其作为 Runnable 的子类传给 execute()真正执行;FutureTask 在 run() 中调用 Callable 对象的 call() 方法并接收返回值或捕获异常保存在Object outcome中,同时管理执行过程中的状态state;FutureTask 同时作为 Future 的子类,通过 get() 返回任务的执行结果,若未执行完成则通过等待队列进行阻塞等待完成;

FutureTask 作为一个 Runnable 的 Future,其中最重要的两个方法如下。

以上就是解析Java异步之call future的详细内容,更多关于Java异步 call future的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解java 三种调用机制(同步、回调、异步)

    1:同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用 2:回调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口: 3:异步调用:一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口 具体说来:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法, 实例1:使用java中Timer来在给定时间间隔发送通知,每隔十秒打印一次数据 Tim

  • java 中同步、异步、阻塞和非阻塞区别详解

    java 中同步.异步.阻塞和非阻塞区别详解 简单点说: 阻塞就是干不完不准回来,一直处于等待中,直到事情处理完成才返回: 非阻塞就是你先干,我先看看有其他事没有,一发现事情被卡住,马上报告领导. 我们拿最常用的send和recv两个函数来说吧... 比如你调用send函数发送一定的Byte,在系统内部send做的工作其实只是把数据传输(Copy)到TCP/IP协议栈的输出缓冲区,它执行成功并不代表数据已经成功的发送出去了,如果TCP/IP协议栈没有足够的可用缓冲区来保存你Copy过来的数据的话

  • 简述JAVA同步、异步、阻塞和非阻塞之间的区别

    同步和异步,阻塞和非阻塞是大家经常会听到的概念,但是它们是从不同维度来描述一件事情,常常很容易混为一谈. 1. 同步和异步 同步和异步描述的是消息通信的机制. 同步 当一个request发送出去以后,会得到一个response,这整个过程就是一个同步调用的过程.哪怕response为空,或者response的返回特别快,但是针对这一次请求而言就是一个同步的调用. 异步 当一个request发送出去以后,没有得到想要的response,而是通过后面的callback.状态或者通知的方式获得结果.可

  • Java异步处理机制实例详解

    通常同步意味着一个任务的某个处理过程会对多个线程在用串行化处理,而异步则意味着某个处理过程可以允许多个线程同时处理.下面我们就来看看有关异步处理的详细内容. 异步通常代表着更好的性能,因为它很大程度上依赖于缓冲,是典型的使用空间换时间的做法,例如在计算机当中,高速缓存作为cpu和磁盘io之间的缓冲地带协调cpu高速计算能力和磁盘的低速读写能力. volatile 应用场景:检查一个应用执行关闭或中断状态.因为此关键字拒绝了虚拟对一个变量多次赋值时的优化从而保证了虚拟机一定会检查被该关键字修饰的变

  • java实现异步导出数据

    问题概述: 使用java作为后台语言,用poi导出数据时无法异步导出,当数据量稍微大点,就会出现页面傻瓜式等待 (点击导出后,页面无任何反应和提示,还以为此功能无效.然则几秒后浏览器才响应.)这样体验非常 不好. 解决办法: 很简单,将下载数据分离为一个单独方法.在触发导出后,先获取并封装数据(数据量大的话这个过程正好给页面做一个等待框,提示正在下载数据),完成后给前台返回一个状态,当前台收到返回正确返回状态后再关闭等待框并调用下载方法. demo: 1.获取并封装数据 @RequestMapp

  • Java并发 CompletableFuture异步编程的实现

    前面我们不止一次提到,用多线程优化性能,其实不过就是将串行操作变成并行操作.如果仔细观察,你还会发现在串行转换成并行的过程中,一定会涉及到异步化,例如下面的示例代码,现在是串行的,为了提升性能,我们得把它们并行化. // 以下两个方法都是耗时操作 doBizA(); doBizB(); //创建两个子线程去执行就可以了,两个操作已经被异步化了. new Thread(()->doBizA()) .start(); new Thread(()->doBizB()) .start(); 异步化,是

  • Java8新的异步编程方式CompletableFuture实现

    一. Future JDK 5引入了Future模式.Future接口是Java多线程Future模式的实现,在java.util.concurrent包中,可以来进行异步计算. Future模式是多线程设计常用的一种设计模式.Future模式可以理解成:我有一个任务,提交给了Future,Future替我完成这个任务.期间我自己可以去做任何想做的事情.一段时间之后,我就便可以从Future那儿取出结果. Future的接口很简单,只有五个方法. public interface Future<

  • 处理java异步事件的阻塞和非阻塞方法分析

    前言 由于多核系统普遍存在,并发性编程的应用无疑比以往任何时候都要广泛.但并发性很难正确实现,用户需要借助新工具来使用它.很多基于 JVM 的语言都属于这类开发工具,Scala 在这一领域尤为活跃.本系列文章将介绍一些针对 Java 和 Scala 语言的较新的并发性编程方法. 在任何并发性应用程序中,异步事件处理都至关重要.事件来源可能是不同的计算任务.I/O 操作或与外部系统的交互.无论来源是什么,应用程序代码都必须跟踪事件,协调为响应事件而采取的操作. Java 应用程序可采用两种基本的异

  • java异步写日志到文件中实现代码

    java异步写日志到文件中详解 实现代码: package com.tydic.ESUtil; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.Properties; public class LogWriter { // 日志的配置文件 publi

  • 解析Java异步之call future

    一.概述 我们大家都知道,在 Java 中创建线程主要有三种方式: 继承 Thread 类: 实现 Runnable 接口: 实现 Callable 接口. 而后两者的区别在于 Callable 接口中的 call() 方法可以异步地返回一个计算结果 Future,并且一般需要配合ExecutorService 来执行.这一套操作在代码实现上似乎也并不难,可是对于call()方法具体怎么(被ExecutorService)执行的,以及 Future 这个结果是怎么获取的,却又不是很清楚了. 那么

  • java多线程编程同步器Future和FutureTask解析及代码示例

    publicinterfaceFuture<V>Future表示异步计算的结果.它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果.计算完成后只能使用get方法来获取结果,如有必要,计算完成前可以阻塞此方法.取消则由cancel方法来执行.还提供了其他方法,以确定任务是正常完成还是被取消了.一旦计算完成,就不能再取消计算.如果为了可取消性而使用Future但又不提供可用的结果,则可以声明Future<?>形式类型.并返回null作为底层任务的结果. Future主要

  • Java异步编程工具Twitter Future详解

    目录 异步编程(Twitter Future) 为啥要异步 基本用法 1.封装计算逻辑,异步返回. 2.异步计算结果串联异步处理 3.并行多个异步任务,统一等待结果 4.异步错误处理 Twitter包装 pom依赖 1.封装计算逻辑,异步返回 2.异步计算结果串联异步处理 3.并行多个异步任务 4.错误处理 其他用法 其他工具 异步编程(Twitter Future) 为啥要异步 异步编程有点难以理解,这东西感觉不符合常理,因为我们思考都是按照串行的逻辑,事都是一件一件办.但在异步计算的情况下,

  • java多线程之Future和FutureTask使用实例

    Executor框架使用Runnable 作为其基本的任务表示形式.Runnable是一种有局限性的抽象,然后可以写入日志,或者共享的数据结构,但是他不能返回一个值. 许多任务实际上都是存在延迟计算的:执行数据库查询,从网络上获取资源,或者某个复杂耗时的计算.对于这种任务,Callable是一个更好的抽象,他能返回一个值,并可能抛出一个异常.Future表示一个任务的周期,并提供了相应的方法来判断是否已经完成或者取消,以及获取任务的结果和取消任务. public interface Callab

  • 深度源码解析Java 线程池的实现原理

    java 系统的运行归根到底是程序的运行,程序的运行归根到底是代码的执行,代码的执行归根到底是虚拟机的执行,虚拟机的执行其实就是操作系统的线程在执行,并且会占用一定的系统资源,如CPU.内存.磁盘.网络等等.所以,如何高效的使用这些资源就是程序员在平时写代码时候的一个努力的方向.本文要说的线程池就是一种对 CPU 利用的优化手段. 线程池,百度百科是这么解释的: 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的

  • java异步编程详解

    很多时候我们都希望能够最大的利用资源,比如在进行IO操作的时候尽可能的避免同步阻塞的等待,因为这会浪费CPU的资源.如果在有可读的数据的时候能够通知程序执行读操作甚至由操作系统内核帮助我们完成数据的拷贝,这再好不过了.从NIO到CompletableFuture.Lambda.Fork/Join,java一直在努力让程序尽可能变的异步甚至拥有更高的并行度,这一点一些函数式语言做的比较好,因此java也或多或少的借鉴了某些特性.下面介绍一种非常常用的实现异步操作的方式. 考虑有一个耗时的操作,操作

  • 5种必会的Java异步调用转同步的方法你会几种

    Sunny先来说一下对异步和同步的理解: 同步调用:调用方在调用过程中,持续等待返回结果. 异步调用:调用方在调用过程中,不直接等待返回结果,而是执行其他任务,结果返回形式通常为回调函数. 其实,两者的区别还是很明显的,这里也不再细说,我们主要来说一下Java如何将异步调用转为同步.换句话说,就是需要在异步调用过程中,持续阻塞至获得调用结果. 不卖关子,先列出五种方法,然后一一举例说明: 使用wait和notify方法 使用条件锁 Future 使用CountDownLatch 使用Cyclic

  • Java异步调用转同步的方法

    先来说一下对异步和同步的理解: 同步调用:调用方在调用过程中,持续等待返回结果. 异步调用:调用方在调用过程中,不直接等待返回结果,而是执行其他任务,结果返回形式通常为回调函数. 其实,两者的区别还是很明显的,这里也不再细说,我们主要来说一下Java如何将异步调用转为同步.换句话说,就是需要在异步调用过程中,持续阻塞至获得调用结果. 不卖关子,先列出五种方法,然后一一举例说明: 使用wait和notify方法 使用条件锁 Future 使用CountDownLatch 使用CyclicBarri

  • Java异步非阻塞编程的几种方式总结

    1 服务端执行,最简单的同步调用方式: 缺陷: 服务端响应之前,IO会阻塞在: java.net.SocketInputStream#socketRead0 的native方法上: 2 JDK NIO & Future java 1.5之后 优点:主线程可以不用等待IO响应,可以去做点其他的,比如说再发送一个IO请求,可以等到一起返回; 缺点:主线程在等待结果返回过程中依然需要等待,没有根本解决此问题; 3 使用Callback回调方式 优点:主线程完成发送请求后,再也不用关心这个逻辑,去执行其

  • 说说Java异步调用的几种方式

    目录 一.通过创建新线程 二.通过线程池 三.通过@Async注解 四.通过CompletableFuture 日常开发中,会经常遇到说,前台调服务,然后触发一个比较耗时的异步服务,且不用等异步任务的处理结果就对原服务进行返回.这里就涉及的Java异步调用的一个知识.下面本文尝试将Java异步调用的多种方式进行归纳. 一.通过创建新线程 首先的我们得认识到,异步调用的本质,其实是通过开启一个新的线程来执行.如以下例子: public static void main(String[] args)

随机推荐