关于java:Java-异步编程的几种方式

38次阅读

共计 5325 个字符,预计需要花费 14 分钟才能阅读完成。

前言

异步编程是让程序并发运行的一种伎俩。它容许多个事件 同时产生,当程序调用须要长时间运行的办法时,它不会阻塞以后的执行流程,程序能够持续运行,当办法执行实现时告诉给主线程依据须要获取其执行后果或者失败异样的起因。应用异步编程能够大大提高咱们程序的吞吐量,能够更好的面对更高的并发场景并更好的利用现有的系统资源,同时也会肯定水平上缩小用户的等待时间等。本文咱们一起来看看在 Java 语言中应用异步编程有哪些形式。

Thread 形式

Java 语言中最简略应用异步编程的形式就是创立一个 Thread 来实现,如果你应用的 JDK 版本是 8 以上的话,能够应用 Lambda 表达式 会更加简洁。为了能更好的体现出异步的高效性,上面提供同步版本和异步版本的示例作为对照:

/**
 * @author mghio
 * @since 2021-08-01
 */
public class SyncWithAsyncDemo {public static void doOneThing() {
    try {Thread.sleep(2000);
    } catch (InterruptedException e) {e.printStackTrace();
    }
    System.out.println("doOneThing ---->>> success");
  }

  public static void doOtherThing() {
    try {Thread.sleep(2000);
    } catch (InterruptedException e) {e.printStackTrace();
    }
    System.out.println("doOtherThing ---->>> success");
  }

  public synchronized static void main(String[] args) throws InterruptedException {StopWatch stopWatch = new StopWatch("SyncWithAsyncDemo");
    stopWatch.start();

    // 同步调用版本
    // testSynchronize();

    // 异步调用版本
    testAsynchronize();

    stopWatch.stop();
    System.out.println(stopWatch);
  }

  private static void testAsynchronize() throws InterruptedException {System.out.println("-------------------- testAsynchronize --------------------");

    // 创立一个线程执行 doOneThing
    Thread doOneThingThread = new Thread(SyncWithAsyncDemo::doOneThing, "doOneThing-Thread");
    doOneThingThread.start();

    doOtherThing();
    // 期待 doOneThing 线程执行实现
    doOneThingThread.join();}

  private static void testSynchronize() {System.out.println("-------------------- testSynchronize --------------------");

    doOneThing();
    doOtherThing();}

}

同步执行的运行如下:

正文掉同步调用版本的代码,失去异步执行的后果如下:

从两次的运行后果能够看出,同步版本耗时 4002 ms,异步版本执行耗时 2064 ms,异步执行耗时缩小将近一半,能够看出应用异步编程后能够大大缩短程序运行工夫。

下面的示例的异步线程代码在 main 办法内开启了一个线程 doOneThing-Thread 用来异步执行 doOneThing 工作,在这时该线程与 main 主线程并发运行,也就是工作 doOneThing 与工作 doOtherThing 并发运行,则等主线程运行完 doOtherThing 工作后同步期待线程 doOneThing 运行结束,整体还是比较简单的。

然而这个示例只能作为示例应用,如果用到了生产环境产生事变后果自负,应用下面这种 Thread 形式异步编程存在两个显著的问题。

  1. 创立线程没有复用。咱们晓得频繁的线程创立与销毁是须要一部分开销的,而且示例里也没有限度线程的个数,如果使用不当可能会把零碎线程用尽,从而引发事变,这个问题应用线程池能够解决。
  2. 异步工作无奈获取最终的执行后果。示例中的这种形式是满足不了的,这时候就须要应用上面介绍的第二种 FutureTask 的形式了。

FutureTask 形式

JDK 1.5 开始,引入了 Future 接口和实现 Future 接口的 FutureTask 类来示意异步计算结果。这个 FutureTask 类不仅实现了 Future 接口还实现了 Runnable 接口,示意一种可生成后果的 Runnable。其能够处于这三种状态:

  • 未启动 当创立一个 FutureTask 没有执行 FutureTask.run() 办法之前
  • 已启动FutureTask.run() 办法执行的过程中
  • 已实现FutureTask.run() 办法失常执行后果或者调用了 FutureTask.cancel(boolean mayInterruptIfRunning) 办法以及在调用 FutureTask.run() 办法的过程中产生异样完结后

FutureTask 类实现了 Future 接口的开启和勾销工作、查问工作是否实现、获取计算结果办法。要获取 FutureTask 工作的后果,咱们只能通过调用 getXXX() 系列办法能力获取,当后果还没进去时候这些办法会被阻塞,同时这了工作能够是 Callable 类型(有返回后果),也能够是 Runnable 类型(无返回后果)。咱们批改下面的示例把两个工作办法批改为返回 String 类型,应用 FutureTask 的办法如下:

