读多写少的场景下,竟然还有比读写锁更牛X的锁?

1)上一篇文章我们聊了读写锁,他的适用场景是读多写少的场景下,那有没有其它性能比读写锁还要牛逼的锁呢?

  • StampedLock ,java1.8诞生的。

2)StampedLock比读写锁牛在什么地方?

  • 读写锁分为两种:读锁和写锁

  • StampedLock有三种模式:写锁和悲观读锁,这两个对应我们的读写锁的写锁和读锁,功能是一样的。但是他的杀手锏是乐观读,注意是乐观读,不是乐观读锁。

  • 乐观读是一种操作,不涉及到锁。当多个线程在读的时候,允许一个线程获取读锁,这个就是StampedLock与读写锁不同的地方。因为不涉及到锁,所以为了保障并发安全,会有一个stamp来作为安全的标志。类似于我们数据库乐观锁的version。

3)写锁和悲观读锁与我们读写锁的细致区别是什么?

  • 他两加锁的时候会返回一个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);
     }

     

4)乐观读是怎样使用的?

  • tryOptimisticRead()就是乐观读,因为是无锁的,所所以共享变量 x 和 y 读入方法局部变量时,x 和 y 有可能被其他线程修改了。因此最后读完之后,还需要再次验证一下是否存在写操作,这个验证操作是通过调用 validate(stamp) 来实现的。

 
 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);
  }
 }
  • 上面的代码中,如果乐观读的时候,存在写操作,那么就把它升级为悲观读锁。这样就避免了乐观读一直循环浪费大量的cpu,使用的时候尽量这样去做。

5)StampedLock有哪些注意事项?

  • 千万不要在线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时调用该阻塞线程的interrupt()方法,会导致运行这个线程的cpu挂掉的。如果实在要用中断方法,那就用带interrupt的悲观读锁 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飙升
     T2.interrupt();
     T2.join();

     

  • StampedLock的功能是不如读写锁的那么多的

  • StampedLock是不支持嵌套使用的,也就是可重入锁。

6)以后有用到StampedLock的需求的时候,使用的模板应该是怎样的?

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);
 }

 

posted on 2022-03-29 20:55  Love&Share  阅读(169)  评论(0编辑  收藏  举报

导航