MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java Spring Boot的异步编程实践

2021-07-201.3k 阅读

异步编程在 Java Spring Boot 中的重要性

在现代的软件开发中,尤其是在构建高性能、可伸缩的应用程序时,异步编程扮演着至关重要的角色。传统的同步编程模型,在处理 I/O 操作(如数据库查询、网络调用等)时,线程会被阻塞,直到操作完成。这意味着在等待的过程中,线程无法执行其他任务,从而降低了系统的整体效率。

Java Spring Boot 作为一个流行的微服务框架,提供了强大的异步编程支持,允许开发者充分利用多核处理器的优势,提高应用程序的响应性和吞吐量。通过异步编程,我们可以将耗时的操作放到后台线程中执行,使得主线程能够继续处理其他请求,从而提升用户体验。

异步方法的基本定义与配置

在 Spring Boot 中,定义一个异步方法非常简单。首先,需要在 Spring 配置类或者主应用类上添加 @EnableAsync 注解,以开启异步处理功能。

假设我们有一个简单的 Spring Boot 应用,主应用类如下:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class AsyncAppApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncAppApplication.class, args);
    }
}

然后,定义一个异步方法。比如,我们创建一个 AsyncService 类,其中包含一个异步方法:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {
    @Async
    public void asyncMethod() {
        // 模拟一个耗时操作
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("异步方法执行完毕");
    }
}

在上述代码中,@Async 注解标记了 asyncMethod 方法为异步方法。当调用这个方法时,它会在一个单独的线程中执行,而不会阻塞调用线程。

异步方法的调用与执行流程

接下来,我们看看如何调用这个异步方法。在一个控制器类中,我们可以这样调用:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async")
    public String triggerAsyncMethod() {
        asyncService.asyncMethod();
        return "异步方法已触发";
    }
}

当我们访问 /async 接口时,asyncMethod 会在后台线程中开始执行,而控制器会立即返回 “异步方法已触发” 给客户端。这样,即使 asyncMethod 中的操作耗时较长,也不会影响主线程对其他请求的处理。

异步方法返回值处理

Future 类型返回值

有时候,我们可能需要获取异步方法的执行结果。Spring Boot 支持使用 Future 类型作为异步方法的返回值。修改 AsyncService 类中的异步方法如下:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;

@Service
public class AsyncService {
    @Async
    public Future<String> asyncMethodWithReturn() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new AsyncResult<>("异步方法执行结果");
    }
}

这里,我们返回了一个 Future<String> 类型的值。AsyncResultFuture 的一个实现类,用于封装异步方法的返回结果。

在控制器中调用这个方法并获取结果:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async/result")
    public String getAsyncResult() {
        Future<String> future = asyncService.asyncMethodWithReturn();
        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            return "获取结果失败";
        }
    }
}

在上述代码中,future.get() 方法会阻塞当前线程,直到异步方法执行完毕并返回结果。

CompletableFuture 类型返回值

CompletableFuture 是 Java 8 引入的一个更强大的异步编程工具,它提供了更灵活的异步操作组合和结果处理方式。在 Spring Boot 中,我们也可以使用 CompletableFuture 作为异步方法的返回值。

修改 AsyncService 类:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class AsyncService {
    @Async
    public CompletableFuture<String> asyncMethodWithCompletableFuture() {
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "CompletableFuture 异步方法执行结果";
        });
    }
}

在控制器中调用并处理结果:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async/cfresult")
    public String getCompletableFutureResult() {
        CompletableFuture<String> future = asyncService.asyncMethodWithCompletableFuture();
        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            return "获取结果失败";
        }
    }
}

CompletableFuture 还支持链式调用、异步处理结果等高级功能。例如,我们可以在获取到结果后进行进一步的处理:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;

@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async/cfchain")
    public String chainCompletableFuture() {
        CompletableFuture<String> future = asyncService.asyncMethodWithCompletableFuture();
        return future.thenApply(result -> "处理后的结果: " + result).join();
    }
}

在上述代码中,thenApply 方法会在异步方法执行完毕并获取到结果后,对结果进行处理,并返回一个新的 CompletableFuture

自定义线程池

默认情况下,Spring Boot 使用一个简单的线程池来执行异步方法。然而,在实际应用中,我们可能需要根据业务需求自定义线程池的配置,以优化性能和资源利用。

首先,创建一个线程池配置类:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean(name = "asyncExecutor")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }
}

在上述代码中,我们定义了一个名为 asyncExecutor 的线程池,设置了核心线程数为 5,最大线程数为 10,队列容量为 25,并指定了线程名称前缀。

然后,在异步方法上指定使用这个自定义的线程池:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {
    @Async("asyncExecutor")
    public void asyncMethodWithCustomExecutor() {
        // 模拟一个耗时操作
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("使用自定义线程池的异步方法执行完毕");
    }
}

通过这种方式,我们可以根据具体的业务场景来优化线程池的配置,提高异步任务的执行效率。

异步异常处理

在异步方法执行过程中,可能会出现异常。Spring Boot 提供了方便的机制来处理异步方法中的异常。

首先,创建一个自定义的异步异常类:

public class AsyncException extends RuntimeException {
    public AsyncException(String message) {
        super(message);
    }
}

然后,在异步方法中抛出这个异常:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {
    @Async
    public void asyncMethodWithException() {
        throw new AsyncException("异步方法中抛出的异常");
    }
}

接下来,创建一个全局的异步异常处理器:

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.lang.reflect.Method;
import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }
}

