深入ReentrantReadWriteLock(一)

news/2025/1/12 18:54:25/

一、为什么要出现读写锁

synchronized和ReentrantLock都是互斥锁。
如果说有一个操作是读多写少的,还要保证线程安全的话。如果采用上述的两种互斥锁,效率方面很定是很低的。
在这种情况下,咱们就可以使用ReentrantReadWriteLock读写锁去实现。
读读之间是不互斥的,可以读和读操作并发执行。
但是如果涉及到了写操作,那么还得是互斥的操作。

static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
readLock.lock();
try {
System.out.println("子线程!");
try {
Thread.sleep(500000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
readLock.unlock();
}
}).start();Thread.sleep(1000);
writeLock.lock();
try {
System.out.println("主线程!");
} finally {
writeLock.unlock();
}
}

二、读写锁的实现原理

ReentrantReadWriteLock还是基于AQS实现的,还是对state进行操作,拿到锁资源就去干活,如果没有拿到,依然去AQS队列中排队。
读锁操作:基于state的高16位进行操作。
写锁操作:基于state的低16为进行操作。
ReentrantReadWriteLock依然是可重入锁。
写锁重入:读写锁中的写锁的重入方式,基本和ReentrantLock一致,没有什么区别,依然是state进行+1操作即可,只要确认持有锁资源的线程,是当前写锁线程即可。只不过之前ReentrantLock的重入次数是state的正数取值范围,但是读写锁中写锁范围就变小了。
读锁重入:因为读锁是共享锁。读锁在获取锁资源操作时,是要对state的高16位进行 + 1操作。因为读锁是共享锁,所以同一时间会有多个读线程持有读锁资源。这样一来,多个读操作在持有读锁时,无法确认每个线程读锁重入的次数。为了去记录读锁重入的次数,每个读操作的线程,都会有一个ThreadLocal记录锁重入的次数。
写锁的饥饿问题:读锁是共享锁,当有线程持有读锁资源时,再来一个线程想要获取读锁,直接对state修改即可。在读锁资源先被占用后,来了一个写锁资源,此时,大量的需要获取读锁的线程来请求锁资源,如果可以绕过写锁,直接拿资源,会造成写锁长时间无法获取到写锁资源。
读锁在拿到锁资源后,如果再有读线程需要获取读锁资源,需要去AQS队列排队。如果队列的前面需要写锁资源的线程,那么后续读线程是无法拿到锁资源的。持有读锁的线程,只会让写锁线程之前的读线程拿到锁资源。

三、写锁分析

3.1 写锁加锁流程概述

3.2 写锁加锁源码分析

写锁加锁流程

