读写锁: ReentrantReadWriteLock

在多线程编程场景中,对共享资源的访问控制极为关键。传统的锁机制在同一时刻只允许一个线程访问共享资源,这在读写操作频繁的场景下,会因为读操作相互不影响数据一致性,而造成不必要的性能损耗。ReentrantReadWriteLock(可重入读写锁)的出现有效解决了这一问题,它允许在同一时间内多个线程进行读操作,同时保证写操作的原子性与线程安全性。本文将深入探讨ReentrantReadWriteLock的原理、使用方式及其应用场景。

ReentrantReadWriteLock 原理剖析

ReentrantReadWriteLock是 Java 并发包java.util.concurrent.locks中的成员,它将对共享资源的访问分为读锁和写锁。读锁允许多个线程同时获取,因为读操作不会修改共享资源,所以多个线程同时读不会产生数据不一致问题。而写锁是独占的,同一时间仅能有一个线程获取写锁,以此保证写操作的原子性和线程安全。

可重入特性

ReentrantReadWriteLock具备可重入特性,即同一个线程能够多次获取读锁或写锁。当线程获取锁后,锁的持有计数会增加,每释放一次锁,持有计数就减少,当持有计数为 0 时,锁才真正被释放。该特性避免了线程在递归调用时出现死锁情况。

公平性与非公平性

ReentrantReadWriteLock支持公平和非公平两种模式。在公平模式下,线程获取锁的顺序依照请求的先后顺序进行;在非公平模式下,线程获取锁的顺序不确定,新请求的线程有可能比等待队列中的线程更早获取到锁。非公平模式在高并发场景下通常性能更佳,因为它减少了线程切换的开销。

代码示例

