You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Webflux共享资源并发访问:ReentrantLock使用合理性及优化方案

问题

我正在实现一个调用外部API的服务,包含两种请求类型:client.authorize()用于获取有效期1小时的新令牌,client.performRequest(..)需要使用前者获取的授权令牌从API拉取数据。Client类基于Spring Reactive的WebClient实现。请问用ReentrantLock同步检查并刷新过期令牌的代码块是否可行?会不会破坏响应式模型?响应式编程里有没有更便捷的共享资源访问方式?

@Service
@RequiredArgsConstructor
public class Service {

    private final Client client;

    @Setter(AccessLevel.NONE)
    private Lock authLock = new ReentrantLock();
    @Setter(AccessLevel.NONE)
    private String authToken;
    @Setter(AccessLevel.NONE)
    private LocalDateTime tokenExpirationDatetime;

    public Mono<Response> performRequest(Request request) {
        return preAuthorize()
                .flatMap(token -> client.performRequest(request, token));
    }

    private Mono<String> preAuthorize() {
        authLock.lock();
        try {
            if (existsActualToken()) {
                return Mono.just(authToken);
            }
            return refreshToken();
        } finally {
            authLock.unlock();
        }
    }

    private boolean existsActualToken() {
        return authToken != null
                && tokenExpirationDatetime.isAfter(LocalDateTime.now());
    }

    private Mono<String> refreshToken() {
        return client.authorize()
                .doOnNext(response -> {
                    this.authToken = response.token();
                    this.tokenExpirationDatetime = response.expirationDatetime();
                })
                .map(Response::token);
    }
}
回答

1. ReentrantLock是否可行?

可行,但存在严重缺陷:

  • ReentrantLock是阻塞式锁,高并发场景下会导致大量线程阻塞等待,违背响应式非阻塞的核心原则,甚至可能引发线程池耗尽问题。
  • 当前代码逻辑有漏洞:refreshToken()返回的是异步Mono,但锁在return refreshToken()后就会释放,其他线程可能在令牌还未刷新完成时进入,重复触发授权请求,导致多次调用client.authorize()。

2. 是否破坏响应式模型?

是的,完全破坏。响应式编程的核心是异步非阻塞、事件驱动,而ReentrantLock会强制线程阻塞,打破Reactor的线程调度策略(比如弹性线程池的非阻塞设计)。混合阻塞与非阻塞代码会让服务失去响应式架构的优势,无法高效处理高并发请求,也无法利用背压机制保护系统稳定性。

3. 响应式编程中更合适的共享资源访问方式

推荐使用Reactor原生的Mono.cache()结合Mono.defer()实现令牌的自动刷新与共享,完全遵循非阻塞原则:

优化方案一:带过期预判的缓存实现

@Service
@RequiredArgsConstructor
public class Service {

    private final Client client;
    private volatile Mono<String> cachedTokenMono;
    private String authToken;
    private LocalDateTime tokenExpirationDatetime;

    public Mono<Response> performRequest(Request request) {
        return getValidToken()
                .flatMap(token -> client.performRequest(request, token));
    }

    private Mono<String> getValidToken() {
        if (cachedTokenMono == null || isTokenExpired()) {
            // 用defer包装,确保每次刷新都是新的授权请求
            cachedTokenMono = Mono.defer(client::authorize)
                    .doOnNext(response -> {
                        this.authToken = response.token();
                        this.tokenExpirationDatetime = response.expirationDatetime();
                    })
                    .map(Response::token)
                    // 提前30秒刷新令牌,避免令牌刚好过期时请求失败
                    .cache(_ -> Duration.between(LocalDateTime.now(), tokenExpirationDatetime.minusSeconds(30)));
        }
        return cachedTokenMono;
    }

    private boolean isTokenExpired() {
        return tokenExpirationDatetime == null || tokenExpirationDatetime.isBefore(LocalDateTime.now());
    }
}

优化方案二:简洁的自动刷新缓存

@Service
@RequiredArgsConstructor
public class Service {

    private final Client client;
    private Mono<String> tokenMono;

    public Mono<Response> performRequest(Request request) {
        return getToken()
                .flatMap(token -> client.performRequest(request, token));
    }

    private Mono<String> getToken() {
        if (tokenMono == null) {
            tokenMono = client.authorize()
                    .map(AuthResponse::token)
                    // 缓存55分钟(比令牌有效期少5分钟),自动触发刷新
                    .cache(Duration.ofMinutes(55))
                    // 授权失败时重置缓存,重新发起请求
                    .onErrorResume(e -> {
                        tokenMono = null;
                        return getToken();
                    });
        }
        return tokenMono;
    }
}

方案优势

  • 完全非阻塞:所有操作都在响应式流中处理,无线程阻塞
  • 自动去重:高并发下只会触发一次授权请求,避免重复调用外部API
  • 自动刷新:利用cache()的超时机制,在令牌即将过期时自动刷新
  • 符合响应式模型:遵循Reactor异步非阻塞原则,充分发挥Spring Reactive的性能优势

内容的提问来源于stack exchange,提问作者Andrei Yusupau

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 03:22:50