Java异步调用接口实现方式

发布时间:2023年12月20日

背景

Java多线程技术面试问得很多很多,而且在做脚本优化、接口异步调用、许多大型框架中使用非常广泛,提升性能巨大。不过实际工作中的业务开发,更多是为了保证业务的安全可靠和节省性能开销,都没怎么使用到。最近做了一个异步调用接口的需求,好久没用过了,一时还有点想不起来,还得看看java api文档,记录一下方便以后查看。

前置知识

异步的原理

Java 异步的基本原理是利用多线程和回调函数。在异步操作中,主线程不必等待异步任务完成,而是可以继续执行其他任务。当异步任务完成时,通过回调函数或者其他机制通知主线程。

  • 启动异步任务: 使用线程、CompletableFuture 或者其他异步编程工具启动异步任务,使其在新线程中执行。

  • 主线程不阻塞: 主线程可以继续执行其他任务,而不必等待异步任务完成。

  • 异步任务完成时通知主线程: 当异步任务完成时,通过回调函数、CompletableFuture 的 thenApply、thenAccept 等方法,或者其他方式通知主线程,以执行相应的逻辑。

处理异步任务的结果: 主线程在接收到异步任务完成的通知后,可以处理异步任务的结果。

异步的方式

  1. 多线程
    在 Java 中,多线程是实现异步操作的基础。通过创建线程,可以在应用程序中并发执行多个任务,实现异步操作的效果。Java 提供了 Thread 类和 Runnable 接口,使得创建和管理线程变得相对容易。
// 示例:创建并启动一个线程
Thread myThread = new Thread(() -> {
    // 异步任务逻辑
    System.out.println("异步任务在新线程中执行");
});

myThread.start(); // 启动线程
  1. Future 和 CompletableFuture
    Future 接口是 Java 中处理异步操作的一种方式,用于表示一个异步计算的结果。但它的使用相对受限,因为它只提供了对异步操作结果的简单访问。

// 示例:使用 Future
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> futureResult = executorService.submit(() -> {
    // 异步任务逻辑
    return "异步任务的结果";
});

// 阻塞等待异步操作完成
String result = futureResult.get();
System.out.println("异步任务的结果:" + result);
CompletableFutureFuture 的一个扩展,提供更丰富的异步编程功能。它支持回调、组合多个异步操作等特性。

// 示例:使用 CompletableFuture
CompletableFuture<String> futureResult = CompletableFuture.supplyAsync(() -> {
    // 异步任务逻辑
    return "异步任务的结果";
});

// 注册回调函数
futureResult.thenAccept(result -> System.out.println("异步任务的结果:" + result));
  1. 回调函数
    回调函数是异步编程中常见的一种机制。通过回调函数,你可以指定在异步操作完成时执行的逻辑。在 CompletableFuture 中,thenApply、thenAccept、thenRun 等方法就是用于注册回调函数的。
CompletableFuture<String> futureResult = CompletableFuture.supplyAsync(() -> {
    // 异步任务逻辑
    return "异步任务的结果";
});

// 注册回调函数
futureResult.thenAccept(result -> System.out.println("异步任务的结果:" + result));

实战操作

需求:需要分别调用A接口和B接口,两个返回结果之间没有关联。

方式一:多线程

public class AsyncExampleThread {
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            String resultA = requestInterfaceA();
            System.out.println("接口A的返回结果:" + resultA);
        });

        Thread threadB = new Thread(() -> {
            String resultB = requestInterfaceB();
            System.out.println("接口B的返回结果:" + resultB);
        });

        // 启动线程
        threadA.start();
        threadB.start();

        // 主线程等待子线程执行完成
        try {
            threadA.join();
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static String requestInterfaceA() {
        // 模拟接口A的请求
        return "Response from Interface A";
    }

    private static String requestInterfaceB() {
        // 模拟接口B的请求
        return "Response from Interface B";
    }
}


