高并发必备(九)!读写锁的进化版?深入理解StampedLock的乐观读与锁升级机制

目录

1. StampedLock 的核心特点

1.1 三种访问模式

1.2 锁升级与降级

2. 核心设计思想

2.1 核心数据结构

2.2 关键方法实现

(1)获取写锁(writeLock)

3. StampedLock 性能优化

3.1 乐观读避免加锁

3.2 自旋优化

4. StampedLock 的局限性

5. 适用场景

6. 总结

7. 示例代码 

结论


本系列教程传送门👉  《Java高并发实战:原理、源码与性能调优》

StampedLock 是 Java 8 引入的一种高性能读写锁,比 ReentrantReadWriteLock 更灵活,支持乐观读、悲观读和写锁三种模式。其底层实现基于 CLH 队列变种 + CAS 操作 + 状态机控制,适用于读多写少的高并发场景。

1. StampedLock 的核心特点

1.1 三种访问模式

StampedLock 提供三种锁模式:

  1. 写锁(Write Lock)

    • 独占锁,类似 ReentrantReadWriteLock 的写锁。

    • 获取写锁后,其他线程无法获取读锁或写锁。

    • 返回一个 long 类型的 Stamp,用于解锁或升级锁。

  2. 悲观读锁(Pessimistic Read Lock)

    • 共享锁,类似 ReentrantReadWriteLock 的读锁。

    • 允许多个线程同时获取读锁,但会阻塞写锁。

    • 返回一个 long 类型的 Stamp,用于解锁。

  3. 乐观读(Optimistic Read)

    • 无锁机制,仅返回一个 Stamp 用于后续验证。

    • 乐观读期间,允许其他线程获取写锁。

    • 读取数据后,需调用 validate(stamp) 检查是否有写操作冲突。

1.2 锁升级与降级

  • 锁升级(读锁 → 写锁):

long stamp = lock.readLock();
try {
    // 尝试升级为写锁
    long writeStamp = lock.tryConvertToWriteLock(stamp);
    if (writeStamp != 0L) {
        stamp = writeStamp; // 升级成功
    } else {
        lock.unlockRead(stamp); // 升级失败,释放读锁并重新获取写锁
        stamp = lock.writeLock();
    }
} finally {
    lock.unlock(stamp);
}

锁降级(写锁 → 读锁): 

long stamp = lock.writeLock();
try {
    // 降级为读锁
    stamp = lock.tryConvertToReadLock(stamp);
} finally {
    lock.unlock(stamp);
}

2. 核心设计思想

StampedLock 的核心优化点:

  • 乐观读(Optimistic Read):不阻塞其他读/写操作,仅通过版本号(Stamp)校验数据是否被修改。

  • 无重入性:不同于 ReentrantReadWriteLock,StampedLock 不支持锁重入,避免死锁风险。

  • 低开销:通过位运算管理锁状态,减少内存占用。

StampedLock 的底层采用 CLH 队列变种(类似 AQS),并结合 自旋优化 提高性能。

2.1 核心数据结构

  • state(long 类型)

    • 低 7 位:读锁计数(最多 126 个读锁)。

    • 第 8 位:写锁标记(WBIT = 0x80)。

    • 其余位:版本号(用于乐观读检查)。

  • 等待队列(CLH 变种)

    • 采用链表结构管理等待线程。

    • 写锁优先,避免读锁饥饿。

2.2 关键方法实现

(1)获取写锁(writeLock)
public long writeLock() {
    long s, next;
    if (((s = state) & ABITS) == 0L) { // 无锁状态
        if (U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) // CAS 设置写锁位
            return next;
    }
    return acquireWrite(false, 0L); // 竞争失败,进入队列等待
}
  • 如果当前无锁(state & ABITS == 0),直接 CAS 设置 WBIT

  • 否则,进入 acquireWrite 自旋或阻塞。

(2)获取读锁(readLock) 

