乐趣区

关于java:并发王者课铂金02豁然开朗晦涩难懂的ReadWriteLock竟如此妙不可言

欢送来到《并发王者课》,本文是该系列文章中的 第 15 篇

在上篇文章中,咱们介绍了 Java 中锁的根底 Lock 接口。在本文中,咱们将介绍 Java 中锁的另外一个重要的基本型接口,即 ReadWriteLock 接口。

在摸索 Java 中的并发时,ReadWriteLock 无疑是重要的,然而了解它却并不容易。如果你此前已经检索材料,应该会发现大部分的文章对它的形容都比拟艰涩难懂,或连篇累牍的源码排列,或隔靴搔痒的喋喋不休,既说不到重点,也说不清前因后果。

所以,在本文中咱们会将介绍的重点放在对思路的了解上,而不是对源码的解读上。对于源码以及其背地的常识,咱们将在前面的更高级的系列中进行解说。

一、了解 ReadWriteLock 存在的价值

了解 ReadWriteLock,首页要了解它存在的意义是什么。换言之,它要解决什么问题。为此,咱们无妨从下图着手一探到底。

不知你看明确了没有,这幅图所表白的有三层含意:

  • 大量线程在竞争同一份资源;
  • 这些线程中有的是 读申请 ,有的是 写申请
  • 在多个线程的申请中,读申请显著高于写申请

这样的场景是否似曾相识?没错,它就是典型的缓存利用场景

家喻户晓,缓存的存在是为了进步利用的读写性能。一方面,咱们须要通过缓存拦挡大量的读数据的申请。另一方面,咱们也须要不定期地更新缓存。但总体而言,更新缓存的次数远远小于读缓存的次数

在这个过程中,关键问题在于,为了保持数据一致性,咱们在读写缓存的时候,不能让读申请拿到脏数据,这就须要用到锁 。然而, 更要害的问题在于,尽管读写之间须要互斥,但读与读之间不能够互斥

总结来说,这个问题次要有上面这几个要点:

  • 数据容许多个线程同时读取,但只容许一个线程进行写入
  • 在读取数据的时候,不能够存在写操作或者写申请
  • 在写数据的时候,不能够存在读申请

如果你对此依然有些迷茫,那么上面这张图倡议你珍藏,这张图正是 ReadWriteLock 对问题的概述和它的解决方案,也是诠释 ReadWriteLock 最好的一幅图。

在你没有了解 ReadWriteLock 之前,你会感觉它非常艰涩且源码干燥。然而,一旦你了解它要解决的问题,以及它所提供的计划后,你会发现它的设计居然如此奇妙 。它居然设计了两种截然不同的锁, 其中一把正如咱们此前认知的那样是线程互斥的,而另一把锁居然能够为多个线程所共享!两把锁的完满配合,解决了并发读写的场景问题。

在豁然开朗后,所谓源码不过是队列与共享,它们是 ReadWriteLock 的一种实现形式,而不是阻挡你了解的绊脚石。

二、自主实现 ReadWriteLock

在了解了 ReadWriteLock 背地的问题和它的解决思路之后,咱们就能够齐全抛开 JDK 中的源码本人实现一把读写锁。

public class ReadWriteLock{

  private int readers       = 0;
  private int writers       = 0;
  private int writeRequests = 0;

  public synchronized void lockRead() throws InterruptedException{while(writers > 0 || writeRequests > 0){wait();
    }
    readers++;
  }

  public synchronized void unlockRead(){
    readers--;
    notifyAll();}

  public synchronized void lockWrite() throws InterruptedException{
    writeRequests++;

    while(readers > 0 || writers > 0){wait();
    }
    writeRequests--;
    writers++;
  }

  public synchronized void unlockWrite() throws InterruptedException{
    writers--;
    notifyAll();}
}

在读锁 lockRead() 中,是不容许有 写申请 写操作 的。如果有,那么读申请将进入期待。

