每日学习Java之一万个为什么?

Java 异步编排与同步工具类对比


一、Java 异步编排概述

1. 什么是异步编排?

异步编排(Asynchronous Composition)是指通过组合多个异步任务,实现任务间的依赖关系、并行执行或结果聚合的编程方式。核心目标是提高程序响应速度和系统吞吐量

2. 核心工具:CompletableFuture

  • Java 8 引入,是 Future 接口的增强版本。
  • 支持链式调用、组合操作(如 thenApply, thenAccept, thenCompose)。
  • 提供异常处理、超时控制、回调机制等功能。
  • 适用场景:需要处理多个异步任务的依赖关系、并行执行、结果聚合等。

二、CompletableFuture 的优点

优点说明
非阻塞式调用通过 supplyAsyncrunAsync 提交任务后,主线程无需等待结果即可继续执行其他操作。
任务组合能力支持链式调用(如 thenApply 处理结果)、多任务并行(如 thenCombine 合并结果)、任务依赖(如 thenCompose)。
异常处理机制通过 .exceptionally().handle() 捕获异常并提供兜底逻辑。
回调支持支持 whenCompletehandle 等回调方法,灵活处理任务完成后的逻辑。
资源管理优化可指定自定义线程池,避免阻塞主线程,提升系统并发性能。

三、同步工具类对比

1. CountDownLatch

  • 功能:等待一组线程完成操作后再继续执行。
  • 特点
    • 计数器只减不增,不可重用。
    • 适用于一次性事件(如初始化、任务分发后等待完成)。
  • 示例
    CountDownLatch latch = new CountDownLatch(3);
    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            // 执行任务
            latch.countDown();
        }).start();
    }
    latch.await(); // 主线程等待
    

2. CyclicBarrier

  • 功能:让一组线程互相等待,直到所有线程都到达屏障点后再同时继续执行。
  • 特点
    • 计数器可重置,支持多次使用。
    • 适用于分阶段协作(如多线程计算后聚合结果)。
  • 示例
    CyclicBarrier barrier = new CyclicBarrier(3, () -> {
        // 所有线程到达后执行的回调
    });
    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            // 执行任务
            barrier.await();
        }).start();
    }
    

3. Semaphore

  • 功能:控制同时访问某个资源的线程数量。
  • 特点
    • 通过许可证机制限制资源访问。
    • 适用于资源池管理(如连接池、限流)。
  • 示例
    Semaphore semaphore = new Semaphore(2); // 最多允许2个线程访问
    semaphore.acquire();
    try {
        // 访问资源
    } finally {
        semaphore.release();
    }
    

四、CompletableFuture 与同步工具类的对比

对比维度CompletableFutureCountDownLatchCyclicBarrierSemaphore
用途异步任务组合、依赖处理、结果聚合等待一组线程完成多线程同步屏障控制资源访问数量
是否可重用✅(支持多次使用)❌(一次性的)✅(可重置)✅(许可证可释放)
是否阻塞主线程❌(非阻塞)✅(通过 await() 阻塞)✅(通过 await() 阻塞)❌(通过 acquire() 阻塞)
异常处理✅(支持 .exceptionally()❌(需手动捕获异常)❌(需手动捕获异常)❌(需手动捕获异常)
适用场景高并发异步任务编排、微服务调用聚合等待多线程初始化完成分阶段协作任务资源池、限流、互斥访问

五、典型应用场景

1. CompletableFuture

  • 微服务调用聚合:同时调用多个远程服务,合并结果。
  • 批量数据处理:并行处理多个独立任务,最后汇总结果。
  • 异步任务链式依赖:A 任务完成后触发 B 任务,B 任务完成后触发 C 任务。

2. CountDownLatch

  • 主线程等待所有子线程完成:如主线程需要收集所有子线程的计算结果。
  • 初始化任务:等待多个初始化任务完成后启动主流程。

3. CyclicBarrier

  • 并行计算分阶段:如 MapReduce 中的多阶段计算任务。
  • 多线程协同:所有线程完成当前阶段后,统一进入下一阶段。

4. Semaphore

  • 连接池管理:限制数据库连接数或 HTTP 客户端连接数。
  • 限流:控制单位时间内访问某个资源的线程数量。

六、总结

工具最佳使用场景
CompletableFuture复杂异步任务编排、非阻塞式高并发处理
CountDownLatch一次性等待多线程完成
CyclicBarrier多线程分阶段协作、屏障同步
Semaphore资源访问控制、限流

建议

  • 如果需要处理异步任务的依赖关系或聚合结果,优先选择 CompletableFuture
  • 如果需要简单等待多线程完成,使用 CountDownLatch
  • 如果需要多线程分阶段同步,使用 CyclicBarrier
  • 如果需要控制资源访问数量,使用 Semaphore

CommandLineRunner 接口功能详解


一、核心功能

CommandLineRunner 是 Spring Boot 提供的一个接口,用于在 Spring 应用上下文完全初始化后、应用正式提供服务前 执行特定的代码逻辑。其核心功能包括:

  1. 执行初始化任务
    在应用启动时加载数据、初始化缓存、检查配置等。
  2. 接收命令行参数
    通过 String[] args 直接访问应用启动时传递的命令行参数。
  3. 支持多任务协作
    可以定义多个 CommandLineRunner 实现类,并通过 @Order 注解控制执行顺序。

二、接口定义

@FunctionalInterface
public interface CommandLineRunner {
    /**
     * 在 SpringApplication 启动后回调
     * @param args 来自应用程序的命令行参数
     * @throws Exception 如果发生错误
     */
    void run(String... args) throws Exception;
}

三、使用方法

1. 实现接口

创建一个类并实现 CommandLineRunner 接口,重写 run 方法。

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class MyStartupRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("应用启动完成,开始执行初始化操作...");
        // 业务逻辑(如加载数据、初始化缓存等)
        if (args.length > 0) {
            System.out.println("接收到的命令行参数: " + String.join(", ", args));
        }
    }
}