public long readLock() {
    long s, next;
    if ((s = state) & ABITS) < RFULL) { // 读锁未满
        if (U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) // CAS 增加读计数
            return next;
    }
    return acquireRead(false, 0L); // 竞争失败,进入队列等待
}
  • 如果读锁未满(< RFULL),直接 CAS 增加读计数。

  • 否则,进入 acquireRead 自旋或阻塞。

(3)乐观读(tryOptimisticRead) 

public long tryOptimisticRead() {
    long s;
    return ((s = state) & WBIT) == 0L ? (s & SBITS) : 0L;
}
  • 如果没有写锁(WBIT == 0),返回当前版本号(s & SBITS)。

  • 否则返回 0(表示乐观读失败)。

(4)验证乐观读(validate) 

public boolean validate(long stamp) {
    return (stamp & SBITS) == (state & SBITS);
}
  • 检查乐观读期间是否有写操作(版本号是否变化)。

3. StampedLock 性能优化

3.1 乐观读避免加锁

double distanceFromOrigin() {
    long stamp = sl.tryOptimisticRead(); // 乐观读
    double x = x, y = y;
    if (!sl.validate(stamp)) { // 检查是否有写操作
        stamp = sl.readLock(); // 冲突则获取悲观读锁
        try {
            x = x;
            y = y;
        } finally {
            sl.unlockRead(stamp);
        }
    }
    return Math.sqrt(x * x + y * y);
}
  • 乐观读失败时,才降级为悲观读锁。

3.2 自旋优化

  • 在竞争不激烈时,采用 自旋 减少线程切换开销。

  • 如果自旋失败,再进入 CLH 队列 阻塞。

4. StampedLock 的局限性

  1. 不支持重入

    • 同一线程重复获取锁会导致死锁。

    • 必须确保锁的释放。

  2. 没有 Condition 支持

    • 必须结合 LockSupport 或 wait/notify 实现条件等待。

  3. 容易导致饥饿

    • 写锁优先,可能导致读锁长时间等待。

5. 适用场景

✅ 读多写少(如缓存、统计计数)。
✅ 乐观读 可提高无竞争时的性能。
❌ 不适合写多读少ReentrantReadWriteLock 可能更好)。
❌ 不适合需要 Condition 的场景(用 ReentrantLock 替代)。


6. 总结

特性StampedLockReentrantReadWriteLock
锁模式写锁、悲观读、乐观读写锁、读锁
重入性❌ 不支持✅ 支持
Condition❌ 不支持✅ 支持
性能✅ 更高(乐观读优化)⚠️ 一般
适用场景读多写少通用

推荐使用场景

  • 高并发读操作(如缓存)。

  • 乐观读能显著减少锁竞争时。

慎用场景

  • 需要 Condition 或重入锁时。

  • 写操作频繁时。

7. 示例代码 

import java.util.concurrent.locks.StampedLock;

public class StampedLockDemo {
    private final StampedLock sl = new StampedLock();
    private int x, y;

    public void move(int deltaX, int deltaY) {
        long stamp = sl.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
        }
    }

    public double distanceFromOrigin() {
        long stamp = sl.tryOptimisticRead();
        int currentX = x, currentY = y;
        if (!sl.validate(stamp)) {
            stamp = sl.readLock();
            try {
                currentX = x;
                currentY = y;
            } finally {
                sl.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

结论

StampedLock 是一种高性能锁,适用于 读多写少 的场景,乐观读 是其最大优势。但它 不支持 Condition 和重入,使用时需谨慎。

专栏合集在这里:
👉 《Java高并发实战:原理、源码与性能调优》(进行中)
👉 《Java集合框架深度解析:从源码到实战应用》(已完结)

📣 互动邀请
如果本文对您有帮助,请不要吝啬您的支持:

👍 点赞 - 您的点赞是我持续创作的最大动力!
🔔 关注 - 获取更多Java核心技术解析和实战技巧
💬 评论 - 有任何问题或建议欢迎留言讨论

🚀 下期预告
接下来我将继续深入讲解:

         《高并发必备(十)!Condition接口使用技巧与AQS底层原理剖析》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值