class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
        System.out.println("Exception message - " + throwable.getMessage());
        System.out.println("Method name - " + method.getName());
        for (Object param : obj) {
            System.out.println("Parameter value - " + param);
        }
    }
}

在上述代码中,CustomAsyncExceptionHandler 类实现了 AsyncUncaughtExceptionHandler 接口,用于处理异步方法中抛出的异常。通过这种方式,我们可以统一捕获和处理异步方法中的异常,避免异常丢失导致系统出现难以排查的问题。

异步编程在实际业务场景中的应用

电商系统中的订单处理

在电商系统中,当用户提交订单后,除了保存订单信息到数据库,还可能需要进行库存扣减、发送订单确认邮件、通知物流系统等操作。这些操作可能涉及不同的服务和接口调用,有些操作可能比较耗时。

我们可以将这些耗时操作定义为异步方法。例如,库存扣减和发送邮件操作可以放在异步方法中执行:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private EmailService emailService;

    public void placeOrder(Order order) {
        // 保存订单信息到数据库
        saveOrderToDb(order);
        asyncDeductInventory(order);
        asyncSendOrderConfirmationEmail(order);
    }

    @Async
    private void asyncDeductInventory(Order order) {
        inventoryService.deductInventory(order.getProductId(), order.getQuantity());
    }

    @Async
    private void asyncSendOrderConfirmationEmail(Order order) {
        emailService.sendOrderConfirmationEmail(order.getCustomerEmail(), order.getOrderId());
    }

    private void saveOrderToDb(Order order) {
        // 实际的数据库保存逻辑
    }
}

通过这种方式,用户提交订单后,系统可以快速返回订单提交成功的响应,而后台的库存扣减和邮件发送操作会在异步线程中执行,不会影响用户体验。

日志记录优化

在应用程序中,日志记录是一个常见的操作。但是,传统的同步日志记录方式可能会影响系统的性能,尤其是在高并发场景下。

我们可以将日志记录操作改为异步方式。例如,创建一个异步日志服务:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Service
public class AsyncLogService {
    private static final Logger logger = LoggerFactory.getLogger(AsyncLogService.class);

    @Async
    public void logMessage(String message) {
        logger.info(message);
    }
}

在其他服务中调用这个异步日志方法:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SomeService {
    @Autowired
    private AsyncLogService asyncLogService;

    public void doSomeWork() {
        // 业务逻辑
        asyncLogService.logMessage("执行了一些工作");
    }
}

这样,日志记录操作会在后台线程中执行,不会阻塞业务逻辑的执行,从而提高系统的整体性能。

异步编程与 Spring Boot 其他特性的结合

与 Spring Data 的结合

在使用 Spring Data 进行数据库操作时,异步编程可以显著提升性能。例如,在进行批量数据查询或长时间运行的数据库事务时,将这些操作异步化可以避免阻塞主线程。

假设我们有一个使用 Spring Data JPA 的用户服务:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.Future;

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Async
    public Future<List<User>> findAllUsersAsync() {
        return new AsyncResult<>(userRepository.findAll());
    }
}

interface UserRepository extends JpaRepository<User, Long> {
}

在控制器中调用这个异步方法:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/users/async")
    public String getUsersAsync() {
        Future<List<User>> future = userService.findAllUsersAsync();
        try {
            List<User> users = future.get();
            return "异步获取到 " + users.size() + " 个用户";
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            return "获取用户失败";
        }
    }
}

通过将数据库查询操作异步化,在查询数据时,主线程可以继续处理其他请求,提高了系统的并发处理能力。

与 Spring Cloud 的结合

在微服务架构中,使用 Spring Cloud 构建分布式系统时,异步编程同样非常重要。例如,在服务间的远程调用中,异步化可以避免因等待响应而阻塞线程。

假设我们有两个微服务,一个是订单服务,另一个是库存服务。订单服务在创建订单时需要调用库存服务进行库存检查。

订单服务中的异步调用:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class OrderService {
    @Autowired
    private InventoryClient inventoryClient;

    @Async
    public CompletableFuture<Boolean> checkInventoryAsync(Long productId, int quantity) {
        return CompletableFuture.supplyAsync(() -> inventoryClient.checkInventory(productId, quantity));
    }
}

@FeignClient(name = "inventory-service")
interface InventoryClient {
    boolean checkInventory(Long productId, int quantity);
}

通过这种异步调用方式,订单服务在调用库存服务时不会阻塞,从而提高了整个微服务系统的响应性和吞吐量。

总结与最佳实践

在 Java Spring Boot 中进行异步编程,可以极大地提升应用程序的性能和响应性。通过合理地定义异步方法、处理返回值、配置线程池和处理异常,我们可以构建出高效、稳定的应用程序。

在实际应用中,应根据业务场景和性能需求来选择合适的异步编程方式。例如,对于不需要获取结果的简单异步任务,可以直接使用无返回值的异步方法;对于需要获取结果的任务,可以选择 FutureCompletableFuture 作为返回值类型。

同时,合理配置线程池参数也是非常关键的。过少的线程可能导致任务处理缓慢,而过多的线程则可能消耗过多的系统资源,导致性能下降。因此,需要根据系统的硬件资源和业务负载来进行调优。

此外,对于异步异常的处理,一定要有统一的机制,确保异常能够被及时捕获和处理,避免因异常丢失而导致系统出现不可预测的问题。

通过深入理解和应用 Spring Boot 的异步编程特性,开发者可以构建出更加健壮、高性能的应用程序,满足现代业务的需求。