private static void testFutureTask() throws ExecutionException, InterruptedException {System.out.println("-------------------- testFutureTask --------------------");

    // 创立一个 FutureTask(doOneThing 工作)FutureTask<String> futureTask = new FutureTask<>(FutureTaskDemo::doOneThing);
    // 应用线程池执行 doOneThing 工作
    ForkJoinPool.commonPool().execute(futureTask);

    // 执行 doOtherThing 工作
    String doOtherThingResult = doOtherThing();

    // 同步期待线程执行 doOneThing 工作完结
    String doOneThingResult = futureTask.get();

    // 工作执行后果输入
    System.out.println("doOneThingResult ---->>>" + doOneThingResult);
    System.out.println("doOtherThingResult ---->>>" + doOtherThingResult);
}

应用 FutureTask 异步编程形式的耗时和下面的 Thread 形式是差不多的,其本质都是另起一个线程去做 doOneThing 工作而后期待返回,运行后果如下:

这个示例中,doOneThingdoOtherThing 都是有返回值的工作(都返回 String 类型后果),咱们在主线程 main 中创立一个异步工作 FutureTask 来执行 doOneThing,而后应用 ForkJoinPool.commonPool() 创立线程池(无关 ForkJoinPool 的介绍见 这里),而后调用了线程池的 execute 办法把 futureTask 提交到线程池来执行。

通过示例能够看到,尽管 FutureTask 提供了一些办法让咱们获取工作的执行后果、工作是否实现等,然而应用还是比较复杂,在一些较为简单的场景(比方多个 FutureTask 之间的关系示意)的编码还是比拟繁琐,还是当咱们调用 getXXX() 系列办法时还是会在工作执行结束前阻塞调用线程,达不到异步编程的成果,基于这些问题,在 JDK 8 中引入了 CompletableFuture 类,上面来看看如何应用 CompletableFuture 来实现异步编程。

CompletableFuture 形式

JDK 8 中引入了 CompletableFuture 类,实现了 FutureCompletionStage 接口,为异步编程提供了一些列办法,如 supplyAsyncrunAsyncthenApplyAsync 等,除此之外 CompletableFuture 还有一个重要的性能就是能够让两个或者多个 CompletableFuture 进行运算来产生后果。代码如下:

/**
 * @author mghio
 * @since 2021-08-01
 */
public class CompletableFutureDemo {public static CompletableFuture<String> doOneThing() {return CompletableFuture.supplyAsync(() -> {
      try {Thread.sleep(2000);
      } catch (InterruptedException e) {e.printStackTrace();
      }
      return "doOneThing";
    });
  }

  public static CompletableFuture<String> doOtherThing(String parameter) {return CompletableFuture.supplyAsync(() -> {
      try {Thread.sleep(2000);
      } catch (InterruptedException e) {e.printStackTrace();
      }
      return parameter + "" +"doOtherThing";
    });
  }

  public static void main(String[] args) throws ExecutionException, InterruptedException {StopWatch stopWatch = new StopWatch("CompletableFutureDemo");
    stopWatch.start();

    // 异步执行版本
    testCompletableFuture();

    stopWatch.stop();
    System.out.println(stopWatch);
  }

  private static void testCompletableFuture() throws InterruptedException, ExecutionException {
    // 先执行 doOneThing 工作,后执行 doOtherThing 工作
    CompletableFuture<String> resultFuture = doOneThing().thenCompose(CompletableFutureDemo::doOtherThing);

    // 获取工作后果
    String doOneThingResult = resultFuture.get();

    // 获取执行后果
    System.out.println("DoOneThing and DoOtherThing execute finished. result =" + doOneThingResult);
  }

}

执行后果如下:

在主线程 main 中首先调用了办法 doOneThing() 办法开启了一个异步工作,并返回了对应的 CompletableFuture 对象,咱们取名为 doOneThingFuture,而后在 doOneThingFuture 的根底上应用 CompletableFuturethenCompose() 办法,让 doOneThingFuture 办法执行实现后,应用其执行后果作为 doOtherThing(String parameter) 办法的参数创立的异步工作返回。

咱们不须要显式应用 ExecutorService,在 CompletableFuture 外部应用的是 Fork/Join 框架异步解决工作,因而,它使咱们编写的异步代码更加简洁。此外,CompletableFuture 类性能很弱小其提供了和很多不便的办法,更多对于 CompletableFuture 的应用请见 这篇。

总结

本文介绍了在 Java 中的 JDK 应用异步编程的三种形式,这些是咱们最根底的实现异步编程的工具,在其之上的还有 Guava 库提供的 ListenableFuture 和 Futures 类以及 Spring 框架提供的异步执行能力,应用 @Async 等注解实现异步解决,感兴趣的话能够自行学习理解。

正文完
 0