从多线程到分布式

从多线程到分布式(四)——高性能读写锁StampedLock

2023-08-10  本文已影响0人  吟游雪人

乐观锁
如果并发产生冲突的概率很低,就不必使用悲观锁,而是使用乐观锁。所谓“乐观”,就是假定冲突的概率很低,所以它采用的“加锁”方式是,先修改完共享资源,再验证这段时间内有没有发生冲突。如果没有其他线程在修改资源,那么操作完成。如果发现其他线程已经修改了这个资源,就放弃本次操作。
所以,同样是CAS来实现,先获取锁的,就叫悲观自旋锁;先修改资源的,就叫乐观锁。

另类的性能锁StamperdLock
说他另类,是因为
1.StampedLock 的悲观读锁、写锁都不支持条件变量
2.不支持重入
3.StampedLock 一定不要调用中断操作(这个感觉算是bug)
之所以会有这些特性,是因为包括ReentrantReadWriteLock,ReentrantLock和信号量等同步工具,都是基于AQS同步框架实现的。而在StampedLock中摒弃了AQS框架,为StampedLock实现提供了更多的灵活性。
性能为王的情况下,也就无需实现公平性,自然也就是非公平锁。

final StampedLock sl =
  new StampedLock();
  
// 获取 / 释放悲观读锁示意代码
long stamp = sl.readLock();
try {
  // 省略业务相关代码
} finally {
  sl.unlockRead(stamp);
}
// 获取 / 释放写锁示意代码
long stamp = sl.writeLock();
try {
  // 省略业务相关代码
} finally {
  sl.unlockWrite(stamp);
}

参操文档的使用示例

class Point {
  //共享的变量x和y
  private int x, y;
  final StampedLock sl = new StampedLock();

  //读取操作
  int distanceFromOrigin() {
    //乐观读,也就是不加锁
    long stamp = sl.tryOptimisticRead();
    // 读的过程数据可能被修改
    int curX = x, curY = y;
    // 判断执行读操作期间,
    // 是否存在写操作,如果存在,
    // 则 sl.validate 返回 false
    if (!sl.validate(stamp)){
      // 升级为读锁
      stamp = sl.readLock();
      try {
        curX = x;
        curY = y;
      } finally {
        // 释放读锁
        sl.unlockRead(stamp);
      }
    }
    return Math.sqrt(
      curX * curX + curY * curY);
  }
}

validate()方法接受参数是上次锁操作返回的邮戳,如果在调用validate()之前,这个锁没有写锁申请过,那就返回true,这也表示锁保护的共享数据并没有被修改

反之,如果锁在validate()之前有写锁申请成功过,那就表示,之前的数据读取和写操作冲突了,程序需要进行重试,或者升级为悲观锁。

可以看到,StamperdLock使用起来比一般的重入锁麻烦的多,之所以使用乐观锁,主要是因为性能问题,在低竞争的环境下,乐观锁的性能会高很多。

上一篇 下一篇

猜你喜欢

热点阅读