比ReadWriteLock效率更高的读写锁

83 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

在开发过程中,遇到了读的操作很多,但是写的操作很少,这时候我们再去对读操作和写操作都加锁,这时候对·读操作再加上锁就会显得多余。这是我们需要采用新的锁-----读写锁

含义

读写锁顾名思义是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的。总结来说,读写锁的特点是:读读不互斥、读写互斥、写写互斥

读写锁可以保证在没有写入操作的时候,多个线程允许同时读取来提高性能。

常用的两种读写锁

  • ReentrantReadWriteLock 可重入锁
  • Java8下面的StampedLock 不可重入锁

对比StampedLock

ReentrantReadWriteLock 存在一个问题就是读锁是一个悲观锁,如果又线程真在进行读操作,写线程需要等待读线程释放锁后才能获取写锁,就是读的过程中中是不允许写入的。他是一个可重入锁,可以在一个线程中反复获取同一个锁。

StampedLock的好处就是在读的过程中允许进行写入操作,但在读的的过程进行写入后就需要判断读的时候是否有写入,来保证数据的一致性,所以StampedLock的读锁是一种乐观锁。乐观锁即通过版本号来控制读的过程中是否有写入操作。他是一个不可重入锁,不能在一个线程中反复获取同一个锁。

StampedLock还提供了更复杂的将悲观读锁升级为写锁的功能,它主要使用在if-then-update的场景:即先读,如果读的数据满足条件,就返回,如果读的数据不满足条件,再尝试写。

  • long tryConvertToWriteLock(long stamp) 转换为写锁(锁升级)
  • long tryConvertToReadLock(long stamp)转换为读锁(锁降级)
  • long tryConvertToOptimisticRead(long stamp)

下面编写代码演示StampedLock的使用

  
  public class StampedLockTest {
      private final StampedLock stampedLock = new StampedLock();
  ​
      private double x = 1.0;
  ​
      // 写锁
      public void add() throws InterruptedException {
          // 版本号
          long stamp = stampedLock.writeLock();
          System.out.println("获取了写锁"+stamp);
          Thread.sleep(500);
          try {
              x = x + 12.0;
          } finally {
              stampedLock.unlock(stamp);
              System.out.println("写锁释放"+stamp);
          }
      }
  ​
      // 读锁
      public double read() throws InterruptedException {
          // 获得一个乐观锁版本号
          long stamp = stampedLock.tryOptimisticRead();
          System.out.println("获取了读锁"+stamp);
          Thread.sleep(1000);
          double y = x;
          // 验证是否有写锁,即版本号是否发生变化
          if (!stampedLock.validate(stamp)) {
              System.out.println("两次版本号不一致");
              long lock = stampedLock.readLock();
              try {
                  y = x;
              } finally {
                  stampedLock.unlockRead(lock);
              }
          }
          return y;
      }
  ​
      public static void main(String[] args) {
          StampedLockTest lockTest = new StampedLockTest();
          Thread add = new Thread(() -> {
              try {
                  lockTest.add();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          });
          Thread read = new Thread(() -> {
              try {
                  lockTest.read();
                  add.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          });
          read.start();
          add.start();
      }
  ​
  }

1666168507823.png

从结果分析可以看出

程序先获取了读锁,在写线程执行完成后版本后发生了变化,所以可以进入if()判断语句。即实现了在读锁的同时可以获的写锁来进行操作。

总结

StampedLock不能完成替代ReentrantReadWriteLock

`StampedLock`不支持公平锁,也不支持Condition

StampedLock不可重入