文章目录

Java StampedLock的乐观读失败后转换为悲观读锁

发布于 2026-05-17 23:30:38 · 浏览 30 次 · 评论 0 条

Java StampedLock的乐观读失败后转换为悲观读锁

Java 并发包中的 StampedLock 提供了一种乐观读机制,通常比标准的 ReentrantReadWriteLock 更快。它的核心思路是:先试着读数据,如果发现数据正在被修改,再升级为悲观读锁。


核心操作流程

乐观读不会阻塞写线程,这非常适合读多写少且读操作很快的场景。当乐观读校验失败时,必须转换为悲观读锁以保证数据一致性。

以下是实现这一机制的标准步骤:

  1. 创建 StampedLock 实例。
  2. 调用 tryOptimisticRead() 方法获取一个版本戳记(stamp)。
  3. 复制 共享变量到局部方法变量中。注意:不要在校验前直接使用共享变量。
  4. 调用 validate(stamp) 方法检查在读取期间是否有写操作发生。
  5. 判断 返回结果:
    • 如果返回 true,说明没有写操作发生,直接使用局部变量。
    • 如果返回 false,说明数据脏了,执行 升级逻辑。

乐观读失败后的转换步骤

validate 返回 false 时,意味着在你读取数据的过程中,有线程修改了数据。此时必须放弃乐观读,转为悲观读锁。请按以下步骤执行:

  1. 调用 readLock() 方法获取悲观读锁。此操作会阻塞,直到获取到锁。
  2. 重新复制 共享变量到局部变量。因为之前的数据已经过时,必须重新读取。
  3. 调用 unlockRead(stamp) 释放悲观读锁。注意:这里的 stampreadLock() 返回的新戳记,不是乐观读的戳记。
  4. 使用 最新的局部变量进行后续业务逻辑处理。

代码实现示例

以下代码演示了一个典型的“观察者”模式,使用乐观读,并在失败时自动降级。

import java.util.concurrent.locks.StampedLock;

public class Point {
    private double x, y;
    private final StampedLock sl = new StampedLock();

    // 写操作(互斥)
    void move(double deltaX, double deltaY) {
        long stamp = sl.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
        }
    }

    // 读操作(乐观读 -> 悲观读转换)
    double distanceFromOrigin() {
        // 1. 尝试获取乐观读锁
        long stamp = sl.tryOptimisticRead();

        // 2. 读取数据到局部变量
        double currentX = x;
        double currentY = y;

        // 3. 校验 stamp 是否有效
        if (!sl.validate(stamp)) {
            // 4. 校验失败,获取悲观读锁
            stamp = sl.readLock();
            try {
                // 5. 重新读取数据
                currentX = x;
                currentY = y;
            } finally {
                // 6. 释放悲观读锁
                sl.unlockRead(stamp);
            }
        }

        // 7. 计算并返回结果
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

逻辑流程图

下图清晰地描述了从乐观读开始,到校验失败后切换为悲观读锁的完整判断路径。

graph TD Start[开始] --> GetStamp[调用 tryOptimisticRead] GetStamp --> CopyVars[复制变量 x, y] CopyVars --> Check{validate stamp ?} Check -->|true| Calc[计算结果] Check -->|false| GetReadLock[调用 readLock] GetReadLock --> CopyVarsAgain[重新复制变量 x, y] CopyVarsAgain --> Unlock[调用 unlockRead] Unlock --> Calc Calc --> End[结束]

关键注意事项

在编写转换逻辑时,请务必遵守以下规范以避免死锁或数据错误:

  • 区分 Stamp:乐观读的 stamp 和悲观读的 stamp 是不同的变量。释放锁时,必须使用 readLock() 返回的那个 stamp
  • 重读变量:进入 if (!sl.validate(stamp)) 代码块后,切勿使用之前 tryOptimisticRead 期间读取的局部变量。必须重新从主内存中读取共享变量。
  • Finally 释放:悲观读锁的释放必须放在 finally 块中,确保代码异常时锁也能被正确释放。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文