5
头图

Preface

Asynchronous programming is a means of allowing programs to run concurrently. It allows multiple things same time. When the program calls a long-running method, it will not block the current execution flow, and the program can continue to run. When the method execution is completed, it will notify the main thread to obtain its execution result as needed or The reason for the failure exception. Using asynchronous programming can greatly improve the throughput of our programs, better face higher concurrency scenarios and make better use of existing system resources, and at the same time will reduce the waiting time of users to a certain extent. In this article, let's take a look at what are the ways to use asynchronous programming Java

Thread method

The easiest way to use asynchronous programming in the Java Thread to achieve. If you use JDK version 8 or higher, you can use Lambda expression will be more concise. In order to better reflect the efficiency of asynchronous, the following provides examples of synchronous and asynchronous versions as a comparison:

/**
 * @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();
  }

}

The synchronous execution is as follows:

1.png

Comment out the code of the synchronous calling version, and the result of asynchronous execution is as follows:

2.png

From the results of the two runs, it can be seen that the synchronous version takes 4002 ms , and the asynchronous version takes 2064 ms . The asynchronous execution time is reduced by nearly half. It can be seen that the use of asynchronous programming can greatly shorten the program running time.

The asynchronous thread code in the above example opens a thread doOneThing-Thread main method to asynchronously execute the doOneThing task. At this time, the thread main main thread, that is, the task doOneThing and the task doOtherThing run concurrently. After completing the doOtherThing task, the synchronization waiting for the thread doOneThing running is relatively simple overall.

But this example can only be used as an example. If you use the production environment, you will be at your own risk. There are two obvious problems in Thread

  1. There is no reuse to create threads. We know that frequent thread creation and destruction require a part of the overhead, and there is no limit to the number of threads in the example. If used improperly, the system threads may be exhausted, causing accidents. This problem can be solved by using thread pools.
  2. Asynchronous tasks cannot obtain the final execution result. This method in the example is not enough. At this time, you need to use the second FutureTask method described below.

FutureTask way

Starting from JDK 1.5 , the Future interface and FutureTask Future interface have been introduced to represent asynchronous calculation results. This FutureTask class implements only Future interface also implements Runnable interface A represents the results generated Runnable . It can be in these three states:

  • did not start when creating a FutureTask without executing the FutureTask.run() method
  • has started in the process of FutureTask.run()
  • has completed in FutureTask.run() normal execution method results or call FutureTask.cancel(boolean mayInterruptIfRunning) method and calling FutureTask.run() after the occurrence of abnormal termination process approach

FutureTask class implements the Future of opening and canceling tasks of the 06106a8f1b3be9 interface, querying whether the tasks are completed, and obtaining calculation results. To get FutureTask task, we can only get it by calling the getXXX() series of methods. These methods will be blocked when the result has not come out. At the same time, the task can be of Callable (with return results), or it can be of type Runnable No results are returned). We modified example of the above two methods modified to return the task String type using FutureTask method are as follows:

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);
}

Using the FutureTask asynchronous programming method Thread method. The essence is to start a new thread to do the doOneThing task and wait for the return. The results are as follows:

3.png

In this example, doOneThing and doOtherThing are all value-returning task (return String type result), we are in the main thread main create an asynchronous task FutureTask to perform doOneThing , then use ForkJoinPool.commonPool() create a thread pool (about ForkJoinPool described see Here ), and then call the execute method of the futureTask to the thread pool for execution.

Through the example, we can see that although FutureTask provides some methods for us to obtain the execution result of the task, whether the task is completed, etc., it is still more complicated to use. In some more complex scenarios (such as the relationship between FutureTask it is quite cumbersome, or as we call getXXX() time series method still before the task is finished block the calling thread can not reach the effect of asynchronous programming, based on these issues, in JDK 8 introduced in CompletableFuture class, let's look at how to use CompletableFuture To achieve asynchronous programming.

CompletableFuture way

JDK 8 introduced CompletableFuture class that implements Future and CompletionStage interface that provides a series asynchronous programming method, such as supplyAsync , runAsync and thenApplyAsync etc., in addition CompletableFuture Another important feature is that it allows two or more CompletableFuture performs operations to produce results. code show as below:

/**
 * @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);
  }

}

The execution results are as follows:

4.png

In the main thread main the first call method doOneThing() ways to open an asynchronous task and returns the corresponding CompletableFuture object, we named doOneThingFuture , then doOneThingFuture use based on CompletableFuture the thenCompose() way for doOneThingFuture method after the completion of the implementation, The asynchronous task created using its execution result as doOtherThing(String parameter)

We do not need to explicitly use ExecutorService , in CompletableFuture used internally Fork/Join framework for asynchronous processing tasks, therefore, it makes asynchronous code we write more concise. In addition, the CompletableFuture class is very powerful and it provides many convenient methods. For more information about CompletableFuture , please refer to the article .

Summarize

This article describes the Java in JDK are three ways to use asynchronous programming, these are the tools we have to achieve the most basic asynchronous programming, as well as above it Guava library provides ListenableFuture and Futures class and Spring framework Provide asynchronous execution capabilities, use @Async and other annotations to achieve asynchronous processing, if you are interested, you can learn and understand by yourself.


mghio
446 声望870 粉丝