而在 lockWrite() 中,同时不容许读申请和其余写操作的存在,此时只容许有一个写申请

以上就是读写锁简略的自主实现形式。当然,它是不欠缺的,只是根本的示例 。它没有思考到根本的 线程重入 问题,真实情况也比它简单很多,但你了解它的意思就好。

三、Java 中的 ReadWriteLock 是如何实现的

最初,咱们再来看 JDK 中的 ReadWriteLock 实现的一些基本思路。ReadWriteLock 和咱们上篇所说的 Lock 接口以及其余类的根本关系如下图所示:

能够看到,JDK 中的读写锁的实现是在 ReentrantReadWriteLock 这个类中。ReentrantReadWriteLock 蕴含了两个外部类:ReadLock 和 WriteLock,而这两个类又实现了 Lock 接口。

读写锁的降级与降级

读写锁的降级与降级是 ReentrantReadWriteLock 中的一个重要知识点,也是高频的面试题。

从读锁到写锁,称之为锁的降级,反之为锁的降级。了解读写锁的降级和降级,最直观的形式是写代码验证。

代码片段 1,先获取读锁,再获取写锁。

public class ReadWriteLockDemo {public static void main(String[] args) {ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readWriteLock.readLock().lock();
        System.out.println("曾经获取读锁...");
        readWriteLock.writeLock().lock();
        System.out.println("曾经获取写锁...");
    }
}

输入后果如下:

曾经获取读锁...

代码片段 2,先获取写锁,再获取读锁:

public class ReadWriteLockDemo {public static void main(String[] args) {ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        readWriteLock.writeLock().lock();
        System.out.println("曾经获取写锁...");
        readWriteLock.readLock().lock();
        System.out.println("曾经获取读锁...");
    }
}

输入后果如下:

曾经获取写锁...
曾经获取读锁...

Process finished with exit code 0

这样一来,后果曾经非常明了。ReentrantReadWriteLock 反对锁的降级,但不反对锁的降级

读写锁中的公平性

在后面的文章中,咱们讲过线程饥饿的由来和结果,所以良好的并发工具类在设计时都会思考到公平性,ReentrantReadWriteLock 也是如此。

在 ReentrantReadWriteLock 中,同时提供了偏心和非偏心两种模式,且默认为非偏心模式。从上面摘取的源码片段中,能够清晰地看到。

 public ReentrantReadWriteLock() {this(false);
 }

    /**
   /**
 * Creates a new {@code ReentrantReadWriteLock} with
 * default (nonfair) ordering properties.
 */
public ReentrantReadWriteLock() {this(false);
}

/**
 * Creates a new {@code ReentrantReadWriteLock} with
 * the given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantReadWriteLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
  readerLock = new ReadLock(this);
  writerLock = new WriteLock(this);
}

小结

以上就是对于读写锁的全部内容。在本文中,咱们从缓存问题登程,接着从 ReadWriteLock 中寻找答案,以便能从更轻松的角度了解 ReadWriteLock 的前因后果。

了解 ReadWriteLock 的要害不在于对源码的分析,而在于对其思路的了解。

另外,咱们简略地介绍了 ReentrantReadWriteLock 中的一些要害知识点,但诸如其背地的 AQS 等并没有开展陈说。对此也不用焦急,咱们会在前面有具体的剖析介绍。

注释到此结束,祝贺你又上了一颗星✨

夫子的试炼

  • 尝试在示例代码中减少对读写线程的重入反对。

延长浏览与参考资料

  • 示例代码参考
  • 《并发王者课》纲要与更新进度总览

对于作者

关注公众号【技术八点半】,及时获取文章更新。传递有品质的技术文章,记录平凡人的成长故事,偶然也聊聊生存和现实。晚上 8:30 推送作者品质原创,早晨 20:30 推送行业深度好文。

如果本文对你有帮忙,欢送 点赞 关注 监督 ,咱们一起 从青铜到王者

退出移动版