除了上述的方式,你也可以在主线程中调用A,在子线程中调用B,这样也无伤大雅,性能开销会更小一点,节省了一个线程。或者你可以考虑用线程池来做

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AsyncExampleThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        executorService.submit(() -> {
            String resultA = requestInterfaceA();
            System.out.println("接口A的返回结果:" + resultA);
        });

        executorService.submit(() -> {
            String resultB = requestInterfaceB();
            System.out.println("接口B的返回结果:" + resultB);
        });

        // 关闭线程池
        executorService.shutdown();
    }

    private static String requestInterfaceA() {
        // 模拟接口A的请求
        return "Response from Interface A";
    }

    private static String requestInterfaceB() {
        // 模拟接口B的请求
        return "Response from Interface B";
    }
}

方式二:回调函数

CompletableFuture接口可能有些同学不太熟悉,举例一下它有哪些方法:

  • thenApply: 对前一阶段的结果应用函数。可以用于转换异步操作的结果。
CompletableFuture<Integer> result = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<String> transformed = result.thenApply(value -> "Transformed result: " + value);
  • thenAccept: 接受前一阶段的结果,并在异步操作完成时执行给定的动作,但不返回新的结果。
CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> "Hello");
result.thenAccept(value -> System.out.println("Received result: " + value));
  • thenCombine: 结合两个独立的 CompletableFuture 的结果,然后执行相应的操作。
CompletableFuture<Integer> resultA = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<Integer> resultB = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> combined = resultA.thenCombine(resultB, (a, b) -> a + b);
  • exceptionally: 处理异常情况,返回一个默认值或执行其他操作。

CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException("Error!");
}).exceptionally(ex -> "Handled Exception: " + ex.getMessage());
  • handle: 处理正常结果和异常情况,类似于 exceptionally,但可以处理正常结果。
CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> handledResult = result.handle((res, ex) -> {
    if (ex != null) {
        return "Handled Exception: " + ex.getMessage();
    } else {
        return "Result: " + res;
    }
});
  • thenCompose: 使用前一个 CompletableFuture 的结果作为输入来构建新的 CompletableFuture。用于处理异步操作的串行执行。

CompletableFuture<Integer> result = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<Integer> composed = result.thenCompose(value -> CompletableFuture.supplyAsync(() -> value * 2));
  • allOf 和 anyOf: 用于等待多个 CompletableFuture 完成的方法。

CompletableFuture<String> resultA = CompletableFuture.supplyAsync(() -> "A");
CompletableFuture<String> resultB = CompletableFuture.supplyAsync(() -> "B");

// allOf 等待所有 CompletableFuture 完成
CompletableFuture<Void> allOfResult = CompletableFuture.allOf(resultA, resultB);

// anyOf 等待任一 CompletableFuture 完成
CompletableFuture<Object> anyOfResult = CompletableFuture.anyOf(resultA, resultB);
  • runAfterBoth: 当两个 CompletableFuture 都完成后,运行一个 Runnable。

CompletableFuture<String> resultA = CompletableFuture.supplyAsync(() -> "A");
CompletableFuture<String> resultB = CompletableFuture.supplyAsync(() -> "B");

CompletableFuture<Void> runAfterBothResult = resultA.runAfterBoth(resultB, () ->
    System.out.println("Both CompletableFuture completed")
);
  • runAfterEither: 当任一 CompletableFuture 完成后,运行一个 Runnable。

CompletableFuture<String> resultA = CompletableFuture.supplyAsync(() -> "A");
CompletableFuture<String> resultB = CompletableFuture.supplyAsync(() -> "B");

CompletableFuture<Void> runAfterEitherResult = resultA.runAfterEither(resultB, () ->
    System.out.println("Either CompletableFuture completed")
);
  • thenAcceptBoth: 当两个 CompletableFuture 都完成时,执行一个 BiConsumer 操作。

CompletableFuture<String> resultA = CompletableFuture.supplyAsync(() -> "A");
CompletableFuture<String> resultB = CompletableFuture.supplyAsync(() -> "B");

