ReentrantReadWriteLock类

2024-05-04 18:12
文章标签 reentrantreadwritelock

本文主要是介绍ReentrantReadWriteLock类,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

为了有了ReentrantLock还需要ReentrantReadWriteLock

ReentrantReadWriteLock是一个读写锁,它允许多个读操作同时进行,但在写操作时会阻止其他所有读和写操作。这种锁特别适合于读取远多于写入的场景,因为它可以提高并发性而不会牺牲数据一致性。

  1. 分离的读锁和写锁:ReentrantReadWriteLock提供了两个锁,一个用于读操作(readLock),另一个用于写操作(writeLock)。
  2. 读锁共享性:读锁可以被多个线程同时持有,只要没有线程持有写锁。
  3. 写锁独占性:写锁是独占的,只能由一个线程持有,并且在任何线程持有写锁时,读锁也不能被其他线程持有。
  4. 公平性选择:与ReentrantLock一样,ReentrantReadWriteLock也可以设置为公平模式。

尽管ReentrantLock提供了强大的功能,但它不支持区分读写操作的锁定策略。在某些情况下,使用ReentrantReadWriteLock可能更高效,因为它允许多个线程同时读取共享资源,从而减少了锁竞争的可能性。这对于读多写少的数据结构特别有用,比如缓存、计数器、集合等。

源码分析

在这里插入图片描述

ReentrantReadWriteLock实现了ReadWriteLock接口,ReadWriteLock接口定义了获取读锁和写锁的规范,具体需要实现类去实现;同时其还实现了Serializable接口,表示可以进行序列化,在源代码中可以看到ReentrantReadWriteLock实现了自己的序列化逻辑。Sync继承自AQS、NonfairSync继承自Sync类、FairSync继承自Sync类;ReadLock实现了Lock接口、WriteLock也实现了Lock接口。

内部类分析

  1. ReadLock: 实现了读锁的功能,提供了读取时的锁定和释放操作。当线程请求读锁时,如果当前没有写锁被其他线程持有,则允许获取读锁。
  2. WriteLock: 实现了写锁的功能,提供了写入时的排他锁定和释放操作。当线程请求写锁时,会阻塞直到所有读锁被释放并且没有其他写锁被持有。
  3. FairSync和NonfairSync: 都是 Sync 的子类,FairSync实现了公平的锁获取策略。在公平模式下,等待时间最长的线程将获得锁。NonfairSync实现了非公平的锁获取策略。在非公平模式下,不保证按照请求锁的顺序授予锁,新来的线程可能会插队获取锁。
  4. RWLock: 定义了读写锁的基本操作。
  5. SyncHelper: 用于帮助管理同步状态。
  6. HoldCounter: HoldCounter主要与读锁配套使用。HoldCounter主要有两个属性,count和tid,其中count表示某个读线程重入的次数,tid表示该线程的tid字段的值,该字段可以用来唯一标识一个线程

主要方法

  • tryRelease 该方法用于释放写锁资源,首先会判断该线程是否为独占线程,若不为独占线程,则抛出异常,否则,计算释放资源后的写锁的数量,若为0,表示成功释放,资源不将被占用,否则,表示资源还被占用。