以下通过一个简单示例代码展示ReentrantReadWriteLock的基本用法。假设存在一个共享缓存,多个线程可能读取缓存中的数据,也可能更新缓存。

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    private static final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    private static String cacheData;

    public static void main(String[] args) {
        // 模拟读线程
        Thread readThread1 = new Thread(() -> {
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " 开始读取数据");
                if (cacheData == null) {
                    System.out.println("缓存中没有数据");
                } else {
                    System.out.println("读取到的数据: " + cacheData);
                }
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " 读取数据结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
            }
        });

        // 模拟写线程
        Thread writeThread1 = new Thread(() -> {
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " 开始写入数据");
                cacheData = "新的数据";
                System.out.println(Thread.currentThread().getName() + " 写入数据结束");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                writeLock.unlock();
            }
        });

        readThread1.start();
        writeThread1.start();

        try {
            readThread1.join();
            writeThread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中:

  1. 首先创建了ReentrantReadWriteLock对象,以及它的读锁和写锁。
  2. readThread1线程模拟读操作,获取读锁后,尝试读取缓存数据。由于读锁允许多个线程同时获取,所以多个读线程可以同时执行读操作。
  3. writeThread1线程模拟写操作,获取写锁后,更新缓存数据。在写锁被持有期间,其他线程无论是读还是写操作,都将被阻塞,直到写锁被释放。

使用场景

  1. 缓存系统:在缓存系统中,读操作的频率通常远高于写操作。使用ReentrantReadWriteLock可以允许多个线程同时读取缓存,只有在更新缓存时才需要获取写锁,从而大大提高系统的并发性能。
  2. 数据库连接池:数据库连接池需要管理多个数据库连接,多个线程可能会同时获取和释放连接信息。通过ReentrantReadWriteLock,读操作(如获取连接状态)可以并发执行,而写操作(如添加或移除连接)则保证线程安全。
  3. 文件系统:在文件系统中,多个线程可能会同时读取文件内容,而写操作(如文件修改)则需要保证原子性。ReentrantReadWriteLock可以有效管理对文件的读写访问,确保数据的一致性。

结语

感谢您的阅读!如果您对 ReentrantReadWriteLock 或其他并发编程话题有任何疑问或见解,欢迎继续探讨。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/964481.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C语言深入探索】:指针高级应用与极致技巧(二)

目录 一、指针与数组 1.1. 数组指针 1.2. 指向多维数组的指针 1.2.1. 指向多维数组元素的指针 1.2.2. 指向多维数组行的指针 1.3. 动态分配多维数组 1.4. 小结 二、指针与字符串 2.1. 字符串表示 2.2. 字符串处理函数 2.3. 代码示例 2.4. 注意事项 三、指针与文件…

吴恩达深度学习——有效运作神经网络

内容来自https://www.bilibili.com/video/BV1FT4y1E74V,仅为本人学习所用。 文章目录 训练集、验证集、测试集偏差、方差正则化正则化参数为什么正则化可以减少过拟合Dropout正则化Inverted Dropout其他的正则化方法数据增广Early stopping 归一化梯度消失与梯度爆…

蓝桥杯刷题 DAY4:小根堆 区间合并+二分

import os import sys import heapq# 请在此输入您的代码if __name__"__main__":x,n map(int,input().split())l[]a[0]*nb[0]*nc[0]*nq[]for i in range(n):l.append(list( map( int ,input().split()) ))l.sort(keylambda pair:-pair[1])total0j0for i in range(x,0…

K8S学习笔记-------1.安装部署K8S集群环境

1.修改为root权限 #sudo su 2.修改主机名 #hostnamectl set-hostname k8s-master01 3.查看网络地址 sudo nano /etc/netplan/01-netcfg.yaml4.使网络配置修改生效 sudo netplan apply5.修改UUID(某些虚拟机系统,需要设置才能生成UUID)#…

大语言模型深度研究功能:人类认知与创新的新范式

在人工智能迅猛发展的今天,大语言模型(LLM)的深度研究功能正在成为重塑人类认知方式的关键力量。这一突破性技术不仅带来了工具层面的革新,更深刻地触及了人类认知能力的本质。本文将从认知科学的角度出发,探讨LLM如何…

【Redis】Redis 经典面试题解析:深入理解 Redis 的核心概念与应用

文章目录 1. Redis 是什么?它的主要特点是什么?答案:主要特点: 2. Redis 的数据结构有哪些?分别适用于什么场景?答案:keys *命令返回的键顺序 3. Redis 的持久化机制有哪些?它们的优…

基于SpringBoot的物资管理系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…

【力扣】53.最大子数组和

AC截图 题目 思路 这道题主要考虑的就是要排除负数带来的负面影响。如果遍历数组,那么应该有如下关系式: currentAns max(prenums[i],nums[i]) pre是之前记录的最大和,如果prenums[i]小于nums[i],就要考虑舍弃pre,从…

本地部署DeepSeek教程(Mac版本)

第一步、下载 Ollama 官网地址:Ollama 点击 Download 下载 我这里是 macOS 环境 以 macOS 环境为主 下载完成后是一个压缩包,双击解压之后移到应用程序: 打开后会提示你到命令行中运行一下命令,附上截图: 若遇…

代码随想录算法【Day36】

Day36 1049. 最后一块石头的重量 II 思路 把石头尽可能分成两堆,这两堆重量如果相似,相撞后所剩的值就是最小值 若石头的总质量为sum,可以将问题转化为0-1背包问题,即给一个容量为sum/2的容器,如何尽量去凑满这个容…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.28 NumPy+Matplotlib:科学可视化的核心引擎

2.28 NumPyMatplotlib:科学可视化的核心引擎 目录 #mermaid-svg-KTB8Uqiv5DLVJx7r {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-KTB8Uqiv5DLVJx7r .error-icon{fill:#552222;}#mermaid-svg-KTB8Uqiv5…

基序和纯度分数的计算

以下对这两个概念的详细解释: 基序 纯度分数 PWM矩阵的来源 为什么会有PWM矩阵? 一个特定的转录因子(TF)的结合位点的基序(motif)并不是唯一的。实际上,TF结合位点通常具有一定的序列变异性&a…

Linux下的编辑器 —— vim

目录 1.什么是vim 2.vim的模式 认识常用的三种模式 三种模式之间的切换 命令模式和插入模式的转化 命令模式和底行模式的转化 插入模式和底行模式的转化 3.命令模式下的命令集 光标移动相关的命令 复制粘贴相关命令 撤销删除相关命令 查找相关命令 批量化注释和去…

有用的sql链接

『SQL』常考面试题(2——窗口函数)_sql的窗口函数面试题-CSDN博客 史上最强sql计算用户次日留存率详解(通用版)及相关常用函数 -2020.06.10 - 知乎 (zhihu.com) 1280. 学生们参加各科测试的次数 - 力扣(LeetCode&…

算法题(57):找出字符串中第一个匹配项的下标

审题: 需要我们根据原串与模式串相比较并找到完全匹配时子串的第一个元素索引,若没有则返回-1 思路: 方法一:BF暴力算法 思路很简单,我们用p1表示原串的索引,p2表示模式串索引。遍历原串,每次遍历都匹配一次…

线性回归原理和算法

线性回归可以说是机器学习中最基本的问题类型了,这里就对线性回归的原理和算法做一个小结。 对于线性回归的损失函数,我们常用的有两种方法来求损失函数最小化时候的θ参数:一种是梯度下降,一种是最小二乘法。 为了防止模型的过拟…

npm知识

npm 是什么 npm 为你和你的团队打开了连接整个 JavaScript 天才世界的一扇大门。它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个包(package)(即,代码模块)。来自…

【贪心算法篇】:“贪心”之旅--算法练习题中的智慧与策略(三)

✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨ ✨ 个人主页:余辉zmh–CSDN博客 ✨ 文章所属专栏:贪心算法篇–CSDN博客 文章目录 前言例题1.最优除法2.跳跃游戏23.跳跃游戏14.加油站5.单调递…

什么是物理地址,什么是虚拟地址?

摘要 什么是物理地址,什么是虚拟地址? 如果处理器没有MMU或未启用,CPU执行单元发出的内存地址直接传到芯片引脚上,被内存芯片接受,这称为物理地址(Physical Addraress) 如果处理器启用了MMU&a…

LabVIEW图片识别逆向建模系统

本文介绍了一个基于LabVIEW的图片识别逆向建模系统的开发过程。系统利用LabVIEW的强大视觉处理功能,通过二维图片快速生成对应的三维模型,不仅降低了逆向建模的技术门槛,还大幅提升了建模效率。 ​ 项目背景 在传统的逆向建模过程中&#xf…