使用 StampedLock实现高效读写

1. 它是什么?

StampedLock 是 Java 8 引入的高性能锁,提供了三种锁模式:写锁、悲观读锁和乐观读锁。
与传统的 ReentrantReadWriteLock 相比,StampedLock 更注重性能,特别适合读多写少的场景。

  • 写锁:阻塞所有其他操作(类似独占锁)。
  • 悲观读锁:共享锁,允许多个线程读取,但会阻塞写操作。
  • 乐观读锁:一种非阻塞读操作,允许并发写操作,并在必要时验证数据一致性。

2. 它的使用场景是什么?

  • 读多写少的场景:如缓存、配置数据读取等场景,StampedLock 的乐观读锁可以显著提高性能。
  • 需要快速读锁验证的场景:在乐观读的情况下,可以验证数据的一致性并在必要时降级为悲观读锁。
  • 写锁需要优先处理的场景:避免传统读写锁中写线程因读操作而长期饥饿的问题。

3. 它有哪些 API?

核心 API

方法名称描述
writeLock()获取写锁,返回一个 stamp(锁标识)。
readLock()获取悲观读锁,返回一个 stamp(锁标识)。
tryOptimisticRead()获取乐观读锁,返回一个 stamp,非阻塞,适合快速读取。
unlockWrite(long stamp)释放写锁,需要传入获取锁时返回的 stamp。
unlockRead(long stamp)释放悲观读锁,需要传入获取锁时返回的 stamp。
validate(long stamp)验证乐观读锁期间是否有写操作发生,返回 true 表示数据未被修改。false表示数据已经被修改过

扩展 API

方法名称描述
tryWriteLock()尝试获取写锁,如果未成功立即返回。
tryReadLock()尝试获取读锁,如果未成功立即返回。
tryConvertToWriteLock(long stamp)尝试将当前锁转换为写锁,成功时返回新 stamp,否则返回 0。
tryConvertToReadLock(long stamp)尝试将当前锁转换为读锁,成功时返回新 stamp,否则返回 0。
isWriteLocked()检查当前锁是否有写锁被占用。
isReadLocked()检查当前锁是否有读锁被占用。

4. 它的使用方式

(1)写锁的使用

public void updateValue(double deltaX, double deltaY) {
    long stamp = lock.writeLock();  // 获取写锁
    try {
        x += deltaX;
        y += deltaY;
    } finally {
        lock.unlockWrite(stamp);  // 释放写锁
    }
}

(2)悲观读锁的使用

public double readValue() {
    long stamp = lock.readLock();  // 获取读锁
    try {
        return Math.sqrt(x * x + y * y);
    } finally {
        lock.unlockRead(stamp);  // 释放读锁
    }
}

(3)乐观读锁的使用

public double readValueOptimistically() {
    long stamp = lock.tryOptimisticRead();  // 获取乐观读锁
    double currentX = x, currentY = y;

    if (!lock.validate(stamp)) {  // 验证数据是否一致
        stamp = lock.readLock();  // 如果不一致,降级为悲观读锁
        try {
            currentX = x;
            currentY = y;
        } finally {
            lock.unlockRead(stamp);  // 释放悲观读锁
        }
    }

    return Math.sqrt(currentX * currentX + currentY * currentY);
}

(4)锁升级的使用

public void conditionalUpdate(double deltaX, double deltaY) {
    long stamp = lock.readLock();  // 获取悲观读锁
    try {
        if (x == 0 && y == 0) {  // 检查条件
            stamp = lock.tryConvertToWriteLock(stamp);  // 升级为写锁
            if (stamp == 0L) {  // 如果升级失败
                stamp = lock.writeLock();  // 显式获取写锁
            }
            x += deltaX;
            y += deltaY;
        }
    } finally {
        lock.unlock(stamp);  // 释放锁
    }
}

5. 它有哪些注意事项

(1)StampedLock 不可重入
StampedLock 不支持重入。如果同一线程尝试再次获取锁(无论读锁还是写锁),会导致死锁。

(2)锁释放需要传入正确的 stamp
每次加锁时都会返回一个唯一的 stamp,在释放锁时需要传入对应的 stamp,否则会抛出 IllegalMonitorStateException。

(3)写优先策略
StampedLock 优先满足写锁请求,避免了读写锁可能出现的写线程饥饿问题。

(4)线程安全
StampedLock 是线程安全的,但不支持条件变量(Condition),因此无法直接使用 wait 或 notify。

(5)适用场景
适合 读多写少 的场景。
不适合写频繁的场景,因为写锁的争用会导致性能下降。

总结

StampedLock 是 Java 并发工具库中的一颗“冷门宝石”,它通过乐观读锁提供了高效的非阻塞读机制,同时避免了写线程饥饿的问题。熟悉其 API 和使用场景,能够帮助你在性能敏感的场景中实现更高效的并发控制!

本文由mdnice多平台发布


加瓦点灯
0 声望0 粉丝

北漂后端程序员