// 写锁加锁的入口
public void lock() {
sync.acquire(1);
}
// 阿巴阿巴!!
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}
// 读写锁的写锁实现tryAcquire
protected final boolean tryAcquire(int acquires) {
// 拿到当前线程
Thread current = Thread.currentThread();
// 拿到state的值
int c = getState();
// 得到state低16位的值
int w = exclusiveCount(c);
// 判断是否有线程持有着锁资源
if (c != 0) {
// 当前没有线程持有写锁,读写互斥,告辞。
// 有线程持有写锁,持有写锁的线程不是当前线程,不是锁重入,告辞。
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 当前线程持有写锁。 锁重入。
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 没有超过锁重入的次数,正常 + 1
setState(c + acquires);
return true;
}
// 尝试获取锁资源
if (writerShouldBlock() ||
// CAS拿锁!compareAndSetState(c, c + acquires))
return false;
// 拿锁成功,设置占有互斥锁的线程
setExclusiveOwnerThread(current);
// 返回true
return true;
}
// ================================================================
// 这个方法是将state的低16位的值拿到
int w = exclusiveCount(c);
state & ((1 << 16) - 1)
00000000 00000000 00000000 00000001 == 1
00000000 00000001 00000000 00000000 == 1 << 16
00000000 00000000 11111111 11111111 == (1 << 16) - 1
&运算,一个为0,必然为0,都为1,才为1
// ================================================================
// writerShouldBlock方法查看公平锁和非公平锁的效果
// 非公平锁直接返回false执行CAS尝试获取锁资源
// 公平锁需要查看是否有排队的,如果有排队的,我是否是head的next

3.3 写锁释放锁流程概述&释放锁源码

释放的流程和ReentrantLock一致,只是在判断释放是否干净时,判断低16位的值

// 写锁释放锁的tryRelease方法
protected final boolean tryRelease(int releases) {
// 判断当前持有写锁的线程是否是当前线程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 获取state - 1
int nextc = getState() - releases;
// 判断低16位结果是否为0,如果为0,free设置为true
boolean free = exclusiveCount(nextc) == 0;
if (free)
// 将持有锁的线程设置为null
setExclusiveOwnerThread(null);
// 设置给state
setState(nextc);
// 释放干净,返回true。 写锁有冲入,这里需要返回false,不去释放排队的Node
return free;
}


http://www.ppmy.cn/news/1236886.html

相关文章

详解Vue中的computed和watch

详解Vue中的computed和watch 前言原理computedcomputed特点computed有几种创建方式应用 WatchWatch有几种创建方式Watch主要内容Watch特性应用场景 computed和Watch区别 前言 在Vue当中&#xff0c;watch和computed都可以实现监听的效果&#xff0c;本文主要是围绕watch和comp…

如何通过类似于Android adb install apk 命令安装三方Harmony Hap包

安装命令 hdc install xxx.hapOpenHarmony设备安装Hap应用的五种方式 https://www.51cto.com/article/762223.htmlhttps://www.51cto.com/article/762223.html DevEco Studio 3.1为例新建个项目&#xff0c;点击File->Project Structure 进入签名页面然后点击Sign in登录华…

Python进行threading多线程编程及高级并发处理机制

threading 模块是 Python 中用于进行多线程编程的标准库之一。通过 threading 模块&#xff0c;你可以创建和管理线程&#xff0c;使得程序能够并发执行多个任务。以下是一些基本的 threading 模块的用法&#xff1a; 1. 创建线程&#xff1a; 使用 threading.Thread 类可以创…

HIVE SQL 判断空值函数

目录 nvl()coalesce() nvl() select nvl(null,2);输出&#xff1a;2 select nvl(,2);输出&#xff1a;‘’ coalesce() select coalesce(null,2);输出&#xff1a;2 select coalesce(,2);输出&#xff1a;‘’ select coalesce(null,null,2);输出&#xff1a;2 *coalesc…

element plus 使用细节

菜鸟一直在纠结这个写不写&#xff0c;因为不难&#xff0c;但是菜鸟老是容易忘记&#xff0c;虽然想想或者搜搜就可以马上写出来&#xff0c;但是感觉每次那样就太麻烦了&#xff0c;不如一股做气写了算了&#xff0c;后面遇见别的就再来补充&#xff01; 文章目录 table 表格…

YOLOv5 分类模型 数据集加载 3

YOLOv5 分类模型 数据集加载 3 自定义类别 flyfish YOLOv5 分类模型 数据集加载 1 样本处理 YOLOv5 分类模型 数据集加载 2 切片处理 YOLOv5 分类模型的预处理&#xff08;1&#xff09; Resize 和 CenterCrop YOLOv5 分类模型的预处理&#xff08;2&#xff09;ToTensor 和 …

火电厂电气部分设计

摘要 本文首先根据任务书上所给系统与线路及所有负荷的参数&#xff0c;分析负荷发展趋势。从负荷增长方面阐明了建站的必要性&#xff0c;然后通过对拟建变电站的概括以及出线方向来考虑&#xff0c;并通过对负荷资料的分析&#xff0c;安全&#xff0c;经济及可靠性方面考虑…

C++二分向量算法:最多可以参加的会议数目 II

本题的其它解法 C二分算法&#xff1a;最多可以参加的会议数目 II 本文涉及的基础知识点 二分查找算法合集 题目 给你一个 events 数组&#xff0c;其中 events[i] [startDayi, endDayi, valuei] &#xff0c;表示第 i 个会议在 startDayi 天开始&#xff0c;第 endDayi …