CompletableFuture<Void> thenAcceptBothResult = resultA.thenAcceptBoth(resultB, (a, b) ->
    System.out.println("Result from A: " + a + ", Result from B: " + b)
);

代码示范一:组合两个接口返回的数据

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class AsyncExample {

    public static void main(String[] args) {
        // 异步请求接口A
        CompletableFuture<String> resultA = CompletableFuture.supplyAsync(() -> requestInterfaceA());

        // 异步请求接口B
        CompletableFuture<String> resultB = CompletableFuture.supplyAsync(() -> requestInterfaceB());

        // 使用thenAcceptBoth方法组合两个异步结果,处理它们的返回值
        resultA.thenAcceptBoth(resultB, (responseA, responseB) -> {
            System.out.println("接口A的返回结果:" + responseA);
            System.out.println("接口B的返回结果:" + responseB);
            // 在这里可以执行进一步的处理逻辑
        });

        // 主线程等待异步操作完成
        try {
            CompletableFuture.allOf(resultA, resultB).get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    private static String requestInterfaceA() {
        // 模拟接口A的请求
        // 返回接口A的结果
        return "Response from Interface A";
    }

    private static String requestInterfaceB() {
        // 模拟接口B的请求
        // 返回接口B的结果
        return "Response from Interface B";
    }
}

代码示范二:分别处理返回结果

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class AsyncExample {

    public static void main(String[] args) {
        // 异步请求接口A
        CompletableFuture<String> resultA = CompletableFuture.supplyAsync(() -> requestInterfaceA());

        // 异步请求接口B
        CompletableFuture<String> resultB = CompletableFuture.supplyAsync(() -> requestInterfaceB());

        // 使用thenAccept方法在两个异步任务都完成时打印结果
        resultA.thenAccept(responseA -> {
            System.out.println("接口A的返回结果:" + responseA);
        });

        resultB.thenAccept(responseB -> {
            System.out.println("接口B的返回结果:" + responseB);
        });

        // 主线程等待异步操作完成
        try {
            CompletableFuture.allOf(resultA, resultB).get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    private static String requestInterfaceA() {
        // 模拟接口A的请求
        // 返回接口A的结果
        return "Response from Interface A";
    }

    private static String requestInterfaceB() {
        // 模拟接口B的请求
        // 返回接口B的结果
        return "Response from Interface B";
    }
}

方式三:ExecutorService 和 Future

这个有点整合方式一和方式二的味道,适用于对于需要更精细控制的项目,例如需要定制线程池的大小或其他行为。

import java.util.concurrent.*;

public class AsyncExampleExecutorService {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        Future<String> resultA = executorService.submit(() -> requestInterfaceA());
        Future<String> resultB = executorService.submit(() -> requestInterfaceB());

        try {
            String responseA = resultA.get();
            String responseB = resultB.get();

            System.out.println("接口A的返回结果:" + responseA);
            System.out.println("接口B的返回结果:" + responseB);

            // 在这里可以执行进一步的处理逻辑

        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }

    private static String requestInterfaceA() {
        // 模拟接口A的请求
        return "Response from Interface A";
    }

    private static String requestInterfaceB() {
        // 模拟接口B的请求
        return "Response from Interface B";
    }
}

总结

使用异步技术在大多情况下能够提升性能,而且会很显著,但不是盲目地使用多线程就能提高性能了。

  1. 要合理考虑多个线程之间的协助,避免线程挂起阻塞太久,额外开启线程的性能开销不可忽视。可以在开启子线程的同时,主线程继续执行其它的操作,执行完成之后再判断子线程是否完成。
  2. 如果想使用多线程提高系统的tps,一定要分析瓶颈所在之处。比如文中举的案例,如果瓶颈在接口响应的时间上,把调用A接口和调用B接口异步处理,并不能提升整体的性能,只能改进整体的响应时间。
文章来源:https://blog.csdn.net/interestANd/article/details/135068520
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。