protected final boolean tryRelease(int releases) {if (!isHeldExclusively())throw new IllegalMonitorStateException();int nextc = getState() - releases;boolean free = exclusiveCount(nextc) == 0;if (free)setExclusiveOwnerThread(null);setState(nextc);return free;}
  • tryAcquire 用于实现锁的获取逻辑,允许线程以可重入的方式获取锁。其中,变量c为锁状态,变量w表示当前状态c中独占锁的持有数量。如果当前状态不为0,说明锁已经被持有。如果当前锁是共享的(w == 0),或者持有锁的线程不是当前线程,则直接返回false,表示获取失败。前状态不为0,说明锁已经被持有。如果当前线程已经持有锁(判断了重入次数是否超过了最大限制),则简单增加状态计数并返回true,表示获取成功:
  boolean tryAcquire(int acquires) {Thread current = Thread.currentThread();int c = getState();int w = exclusiveCount(c);if (c != 0) {// (Note: if c != 0 and w == 0 then shared count != 0)if (w == 0 || current != getExclusiveOwnerThread())return false;if (w + exclusiveCount(acquires) > MAX_COUNT)throw new Error("Maximum lock count exceeded");// Reentrant acquiresetState(c + acquires);return true;}if (writerShouldBlock() ||!compareAndSetState(c, c + acquires))return false;//设置当前线程为独占锁的所有者setExclusiveOwnerThread(current);return true;}
  • tryReleaseShared 这个方法的作用是尝试释放共享锁(读锁)。
 protected final boolean tryReleaseShared(int unused) {Thread current = Thread.currentThread();// 当前线程为第一个读线程if (firstReader == current) {if (firstReaderHoldCount == 1)firstReader = null;elsefirstReaderHoldCount--;} else {HoldCounter rh = cachedHoldCounter;//获取缓存的HoldCounter对象rh,如果rh为空或者rh.tid不等于当前线程的ID,则从readHolds中获取新的HoldCounter对象。if (rh == null || rh.tid != getThreadId(current))rh = readHolds.get();int count = rh.count;if (count <= 1) {//从readHolds中移除当前的HoldCounter对象。readHolds.remove();if (count <= 0)throw unmatchedUnlockException();}--rh.count;}for (;;) {int c = getState();int nextc = c - SHARED_UNIT;//尝试更新状态值if (compareAndSetState(c, nextc))return nextc == 0;}}
  • tryAcquireShared 用于读锁线程获取读锁。如果exclusiveCount©不等于0且getExclusiveOwnerThread()不等于当前线程,说明有其他线程持有写锁,此时无法获取共享锁,直接返回-1。如果readerShouldBlock()为false且r(读取次数)小于MAX_COUNT,尝试通过CAS操作更新状态值c,将共享锁计数器加1。如果更新成功,根据不同情况更新firstReader和firstReaderHoldCount的值,并返回1表示获取共享锁成功。
 protected final int tryAcquireShared(int unused) {Thread current = Thread.currentThread();int c = getState();if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)return -1;int r = sharedCount(c);if (!readerShouldBlock() &&r < MAX_COUNT &&compareAndSetState(c, c + SHARED_UNIT)) {if (r == 0) {firstReader = current;firstReaderHoldCount = 1;} else if (firstReader == current) {firstReaderHoldCount++;} else {HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))cachedHoldCounter = rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;}return 1;}return fullTryAcquireShared(current);}

代码使用示例

ReadThread和WriteThread是自定义的线程类,它们分别代表读线程和写线程。读线程:

public class ReadThread extends Thread{private ReentrantReadWriteLock rrwLock;public ReadThread(String name, ReentrantReadWriteLock rrwLock) {super(name);this.rrwLock = rrwLock;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " trying to lock");try {rrwLock.readLock().lock();System.out.println(Thread.currentThread().getName() + " lock successfully");Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();} finally {rrwLock.readLock().unlock();System.out.println(Thread.currentThread().getName() + " unlock successfully");}}
}

写线程:

public class WriteThread extends Thread{private ReentrantReadWriteLock rrwLock;public WriteThread(String name, ReentrantReadWriteLock rrwLock) {super(name);this.rrwLock = rrwLock;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " trying to lock");try {rrwLock.writeLock().lock();System.out.println(Thread.currentThread().getName() + " lock successfully");} finally {rrwLock.writeLock().unlock();System.out.println(Thread.currentThread().getName() + " unlock successfully");}}
}

使用,创建两个读线程和一个写线程。然后启动这些线程:

public static void main(String[] args) {ReentrantReadWriteLock rrwLock = new ReentrantReadWriteLock();ReadThread rt1 = new ReadThread("rt1", rrwLock);ReadThread rt2 = new ReadThread("rt2", rrwLock);WriteThread wt1 = new WriteThread("wt1", rrwLock);rt1.start();rt2.start();wt1.start();}

执行结果:
在这里插入图片描述

这篇关于ReentrantReadWriteLock类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/959804

相关文章

55.ReentrantReadWriteLock应用于缓存

