• 读多写少的场景下,竟然还有比读写锁更牛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);
     }

     

  • 相关阅读:
    Dell Optiplex 330上Windows 7激活方法与工具
    了解控制器、控制器操作和操作结果
    [转]为C# Windows服务添加安装程序
    前段时间晚上回家不想看电视了,就做了个网站
    MSSQL 2008里事务的一个问题
    关于JavaScript解析XML的性能的问题(已解决)
    自己做了一个教育的网站
    Windows 7下安装SQL Server 2005过程详解
    (转)C#学习基础概念二十五问
    了解模型、视图和控制器
  • 原文地址:https://www.cnblogs.com/YXBLOGXYY/p/16073795.html
Copyright © 2020-2023  润新知