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

基于Mutiny的Token缓存实现线程安全问题咨询

解决响应式Token获取的线程安全问题

问题描述

我是响应式编程新手,正学习基础概念。我想实现如下逻辑:若认证Token仍有效则返回,否则请求新Token。我编写的代码如下:

public class AsyncTokenSupplier implements Supplier<Uni<Token>> {
   private Token cachedToken;

   @Override
   public Uni<Token> get() {
        if (isTokenValid(cachedToken)) {
            return Uni.createFrom().item(cachedToken);
        } else {
            return doGetToken().onItem().invoke(token -> {
                cachedToken = token;
            });
        }
    }

   private Uni<Token> doGetToken() {
      //Api call here
   }

   private boolean isTokenValid(Token token) {
      if (token == null) {
         return false;
      }
      //Expiration check here
   }
}

但该代码存在线程安全问题:当多个线程并行执行get()方法时,可能会同时发起多次新Token请求,希望得到相关指导与建议。

解决方案

1. 原子引用+请求防重复方案

用AtomicReference存储缓存Token和正在进行的请求,通过CAS操作确保同一时间只有一个请求发起Token刷新,其他请求等待同一个异步结果:

import java.util.concurrent.atomic.AtomicReference;
import io.smallrye.mutiny.Uni;

public class AsyncTokenSupplier implements Supplier<Uni<Token>> {
    private final AtomicReference<Token> cachedToken = new AtomicReference<>();
    private final AtomicReference<Uni<Token>> pendingRequest = new AtomicReference<>();

    @Override
    public Uni<Token> get() {
        Token current = cachedToken.get();
        if (isTokenValid(current)) {
            return Uni.createFrom().item(current);
        }

        // 检查是否已有正在进行的请求
        Uni<Token> existingRequest = pendingRequest.get();
        if (existingRequest != null) {
            return existingRequest;
        }

        // 创建新的Token请求
        Uni<Token> newRequest = doGetToken()
                .onItem().invoke(token -> {
                    cachedToken.set(token);
                    pendingRequest.set(null); // 请求完成后清空标记
                })
                .onFailure().invoke(throwable -> {
                    pendingRequest.set(null); // 失败也要清空,避免阻塞后续请求
                });

        // CAS操作确保只有第一个线程能设置成功
        if (pendingRequest.compareAndSet(null, newRequest)) {
            return newRequest;
        } else {
            return pendingRequest.get();
        }
    }

    private Uni<Token> doGetToken() {
        // 替换为实际的认证API调用逻辑
        return Uni.createFrom().item(new Token());
    }

    private boolean isTokenValid(Token token) {
        if (token == null) return false;
        // 替换为实际的过期时间校验逻辑
        return token.getExpiresAt() > System.currentTimeMillis();
    }
}

// 示例Token类
class Token {
    private long expiresAt;

    public long getExpiresAt() {
        return expiresAt;
    }

    public void setExpiresAt(long expiresAt) {
        this.expiresAt = expiresAt;
    }
}

2. 利用Mutiny的Memoize特性(简洁版)

Mutiny内置的memoize操作符可以自动合并重复请求,并根据Token有效期自动刷新,无需手动处理原子引用:

import io.smallrye.mutiny.Uni;

public class AsyncTokenSupplier implements Supplier<Uni<Token>> {
    private Uni<Token> cachedTokenUni;

    @Override
    public Uni<Token> get() {
        if (cachedTokenUni == null || !isCachedTokenValid()) {
            cachedTokenUni = doGetToken()
                    // 缓存直到Token失效,失效后自动重新请求
                    .memoize().until(this::isTokenValid);
        }
        return cachedTokenUni;
    }

    private boolean isCachedTokenValid() {
        try {
            Token token = cachedTokenUni.await().indefinitely();
            return isTokenValid(token);
        } catch (Exception e) {
            return false;
        }
    }

    private Uni<Token> doGetToken() {
        // 替换为实际的认证API调用逻辑
        return Uni.createFrom().item(new Token());
    }

    private boolean isTokenValid(Token token) {
        if (token == null) return false;
        return token.getExpiresAt() > System.currentTimeMillis();
    }
}

核心注意事项

  • 避免竞态:两种方案都确保了同一时间只有一个Token刷新请求发起,防止重复调用认证API。
  • 故障恢复:请求失败后必须清空等待标记或让缓存失效,避免后续请求一直阻塞在失败结果上。
  • 响应式语义:尽量使用Mutiny提供的操作符处理异步逻辑,避免在响应式代码中使用不必要的阻塞操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 21:05:25