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

AtomicReference的compareAndSet与get线程同步问题及解决方案咨询

AtomicReference线程安全问题解答

首先看原代码实现:

public class MyClass {

    private AtomicReference<AccessTokenWithExpirationDate> accessTokenWithExpirationDate = new AtomicReference<>();

    private AccessTokenWithExpirationDate putAccessTokenToCache(AccessTokenDto accessTokenDto) {
        AccessTokenWithExpirationDate currentValue = this.accessTokenWithExpirationDate.get();
        AccessTokenWithExpirationDate accessTokenWithExpirationDate
                = new AccessTokenWithExpirationDate(accessTokenDto);
        
        // if currentValue the same, update.
        // if currentValue not the same - another thread already updated it. continue.
        this.accessTokenWithExpirationDate.compareAndSet(currentValue, accessTokenWithExpirationDate);
        
        return accessTokenWithExpirationDate;
    }

    private Optional<String> getAccessTokenFromCache() {
        Optional<AccessTokenWithExpirationDate> accessTokenWithExpirationDate = Optional.ofNullable(this.accessTokenWithExpirationDate.get());
        if (accessTokenWithExpirationDate.isPresent() && isTokenNotExpired(accessTokenWithExpirationDate.get())) {
            return accessTokenWithExpirationDate
                    .map(AccessTokenWithExpirationDate::getTokenDto)
                    .map(AccessTokenDto::getAccessToken);
        } else {
            return Optional.empty();
        }
    }
}

问题1解答

不会等待,这个行为没有任何保证。

AtomicReference的compareAndSet和get都是原子操作,但两者之间没有同步阻塞关系:

  • compareAndSet仅保证自身的原子性(要么完全更新成功,要么完全失败),不会阻塞其他线程的get操作。
  • 其他线程调用get时,可能拿到旧值,也可能拿到compareAndSet更新后的新值,完全取决于CPU指令的执行顺序。
  • Java内存模型只保证:如果compareAndSet执行成功,后续的get操作一定能看到这个更新结果,但不保证正在进行的get会等待compareAndSet完成。简单说,读写是并发的,没有强制的先后顺序。

问题2解答

单纯靠AtomicReference的原生方法无法实现“所有get()线程等待compareAndSet()完成再取值”的需求——因为AtomicReference基于无锁CAS设计,核心就是允许读写并发,本身不支持读操作阻塞等待写操作完成。

要实现这个需求,必须引入显式同步机制,以下是两种可行方案:

方案1:使用synchronized同步锁

给读写方法加上synchronized修饰,让同一时间只有一个线程能执行读写操作,从而保证读操作必须等待写操作完成后再执行:

public class MyClass {

    private AtomicReference<AccessTokenWithExpirationDate> accessTokenWithExpirationDate = new AtomicReference<>();

    private synchronized AccessTokenWithExpirationDate putAccessTokenToCache(AccessTokenDto accessTokenDto) {
        AccessTokenWithExpirationDate currentValue = this.accessTokenWithExpirationDate.get();
        AccessTokenWithExpirationDate accessTokenWithExpirationDate
                = new AccessTokenWithExpirationDate(accessTokenDto);
        
        this.accessTokenWithExpirationDate.compareAndSet(currentValue, accessTokenWithExpirationDate);
        
        return accessTokenWithExpirationDate;
    }

    private synchronized Optional<String> getAccessTokenFromCache() {
        Optional<AccessTokenWithExpirationDate> accessTokenWithExpirationDate = Optional.ofNullable(this.accessTokenWithExpirationDate.get());
        if (accessTokenWithExpirationDate.isPresent() && isTokenNotExpired(accessTokenWithExpirationDate.get())) {
            return accessTokenWithExpirationDate
                    .map(AccessTokenWithExpirationDate::getTokenDto)
                    .map(AccessTokenDto::getAccessToken);
        } else {
            return Optional.empty();
        }
    }
    
    // 假设已实现的过期判断方法
    private boolean isTokenNotExpired(AccessTokenWithExpirationDate token) {
        // 具体过期逻辑实现
        return true;
    }
}

方案2:使用ReentrantLock显式锁

和synchronized原理类似,但锁的控制更灵活,适合复杂场景:

import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

public class MyClass {

    private AtomicReference<AccessTokenWithExpirationDate> accessTokenWithExpirationDate = new AtomicReference<>();
    private final ReentrantLock lock = new ReentrantLock();

    private AccessTokenWithExpirationDate putAccessTokenToCache(AccessTokenDto accessTokenDto) {
        lock.lock();
        try {
            AccessTokenWithExpirationDate currentValue = this.accessTokenWithExpirationDate.get();
            AccessTokenWithExpirationDate accessTokenWithExpirationDate
                    = new AccessTokenWithExpirationDate(accessTokenDto);
            
            this.accessTokenWithExpirationDate.compareAndSet(currentValue, accessTokenWithExpirationDate);
            
            return accessTokenWithExpirationDate;
        } finally {
            lock.unlock();
        }
    }

    private Optional<String> getAccessTokenFromCache() {
        lock.lock();
        try {
            Optional<AccessTokenWithExpirationDate> accessTokenWithExpirationDate = Optional.ofNullable(this.accessTokenWithExpirationDate.get());
            if (accessTokenWithExpirationDate.isPresent() && isTokenNotExpired(accessTokenWithExpirationDate.get())) {
                return accessTokenWithExpirationDate
                        .map(AccessTokenWithExpirationDate::getTokenDto)
                        .map(AccessTokenDto::getAccessToken);
            } else {
                return Optional.empty();
            }
        } finally {
            lock.unlock();
        }
    }
    
    private boolean isTokenNotExpired(AccessTokenWithExpirationDate token) {
        return true;
    }
}

注意:AtomicReference的getAndUpdate、updateAndGet等封装方法本质还是CAS循环,依然无法阻塞读操作,所以不能满足需求。只有通过读写互斥的同步锁,才能强制读操作等待写操作完成。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 05:45:43