简单的缓存案例 模拟一个数据层dao @Slf4jpublic class GenericDao {public <T> T queryOne(Class<T> beanClass, String sql, Object... args) {try {log.debug("进入数据库查询.....");Constructor<T> constructor = beanClass.getDec

多线程学习(七)-读写锁(ReentrantReadWriteLock)与应用(缓存)

读写锁(ReentrantReadWriteLock):分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁! 读写锁机制      读-读不互斥      读-写互斥

Java中的读写锁ReentrantReadWriteLock详解,存在一个小缺陷

写在开头 最近是和java.util.concurrent.locks包下的同步类干上了,素有 并发根基 之称的concurrent包中全是精品,今天我们继续哈,今天学习的主题要由一个大厂常问的Java面试题开始: 小伙子,来说一说Java中的读写锁,你都用过哪些读写锁吧? 这个问题小伙伴们遇到了该如何回答呢?心里琢磨去吧,哈哈😄,不过build哥的回答要用从ReentrantRea

七:ReentrantReadWriteLock —— 读写锁

目录 1、ReentrantReadWriteLock 入门1.1、概念1.2、案例1.2.1、写写互斥1.2.2 锁降级 2、ReentrantReadWriteLock 源码解析2.1、属性2.2、构造方法2.3、内部类2.4、读写状态的设计 —— 按位切割使用2.5、【写锁】加锁方法 `lock()` —— ReentrantReadWriteLock.WriteLock2.4.1、

读写锁ReentrantReadWriteLock源码分析

文章目录 读写锁的介绍写锁详解写锁的获取写锁的释放 读锁详解读锁的获取读锁的释放 锁降级 读写锁的介绍 在并发场景中用于解决线程安全的问题,我们几乎会高频率的使用到独占式锁,通常使用java提供的关键字synchronized(关于synchronized可以看这篇文章)或者concurrents包中实现了Lock接口的ReentrantLock。它们都是独占式获取锁,也就是在

一文搞懂“ReentrantReadWriteLock——读写锁”

文章目录 初识读写锁ReentrantReadWriteLock类结构注意事项 ReentrantReadWriteLock源码分析读写状态的设计HoldCounter 计数器读锁的获取读锁的释放写锁的获取写锁的释放 锁降级 初识读写锁 Java中的锁——ReentrantLock和synchronized都是排它锁,意味着这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时

死磕Java并发编程(7):读写锁 ReentrantReadWriteLock 源码解析

这篇文章 我们在一起来看看 读写锁 ReentrantReadWriteLock 的源码分析,基于Java8。 阅读建议:由于Java并发包中的锁都是基于AQS实现的,本篇的读写锁也不例外。如果你还不了解的话,阅读起来会比较吃力。建议先阅读上一篇文章关于 AbstractQueuedSynchronizer 的源码解析。 AQS详解,这次就彻底搞懂Java并发包中的锁原理,不用每次面试都去背一遍

死磕 java同步系列之ReentrantReadWriteLock源码解析

问题 (1)读写锁是什么? (2)读写锁具有哪些特性? (3)ReentrantReadWriteLock是怎么实现读写锁的? (4)如何使用ReentrantReadWriteLock实现高效安全的TreeMap? 简介 读写锁是一种特殊的锁,它把对共享资源的访问分为读访问和写访问,多个线程可以同时对共享资源进行读访问,但是同一时间只能有一个线程对共享资源进行写访问,使用读写锁可以极

Java 读写锁 ReentrantReadWriteLock 源码分析

本文内容:读写锁 ReentrantReadWriteLock 的源码分析,基于 Java7/Java8。 阅读建议:虽然我这里会介绍一些 AQS 的知识,不过如果你完全不了解 AQS,看本文就有点吃力了。 目录 使用示例 下面这个例子非常实用,我是 javadoc 的搬运工: // 这是一个关于缓存操作的故事class CachedData { Object data; volat

JUC-ReentrantLock,ReentrantReadWriteLock,StampedLock

1. 概述 前面介绍过了synchronized关键字作用的锁升级过程 无锁->偏向锁->轻量锁->重锁 下面再介绍实现Lock接口的锁的升级过程 无锁->独占锁(ReentrantLock,Synchronized)->读写锁(ReentranReadWriteLock)->邮戳锁(StampedLock) 并准备了一些问题,回顾一下自己对知识的掌握程度。 你知道Java里面有哪些锁?你说你