第二部分:并发工具类18->StampedLock:比读写锁更快的锁

1.StampedLock

读多写少的场景,StampedLock性能比读写锁更好

2.StampedLock 支持的三种锁模式

读写锁,2种模式,读锁,写锁
stampedLock是3种模式,写锁,悲观锁,乐观锁

写锁,悲观锁的语意和ReadWriteLock的写锁,读锁,语意类似,允许多个线程同时获取悲观读锁,但是只允许一个线程获取写锁,写锁和悲观读锁是互斥的
不同之处StampedLock的写锁和悲观读锁加锁成功后都会返回一个stamp,然后解锁的时候需要传入stamp


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

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

性能好的的关键是stampedLock支持乐观读方式,ReadWriteLock支持多个线程同时读
多个线程同时读的时候,所有的写操作会被阻塞;
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);
  }
}

乐观读期间,存在写操作,会把乐观读升级为悲观读锁

3.理解乐观读

数据库的乐观锁场景
version字段,每次更新product_doc表,都将version字段加1,展示的时候,version字段和其他业务字段一起返回给生产订单ui


select id,... ,version
from product_doc
where id=777

用户保存的时候,利用sql 更新订单,假设该生产订单version=9


update product_doc 
set version=version+1,...
where id=777 and version=9

如果执行成功,并返回修改条数为1,那么说明从生产订单ui到执行操作,保存数据期间,没有其他人修改过这条数据,因为如果其他人修改过这条数据,那么版本一定大于9

4.数据库的乐观锁

查询的时候把version字段查出来,更新的时候利用version字段做验证。
这个version字段类似于stampedLock中的stamp

5.stampedLock使用注意事项

stampedLock没有添加Reentrant前缀,不可重入的锁
stampedLock的悲观读锁,写锁不支持条件变量
坑:如果线程阻塞在stampedLock的readLock或者writeLock时,调用interrupt方法时,会导致cpu飙升.使用stampedLock的时候一定不要使用interrupt


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飙升
T2.interrupt();
T2.join();

6.StampedLock的官方实例


final StampedLock sl = 
  new StampedLock();

// 乐观读
long stamp = 
  sl.tryOptimisticRead();
// 读入方法局部变量
......
// 校验stamp
if (!sl.validate(stamp)){
  // 升级为悲观读锁
  stamp = sl.readLock();
  try {
    // 读入方法局部变量
    .....
  } finally {
    //释放悲观读锁
    sl.unlockRead(stamp);
  }
}
//使用方法局部变量执行业务操作
......
posted @ 2021-07-06 16:18  SpecialSpeculator  阅读(63)  评论(0编辑  收藏  举报