跳至主要內容

StampedLock(读写锁)

Yaien Blog原创大约 3 分钟并发线程安全Lock

StampedLock(读写锁)

StampedLock 是 Java 8 引入的一个新的读写锁,其设计目标是为了解决 ReentrantReadWriteLock 的一些性能问题,提供了乐观读锁的机制。

import java.util.concurrent.locks.StampedLock;

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() {
        long stamp = sl.tryOptimisticRead();  // 尝试乐观读
        double currentX = x, currentY = y;
        if (!sl.validate(stamp)) {  // 验证 stamp 是否有效
            stamp = sl.readLock();  // 如果无效,退化为读锁
            try {
                currentX = x;
                currentY = y;
            } finally {
                sl.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

上面是一个StampedLock的简单例子,在这个例子中,Point类使用 StampedLock 来同步对 x 和 y 的访问。move 方法使用写锁,确保对 x
和 y 的修改是原子的。distanceFromOrigin 方法首先尝试使用乐观读锁来计算距离,如果在读取过程中 x 和 y 被修改了,那么它会退化为使用读锁。

特性

三种锁模式

StampedLock提供了三种锁模式:读锁、写锁、乐观读。

写锁和读锁与ReentrantReadWriteLock 的写锁和读锁类似,乐观读是新实现的一种锁。

不支持重入

不支持可重入可能是出于性能考虑。要实现重入功能,锁内部需要保存更多的信息,比如持有锁的线程,以及它获取锁的次数等。这将使得锁的实现更为复杂,也会带来更多的性能开销。

StampedLock 的设计目标是提供一个比 ReentrantReadWriteLock 更高性能的读写锁,因此它选择了放弃重入功能,以换取更高的性能。

不支持锁升级

既不支持从读锁升级到写锁,尝试这样做会导致阻塞。这也是为了避免潜在的死锁问题。

支持锁降级

既可以从写锁降级到读锁,也可以在持有写锁的情况下获取乐观读锁。

乐观读

乐观读(Optimistic Read)是一种非阻塞的读锁,它在获取锁的时候不会阻塞写线程,这个特性使得乐观读在并发读操作远大于写操作的场景中能够提高性能。

public double distanceFromOrigin() { 
    long stamp = sl.tryOptimisticRead();  // 乐观读
    double 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);
}

以上代码是一个乐观读的例子,在这个例子中,首先尝试获取一个乐观读锁,然后读取数据。如果在读取数据的过程中,有其他线程获取到了写锁,我们就重新获取一个读锁,然后再次读取数据。

基本思想

如果一个线程去读取数据,它假设在读数据过程中不会被其他线程进行写操作,因此他并不会去真正的获取一把锁,而是获取一个stamp时间戳,然后直接读取数据。
读取完成后,这个线程会使用 validate()方法检查在它读取数据的过程中,是否有其他线程获取到了写锁。

  • 如果没有,那么它就可以确信读到的数据是有效的。
  • 如果有其他线程获取到了写锁,那么它就需要使用一种回退策略,通常是尝试重新获取一个读锁或写锁。

优缺点

优点:性能高。乐观读是进行了假设,直接获取值后再去判断获取值是否有变更,并不阻塞写线程
缺点:如果数据频繁发生变更,乐观读可能需要多次回退重试才能读取到有效的数据,这可能就会导致实际性能低于普通读锁

使用场景

StampedLock的使用场景通常在需要高并发读写操作的情况下,而且读操作远大于写操作,这时候使用 StampedLock 可以提高性能。

StampedLock特别适合在数据结构中,如哈希映射和并发数组等。

上次编辑于:
贡献者: yanggl