Java 异步编程本应更简单才对

2023年 12月 12日 104.9k 0

在过去的好多年里,多线程和异步一直作为 Java 技术里的高级部分,在技术序列中,一个语言分为入门部分、进阶部分和高级部分,所以,异步是作为其中的高级技术部分存在的。

关于异步和多线程这部分吧,常常存在于面试题、八股文当中,但是在大多数的项目代码中你根本看不着它。神奇吗,就是这么神奇。

纠其原因可能有两个:

  • 本身大多数项目就很简单,根本就用不着多线程和异步,毕竟平庸属于大多数;
  • Java 中关于多线程和异步的部分确实对于新手不太友好,涉及到的类且多且乱,而且不符合我们正常的思考方式;
  • 我就见过很多同学,多次想入门多线程和异步,但是多次被劝退,或者在大门口反复横跳。

    一旁的 Node.js 、Go 憋了一眼:哼,不就会异步吗,有那么难吗?

    在 Java 中实现异步编程有什么方式呢?

    异步回调函数

    最开始写前端的时候最常用这种回调函数的方法,在 JavaScript 中,函数是一等公民,用法非常灵活。但是在 Java 中,回调方式并不常用。

    在异步调用结束或者发生异常的时候主动的调用回调方法,以此来达到异步通知的目的。首先定义一个回调接口,如下:

    public interface ICallBackService {
    
        /**
         * 回调方法
         * @param args 参数
         */
        void callback(String ...args) throws InterruptedException;
    }

    然后在你的异步方法中加一个回调参数,参数类型就是上面的 ICallBackService接口类型。

    public class Work {
    
        /**
         * 业务逻辑
         * @param callBackService
         */
        public void doWork(ICallBackService callBackService) throws InterruptedException {
            System.out.println("开始回调");
            callBackService.callback("第一个参数","第二个参数");
            System.out.println("回调结束");
        }
    }

    之后在调用端调用doWork方法执行异步调用。

    public static void main(String[] args) throws InterruptedException {
            System.out.println("准备发起异步调用");
            Thread thread = new Thread(() -> {
                Work work = new Work();
                try {
                    work.doWork(new ICallBackService() {
                        @Override
                        public void callback(String... args) throws InterruptedException {
                            Thread.sleep(1000);
                            System.out.printf("正在执行回调动作:%s%n",args==null?"无参数":String.join(",", args));
                        }
                    });
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            thread.start();
    
            System.out.println("继续干其他事儿");
        }

    执行后,打印的结果,主线程该干什么干什么,异步方法执行后,主动调用回调方法。

    准备发起异步调用 继续干其他事儿 异步执行 正在执行回调动作:第一个参数,第二个参数 回调结束

    回调这种机制有个最要命的问题,它会导致代码逻辑的割裂,本来是一个从开始到结束的完整执行过程,但是回调方法脱离了代码主流程,导致我们看代码的时候产生跳跃感。

    CompletableFuture 异步

    自从 Java 8 出现 Future 之后,异步编程就变得简单多了,回调函数完全可以不用了。再遇到需要异步的场景时,可以直接祭出 CompletableFuture,CompletableFuture 除了有最基础的异步调用功能外,还支持异步任务链、组合任务等等。

    异步编程最繁杂的地方就是流程控制,对于 NodeJS 那种天生就是异步的语言来说,有丰富的第三方框架,而对于 Java 来说,到现在都比较少。

    在不借助第三方框架的情况下,CompletableFuture 应该是最优解了。

    下面这段代码展示了异步调用两个任务,然后将两个任务的返回结果合并到一起,用到了 CompletableFuture 的组合任务功能。

    public static void main(String[] args) throws ExecutionException, InterruptedException {
     //异步发起第一个任务
     CompletableFuture firstTask = CompletableFuture.supplyAsync(() -> {
        try {
         Thread.sleep(1000);
        } catch (InterruptedException e) {
         throw new RuntimeException(e);
        }
        return "第一个任务的结果";
       }
     );
     //异步发起第二个任务
     CompletableFuture secondTask = CompletableFuture.supplyAsync(() -> "第二个任务的结果");
    
     // 合并两个任务的结果
     CompletableFuture combineTask = firstTask.thenCombineAsync(secondTask, (firstResult, secondResult) -> {
      return firstResult + "&&" + secondResult;
     });
    
     combineTask.thenAccept((result) -> {
      System.out.println("最终结果:" + result);
     });
     System.out.println("其他任务,该干嘛干嘛");
    
     combineTask.join();
    }
    }

    Reactor 响应式编程

    用过 Spring Boot 的同学一定看到过 webFlux 这个东西,其实它就是 Reactor 中的功能。Reactor 的核心包是 reactor-core ,专为异步编程而生,已经是 Spring Boot 的内置框架了。

    Reactor 是一个完全非阻塞的JVM响应式编程框架。响应式编程是一种涉及数据流和变化传播的异步编程范式。这意味着可以通过编程语言轻松地表示静态(如数组)或动态(如事件发射器)数据流。

    事件发射器可以理解为事件驱动,如果做过 GUI 或客户端开发的肯定对事件驱动非常熟悉,事件驱动其实就是顺着人的思考模式来的,进行什么操作就触发什么事件。

    下面是用 Reactor 实现的一个简单异步任务,其中subscribe 方法可以理解为一个事件订阅器,在里面可以订阅 onNext (也就是正常执行)、onError (发生错误是执行)以及onComplete(执行完成)等事件。每命中一个事件,就可以驱动这个事件做一些事情。

    就是以顺序写代码的方式,实现异步的逻辑。

    public static void main(String[] args) {
     Mono asyncTask = Mono.fromCallable(() -> {
      // 模拟异步操作
      Thread.sleep(1000);
      // 返回结果
      return "任务执行成功";
     });
    
     // 订阅事件
     asyncTask.subscribe(
       result -> {
        // onNext 事件,处理任务成功的情况
        System.out.println("任务成功,结果:" + result);
       },
       error -> {
        // onError 事件,处理任务出错的情况
        System.err.println("任务出错:" + error.getMessage());
       },
       () -> {
        // onComplete 事件,处理任务完成的情况
        System.out.println("任务完成");
       }
     );
    
     // 使用 block 方法等待异步任务完成
     String result = asyncTask.block();
     System.out.println("主线程等待结果:" + result);
    }

    执行以上代码前,需要引入 reactor-core 依赖包。

    
      io.projectreactor
      reactor-core
      3.6.0
    

    与 Reactor 类似的还有 RxJava,在 Android 开发上用的最多。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论