Java实现异步的几种方式

异步编程在对响应时间近乎严苛的今天,受到了越来越多的关注,尤其是在IO密集型业务中。对比传统的同步模式,异步编程可以提高服务器的响应时间和处理业务的能力,从而达到快速给用户响应的效果。

代码前置:方法中会直接使用到线程池和print函数

public class TestAsync {
    // 创建一个线程池,大小为10
    ExecutorService executorService = Executors.newFixedThreadPool(10);

    private String printThread(String message) {
        return Thread.currentThread().getName() + " " + message;
    }
    //下面的示例代码...
}

1 Future

①介绍

  • java.util.concurrent.Future是JDK5引入的,用来获取一个异步计算的结果。可以使用isDone方法检查计算是否完成,也可以使用get阻塞住调用线程,直到计算完成返回结果,使用cancel方法停止任务的执行。
  • FutureTask.java是对Futre和Runnable最简单的实现,实现了run函数,所以可以直接执行,任务执行结束通过set()保存结果,setException()保存异常信息。通常配合executorService.submit()一起使用,ExecutorService中将任务包装成FutureTask执行execute();

样例如下图:

java异步执行方法 java 异步执行_java异步执行方法

②代码实现

public class TestAsync {
    // 创建一个线程池,大小为10
    ExecutorService executorService = Executors.newFixedThreadPool(10);

    private String printThread(String message) {
        return Thread.currentThread().getName() + " " + message;
    }

    /**
     * 小明需要等待厨师炒好菜
     * @throws InterruptedException
     * @throws ExecutionException
     */
    @Test
    public void futureCallBackTest() throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        System.out.println(printThread("小明点餐"));
        Future<String> future = executorService.submit(() -> {
            System.out.println(printThread("厨师开始炒菜"));
            Thread.sleep(2000);
            System.out.println(printThread( "厨师炒好菜"));
            return "饭菜好了";
        });
        //休眠5s,模拟其他业务代码执行【可以执行其他操作,而不受厨师炒菜的影响】
        Thread.sleep(5000);
        String result = future.get();
        executorService.shutdown();
        System.out.println(printThread(result + ",小明开始吃饭"));
        //long end = System.currentTimeMillis();
        //long res = end - start;
        //System.out.println("耗费时间:" + res);
    }
}

③结果和分析

  • 运行结果:
  • 优缺点:
  1. 能获取异步线程执行结果
  2. 无法方便得知任务何时完成
  3. 在主线程获取任务的过程中会导致主线程阻塞
  4. 复杂一点的情况下,比如多个异步任务的场景,一个异步任务依赖上一个异步任务的执行结果,异步任务合并等,Future无法满足需求

java异步执行方法 java 异步执行_java_02

2 ListenableFuture(Guava)

①介绍

  • Google框架中的Guava 提供了 ListenableFuture 类来执行异步操作
  • ListenableFuture对Java原生的Future做了扩展,顾名思义就是使用了监听器模式实现了回调
  • 通过addListener(Runnable listener, Executor executor)方法添加回调任务。
  • 要使用listenableFuture还要结合MoreExecutor线程池,MoreExecutor是对Java原生线程池的封装,比如常用的MoreExecutors.listeningDecorator(threadPool); 修改Java原生线程池的submit方法,封装了future返回listenableFuture。

添加maven依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.2-jre</version>
</dependency>

样例:

java异步执行方法 java 异步执行_reactor_03

②代码

/**
 * Google的guava
 * @throws InterruptedException
 * @throws ExecutionException
 */
@Test
public void listenableFutureTest() throws InterruptedException, ExecutionException {
    System.out.println(printThread("小明点餐"));
    ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
    ListenableFuture<String> listenableFuture = listeningExecutorService.submit(() -> {
        System.out.println(printThread("厨师开始炒菜"));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(printThread( "厨师炒好菜"));
        return "饭菜好了";
    });

    Futures.addCallback(listenableFuture, new FutureCallback<String>() {
        @Override
        public void onSuccess(@Nullable String s) {
            System.out.println(printThread(s + ",小明开始吃饭"));
        }

        @Override
        public void onFailure(Throwable throwable) {
            System.out.println(printThread( throwable.getMessage()));
        }
    }, executorService);

    System.out.println(printThread( "小明开始玩游戏"));
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(printThread("小明结束玩游戏"));


    listenableFuture.get();
    listeningExecutorService.shutdown();
    executorService.shutdown();
}

③结果和分析

  • 运行结果

这里除main线程外,重新创建了两个线程,没有阻塞等待

  • 优缺点:
  • 充分利用了时间片,只要任务结束就马上响应
  • 但是可能会导致Callback Hell(回调地狱)

    例如,我们可以通过回调实现如下逻辑:

    但是该方法会导致代码难以理解同时不易于维护,由于代码过多,此处以图展现

java异步执行方法 java 异步执行_响应式_04

3 CompleteableFuture

①介绍

  • Java8的新类,借鉴了Google Guava的ListenableFuture
  • CompleteableFuture可以用声明式语义来创建多种异步任务:
  1. 创建一个异步任务
  2. 创建一个异步任务,并且该任务必须在一个前驱异步任务之后执行,其以前驱任务的输出作为自身的输入
  3. 创建一个异步任务,且该任务必须在若干个前驱异步任务中的(任意或全部)完成之后执行,其以全部(或任一)前驱任务的输出作为自身的输入

样例:

java异步执行方法 java 异步执行_流式_05

②代码

