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

关于StampedLock锁请求排队机制及缓存锁实现的技术问询

关于StampedLock的两个问题解答

1. StampedLock是如何对锁请求进行排队的?

StampedLock的排队机制和传统的ReentrantReadWriteLock有很大区别,它没有依赖AQS的标准CLH队列,而是实现了一个自定义的CLH变体队列,专门适配它的三种锁模式(写锁、悲观读锁、乐观读):

  • 写锁请求:当写锁请求到来时,如果当前锁处于无锁状态或只有乐观读操作,写锁会直接获取;如果当前有活跃的悲观读锁或写锁,写锁请求会被加入队列尾部,严格按FIFO顺序等待,只有前面的锁请求全部完成后才能获取锁。
  • 悲观读锁请求:如果当前没有写锁持有,悲观读锁可以直接获取,不需要排队;但如果当前有写锁持有,或者队列头部是写锁请求,悲观读锁会进入队列。不过有个优化:如果队列中已经有悲观读锁请求,后续的悲观读锁可以直接获取,不用排队到尾部,这是为了避免读请求被写请求长时间阻塞,提升读性能。
  • 乐观读:乐观读是无锁操作,完全不需要排队。它只是获取一个当前的版本戳(stamp),之后验证这个戳是否有效(即期间没有写操作发生),所以不涉及任何排队逻辑。

2. 基于Java8的StampedLock缓存锁可靠实现

我完全懂你当初发现ReentrantReadWriteLock不支持读锁升级为写锁时的震惊——这个场景在缓存加载中太常见了:先读缓存,没命中就需要升级写锁去加载数据。而StampedLock刚好能解决这个问题,下面是一个我在项目中用过的、经过验证的Java8兼容实现:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.StampedLock;

public class StampedLockCache<K, V> {
    // 底层缓存容器,这里用HashMap,实际场景可按需替换为ConcurrentHashMap(StampedLock已保证并发安全)
    private final Map<K, V> cache = new HashMap<>();
    private final StampedLock lock = new StampedLock();

    // 核心缓存获取方法:读缓存→未命中则加载并写入
    public V get(K key) {
        // 第一步:尝试乐观读,无锁开销
        long stamp = lock.tryOptimisticRead();
        V value = cache.get(key);
        
        // 验证乐观读的版本戳是否有效——如果期间有写操作,戳会失效
        if (!lock.validate(stamp)) {
            // 乐观读失效,升级为悲观读锁
            stamp = lock.readLock();
            try {
                // 再次读取缓存,避免在获取读锁期间已有其他线程写入数据
                value = cache.get(key);
                if (value == null) {
                    // 尝试将读锁升级为写锁
                    long writeStamp = lock.tryConvertToWriteLock(stamp);
                    if (writeStamp != 0L) {
                        // 升级成功,更新戳为写锁的戳
                        stamp = writeStamp;
                        // 模拟从数据库/远程接口加载数据,替换为实际业务逻辑
                        value = loadDataFromSource(key);
                        cache.put(key, value);
                    } else {
                        // 升级失败(比如此时有其他写锁等待),先释放读锁,再获取写锁
                        lock.unlockRead(stamp);
                        stamp = lock.writeLock();
                        try {
                            // 双重检查,避免释放读锁到获取写锁期间有其他线程写入
                            value = cache.get(key);
                            if (value == null) {
                                value = loadDataFromSource(key);
                                cache.put(key, value);
                            }
                        } finally {
                            lock.unlockWrite(stamp);
                        }
                    }
                }
            } finally {
                // 最后根据当前持有的锁类型释放锁,避免泄漏
                if (lock.isReadLockStamp(stamp)) {
                    lock.unlockRead(stamp);
                }
            }
        }
        return value;
    }

    // 模拟数据源加载方法,实际替换为你的业务逻辑
    private V loadDataFromSource(K key) {
        try {
            // 模拟IO耗时,比如数据库查询、HTTP请求
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("加载数据中断", e);
        }
        return (V) ("cached_data_" + key);
    }

    // 主动更新缓存的方法
    public void put(K key, V value) {
        long stamp = lock.writeLock();
        try {
            cache.put(key, value);
        } finally {
            lock.unlockWrite(stamp);
        }
    }

    // 可选:删除缓存的方法
    public void remove(K key) {
        long stamp = lock.writeLock();
        try {
            cache.remove(key);
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

实现关键点说明:

  • 乐观读优先:先尝试无锁的乐观读,只有当发现数据可能被修改时才升级为悲观锁,最大化读性能。
  • 安全的读升级写:通过tryConvertToWriteLock尝试平滑升级,失败则优雅降级为“释放读锁→获取写锁”,同时加入双重检查避免重复加载数据,完美解决了ReentrantReadWriteLock的升级痛点。
  • Java8完全兼容:所有API都是Java8中StampedLock提供的,没有使用后续版本新增的方法。
  • 锁安全释放:在finally块中根据锁类型释放,避免锁泄漏。

注意事项:

  • StampedLock是不可重入的,同一个线程不能重复获取同一锁,否则会导致死锁,这一点和ReentrantReadWriteLock不同,一定要注意。
  • 乐观读的validate步骤不能省略,否则会读到脏数据。
  • 如果缓存的Value是可变对象,建议返回不可变副本,避免外部修改导致的并发问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:47:56