并发编程 StampedLock

本文将介绍并发编程中的 StampedLock,可以视为改进的读写锁。

一、适用场景

读多写少场景,且性能优于 ReadWriteLock。

二、三种模式

StampedLock 提供了三种模式,分别是:

  • 写锁:类似于读写锁的写锁,区别在于加锁时会返回一个标记,解锁时应该传入该标记

    1
    2
    3
    StampedLock stampedLock = new StampedLock();
    long stamp = stampedLock.writeLock();
    stampedLock.unlockWrite(stamp);
  • 悲观读锁:类似于读写锁的写锁,区别在于加锁时会返回一个标记,解锁时应该传入该标记

    1
    2
    3
    StampedLock stampedLock = new StampedLock();
    long stamp = stampedLock.readLock();
    stampedLock.unlockRead(stamp);
  • 乐观读:不加锁的读;StampedLock 性能优于 ReadWriteLock 的关键

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    private int i = 0;

    private StampedLock stampedLock = new StampedLock();

    public int getI() {
    // 乐观读
    long stamp = stampedLock.tryOptimisticRead();
    // 获取资源
    int tempI = i;
    // 判断此期间是否有写锁获取
    if (!stampedLock.validate(stamp)) {
    // 升级为悲观读锁
    stamp = stampedLock.readLock();
    try {
    tempI = i;
    } finally {
    stampedLock.unlockRead(stamp);
    }
    }
    return tempI;
    }
    • 通过 tryOptimisticRead() 表示将要进行乐观读,同时获取当前的标记
    • 访问资源
    • 通过 validate(stamp) 方法判断执行 tryOptimisticRead() 至今是否曾被获取写锁,
      • 如果有,升级为悲观读锁,读取最新数据
      • 如果没有,无需额外操作

其中,

  • 悲观读锁的做法是:每次要读取资源时,首先获取读锁,在此期间禁止写锁的获取
  • 乐观读的做法是:每次要读取资源时,获取一个标记,读取结束后,通过标记判断资源是否在此期间被修改

相比较而言,乐观读具有更好的性能。

三、注意事项

  • StampedLock 并不支持重入

  • StampedLock 的写锁、悲观读锁并不支持条件

    ReadWriteLock 的 readLock()writeLock() 方法均会返回一个 Lock 实例,可以通过该实例获取条件;

    StampedLock 的 readLock()writeLock() 方法会在调用时直接加锁,并且只会返回一个标记,因此无法获取条件

  • 使用 StampedLock 时不要调用中断操作

    如果线程堵塞在 StampedLock 的 readLock()writeLock() 方法上时,调用该线程的 interrupt(),将会导致 CPU 飙升。

    如果需要支持中断操作,可以使用 readLockInterruptibly()writeLockInterruptibly() 方法。

参考

  • Java 并发编程实战