@Test
    public void completeableFutureTest()  {
        System.out.println(printThread("小明点餐"));

        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(printThread("厨师开始做菜"));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(printThread("厨师菜做好了"));
            return "菜已装盘";
        });

        CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(() -> {
            System.out.println(printThread( "小明开始玩游戏"));
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(printThread("小明结束玩游戏"));
        });

        CompletableFuture<Void> completableFuture2 = completableFuture
                .thenAcceptBoth(completableFuture1,(a, b) -> System.out.println(printThread( a + ", 小明开始吃饭,并点了饮料")))
                .thenApplyAsync((b) -> {
                    System.out.println(printThread("服务员拿饮料"));
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return "饮料好了";
                },executorService)
                .thenAcceptAsync((s) -> System.out.println(printThread(s + ",小明开始喝饮料")));

        completableFuture2.join();
    }

③结果和分析

  • 运行结果
  • 优缺点:

可以看到我们可以让通过监听的方式让线程实现某种业务顺序

4 Reactor

①介绍

  • Reactor 框架是 Pivotal 公司( Spring 家族公司)开发的,实现了 Reactive Programming 思想,符合 Reactive Streams 规范的一项技术;
  • Reactor 是一个高度可伸缩、内存利用率高、灵活性强的响应式编程框架,有助于提高应用程序的性能和稳定性。
  • Reactor 是基于流式编程的一种响应式编程框架。它实现了响应式流 API,可以处理并发数据的异步流,并支持通过多种操作符进行流式转换和操作。与传统的命令式编程范式不同,响应式编程可以更容易地实现异步、响应式、非阻塞和可响应式等特性,并且在处理IO密集型应用程序时具有明显的优势。
  • 主要接口:
  1. Publisher
  2. Subscriber
  3. Subcription
    其中,Subcriber 中便包含了非常重要的 onNext、onError、onCompleted 这三个方法。

    导入maven依赖:
<!--reactor-->
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
    <version>3.2.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <version>3.2.3.RELEASE</version>
    <scope>test</scope>
</dependency>

②代码

public class TestReactor {

    public static void main(String[] args) throws InterruptedException {
        Flux.just(1, 2, 3, 4, 5)
                .subscribeOn(Schedulers.parallel())
                .subscribe(new CoreSubscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        System.out.println(printThread("onSubscribe, " + s.getClass().toString()));
                        s.request(5);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(printThread("next: " + integer));
                    }

                    @Override
                    public void onError(Throwable t) {

                    }

                    @Override
                    public void onComplete() {
                        System.out.println(printThread("complete"));
                    }
                });
        Thread.sleep(1000);
    }

    private static String printThread(String note) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("hh:mm:ss");
        long time = System.currentTimeMillis();
        Date date = new Date(time);
        return Thread.currentThread().getName() + " " + simpleDateFormat.format(date) + " " + time + " " + note;
    }
}

③结果和分析

  • 运行结果:
  • 分析

可以看到输出结果是按照一定顺序的

  • 我们可以根据流式编程来处理很多复杂的业务逻辑

5 Java回调方式

①介绍

回调函数机制:回调函数是一种面向事件或消息的程序设计方法,它通过将函数对象作为参数传递给其他函数或对象来实现异步操作。在 Java 中,可以使用接口或匿名类来实现回调函数机制。

②代码

public class AsyncTask {
  
  // 定义回调接口
  public interface Callback {
    void onCompleted(String result);
  }
  
  // 模拟一个耗时操作
  public void doTask(final Callback callback) {
    new Thread(new Runnable() {
      @Override
      public void run() {
        // 在新的线程中执行异步任务,模拟一个耗时操作,即等待 3 秒
        try {
          Thread.sleep(3000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        // 返回一个结果给回调函数
        callback.onCompleted("AsyncTask completed!");
      }
    }).start();
  }
  
  // 测试代码
  public static void main(String[] args) {
    // 创建一个 AsyncTask 实例
    AsyncTask asyncTask = new AsyncTask();
    
    // 调用 doTask 方法并传入回调函数
    asyncTask.doTask(new Callback() {
      @Override
      public void onCompleted(String result) {
        // 在回调函数中打印异步任务的结果
        System.out.println(result);
      }
    });
    
    // 再做一些其他操作
    // ...
  } 
}

③结果和分析

  • AsyncTask 类中的 doTask 方法模拟了一个耗时操作,并通过回调函数的方式实现了异步。在主程序中,我们创建一个 AsyncTask 实例并调用它的 doTask 方法来执行异步任务,同时传入一个回调函数,在回调函数中打印异步任务的结果。在执行异步任务的同时,我们还可以继续进行其他操作。这样可以避免阻塞主线程,提高程序的并发性能。
  • 总之,回调函数是 Java 中一种实现异步操作的简单、方便的方式,可以帮助我们在异步执行的任务完成后及时处理结果,提高程序的响应性和并发性能。

6 Java实现异步方式

  1. 回调函数机制:回调函数是一种面向事件或消息的程序设计方法,它通过将函数对象作为参数传递给其他函数或对象来实现异步操作。在 Java 中,可以使用接口或匿名类来实现回调函数机制。
  2. Future 和 CompletableFuture:Future 是 Java 中的一种异步编程模型,它允许调用者异步地等待另一个线程的执行结果。CompletableFuture 是 Java 8 引入的一种强化版 Future,可以更方便地实现异步事件的组合、异常处理等操作。
  3. 线程池和多线程:在 Java 中,可以使用线程池和多线程来异步执行任务。通过将任务提交给线程池,可以让执行过程异步地执行,并且可以利用多个线程来处理任务,提高程序的并发性能。
  4. RxJava:RxJava 是一个基于 ReactiveX 编程模型的响应式编程库,在处理异步事件流时具有很高的效率和灵活性。RxJava 提供了多种操作符用于处理数据流,包括过滤、转换、组合等。