2. 添加注解

  • 使用 @Component 注解,Spring 会自动扫描并注册该 Bean。
  • 如果需要控制执行顺序,使用 @Order(int value) 注解。
import org.springframework.core.annotation.Order;

@Component
@Order(1) // 执行顺序为1(数字越小优先级越高)
public class FirstRunner implements CommandLineRunner { ... }

@Component
@Order(2)
public class SecondRunner implements CommandLineRunner { ... }

四、典型应用场景

场景说明
数据初始化在应用启动时加载初始数据到数据库或缓存中。例如:插入默认用户、角色或权限数据。
配置检查验证配置文件或环境变量是否正确(如检查数据库连接、外部服务地址)。
缓存预热在应用启动时预先加载热点数据到 Redis 或本地缓存中,避免首次请求性能下降。
后台任务启动启动定时任务、消息监听器或异步任务。
命令行参数处理根据启动参数执行特定逻辑(如 --init 参数触发初始化操作)。

五、示例代码

1. 基础示例

@Component
public class SimpleRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("应用启动后执行初始化逻辑");
        if (args.length > 0 && "init".equals(args[0])) {
            System.out.println("检测到 init 参数,执行初始化操作");
        }
    }
}

2. 带日志和参数解析

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class DataInitializer implements CommandLineRunner {
    private static final Logger logger = LoggerFactory.getLogger(DataInitializer.class);

    @Override
    public void run(String... args) throws Exception {
        logger.info("开始初始化数据...");
        if (args.length > 0 && "--force-init".equals(args[0])) {
            // 执行强制初始化逻辑
            logger.info("执行强制初始化操作");
        }
        logger.info("数据初始化完成");
    }
}

六、注意事项

1. 执行时机

  • CommandLineRunnerrun 方法会在 Spring 上下文初始化完成后 被调用。
  • 此时,所有 Bean 已经完成初始化,可以安全地调用依赖的 Bean。

2. 异常处理

  • 如果 run 方法抛出异常,Spring Boot 会终止应用启动流程(除非异常被捕获)。
  • 建议在方法内部使用 try-catch 捕获异常并记录日志。

3. 命令行参数

  • 参数通过 String[] args 传递,需手动解析(如 --key=value 格式)。
  • 示例:
    启动命令:java -jar app.jar --init --env=prod
    参数解析:
    for (String arg : args) {
        if (arg.startsWith("--env=")) {
            String env = arg.substring(6);
            System.out.println("环境参数: " + env);
        }
    }
    

4. 执行顺序

  • 默认情况下,多个 CommandLineRunner 的执行顺序是 无序的
  • 使用 @Order 注解或实现 Ordered 接口可控制顺序。

5. 关闭方法

  • CommandLineRunner 本身没有关闭逻辑,但可以通过以下方式控制行为:
    • run 方法中添加条件判断(如检查环境参数)。
    • 抛出异常终止应用启动(需谨慎使用)。

七、与 ApplicationRunner 的区别

特性CommandLineRunnerApplicationRunner
参数类型String[] args(原始字符串数组)ApplicationArguments(封装后的参数对象)
适用场景简单参数解析复杂参数处理(支持解析 --key=value 格式)
接口复杂度简单稍复杂

八、最佳实践

  1. 避免长时间阻塞主线程
    如果初始化逻辑耗时较长,建议将任务提交到线程池异步执行。

  2. 依赖检查
    确保 run 方法中调用的 Bean 已正确注入(如数据库连接、外部服务)。

  3. 日志记录
    在关键步骤添加日志,便于调试和监控。

  4. 参数校验
    对命令行参数进行校验,避免因参数错误导致初始化失败。

  5. 幂等性设计
    如果任务可能重复执行(如多次启动应用),确保初始化逻辑具有幂等性。


九、总结

功能说明
核心作用应用启动后执行初始化任务
执行时机Spring 上下文初始化完成后
参数支持直接接收命令行参数
适用场景数据初始化、配置检查、缓存预热等
扩展性支持多个 Runner 并控制执行顺序

通过合理使用 CommandLineRunner,可以显著提升 Spring Boot 应用的灵活性和健壮性。

BitMap

Redis提供了四个常用命令处理二进制数组

  • SETBIT:为数组指定偏移量上的二进制位设置值,偏移量从0开始计数,二进制位的值只能为0/1.(设置下标值)
  • GETBIT:获取某个下标的值
  • BITCOUNT:统计二进制为1的数量
  • BITOP:对多个位数组进行运算

分布式缓存Redis

在这里插入图片描述

分布式解锁问题

在这里插入图片描述

RouYi @InnerAuth

网关清除请求头信息,防止伪造内部交互。

@Aspect
@Component
public class InnerAuthAspect implements Ordered
{
    @Around("@annotation(innerAuth)")
    public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable
    {
        String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE);
        // 内部请求验证
        if (!StringUtils.equals(SecurityConstants.INNER, source))
        {
            throw new InnerAuthException("没有内部访问权限,不允许访问");
        }

        String userid = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID);
        String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME);
        // 用户信息验证
        if (innerAuth.isUser() && (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)))
        {
            throw new InnerAuthException("没有设置用户信息,不允许访问 ");
        }
        return point.proceed();
    }

    /**
     * 确保在权限认证aop执行前执行
     */
    @Override
    public int getOrder()
    {
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }
}

RBAC权限模型

在这里插入图片描述

在这里插入图片描述

RouYi H5端会员登录

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~Yogi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值