Java并发18-StampLock


Java并发18-StampedLock

StampedLock模式

读多写少场景下,StampedLock的性能比ReadWriteLock性能更好。


```StampedLock```支持写锁,悲观读锁和乐观读。

`StampedLock` 的写锁和悲观读锁加锁成功后,会返回一个 `stamp`;解锁的时候,需要传入这个 `stamp`。

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

// 获取/释放写锁示意代码
long stamp = sl.writeLock();
try {
  //省略业务相关代码
} finally {
  sl.unlockWrite(stamp);
}

StampedLock性能好的原因是因为它支持乐观读,乐观读即无锁。

官方示例展示

class Point {
  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);
  }
}

使用注意事项

StampedLock的功能仅仅是ReadWriteLock的子集,StampedLock不支持重入。

StampedLock的悲观读锁、写锁都不支持条件变量。

使用StampedLock一定不要调用中断操作,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁 writeLockInterruptibly()

final StampedLock lock = new StampedLock();
Thread T1 = new Thread(()->{
  // 获取写锁
  lock.writeLock();
  // 永远阻塞在此处,不释放写锁
  LockSupport.park();
});
T1.start();
// 保证T1获取写锁
Thread.sleep(100);
Thread T2 = new Thread(()->
  //阻塞在悲观读锁
  lock.readLock()
);
T2.start();
// 保证T2阻塞在读锁
Thread.sleep(100);
//中断线程T2
//会导致线程T2所在CPU飙升
//内部实现里while循环里面对中断的处理有点问题
T2.interrupt();
T2.join();

使用模板

StampedLock读模板:

final StampedLock sl = new StampedLock();

// 乐观读
long stamp = sl.tryOptimisticRead();
// 读入方法局部变量
......
// 校验stamp
if (!sl.validate(stamp)){
  // 升级为悲观读锁
  stamp = sl.readLock();
  try {
    // 读入方法局部变量
    .....
  } finally {
    //释放悲观读锁
    sl.unlockRead(stamp);
  }
}
//使用方法局部变量执行业务操作
......

StampedLock写模板:

long stamp = sl.writeLock();
try {
  // 写共享变量
  ......
} finally {
  sl.unlockWrite(stamp);
}

思考

找BUG

StampedLock支持锁的降级(tryConvertToReadLock())和升级(tryConvertToWriteLock()

private double x, y;
final StampedLock sl = new StampedLock();
// 存在问题的方法
void moveIfAtOrigin(double newX, double newY){
 long stamp = sl.readLock();
 try {
  while(x == 0.0 && y == 0.0){
    //tryConvertToWriteLock(stamp)这个方法内部会释放悲观读锁stamp  
    long ws = sl.tryConvertToWriteLock(stamp);
    if (ws != 0L) {
      x = newX;
      y = newY;
      break;
    } else {
      sl.unlockRead(stamp);
      stamp = sl.writeLock();
    }
  }
 } finally {
  sl.unlock(stamp);
}

在锁升级成功的时候,最后没有释放最新的写锁,可以在if块的break上加个stamp=ws进行释放。

别人的总结

StampedLock读模板,先通过乐观读或者悲观读锁获取变量,然后利用这些变量处理业务逻辑,会不会存在线程安全的情况? 比如,读出来的变量没问题,但是进行业务逻辑处理的时候,这时读出的变量有可能发生变化了(比如被写锁改写了)?所以当使用乐观读锁时,是不是等业务都处理完了(比如先利用变量把距离计算完),再判断变量是否被改写,如果没改写,直接return;如果已经改写,则使用悲观读锁做同样的事情。不过如果业务比较耗时,可能持有悲观锁的时间会比较长?

——Presley

两种场景,如果处理业务需要保持互斥,那么就用互斥锁,如果不需要保持互斥才可以用读写锁。

一般来讲缓存是不需要保持互斥性的,能接受瞬间的不一致。


文章作者: Wendell
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Wendell !
评论
  目录