在高性能并发编程中,如何有效地管理共享资源的访问是核心挑战之一。传统的排他锁(如ReentrantLock)在读多写少的场景下,性能瓶颈尤为突出,因为它不允许并发读取。Java并发包(java.util.concurrent.locks)提供的ReadWriteLock接口及其实现ReentrantReadWriteLock,以及分布式锁框架Redisson提供的RReadWriteLock,为解决这一问题提供了高效的解决方案。

1. 引言

随着多核处理器和分布式系统的普及,并发编程已成为现代软件开发不可或缺的一部分。在多线程环境中,对共享资源的正确访问是确保数据一致性和程序稳定性的关键。然而,不恰当的锁机制可能导致性能瓶颈,尤其是在读操作远多于写操作的场景下。例如,一个缓存系统,其数据被频繁读取,但更新频率较低。如果使用synchronized关键字或ReentrantLock这样的排他锁,即使是多个线程同时读取数据,也必须串行执行,这极大地限制了系统的并发能力。

为了解决这一问题,Java引入了读写锁(ReadWriteLock)的概念。读写锁允许多个读线程同时访问共享资源,从而提高并发性;但在有写线程访问时,所有读写操作都将被阻塞,以保证数据的一致性。这种“读-读共享,读-写互斥,写-写互斥”的特性,使得读写锁成为处理读多写少场景的理想选择。

2. ReadWriteLock 核心概念与原理

2.1 什么是ReadWriteLock

ReadWriteLock是Java java.util.concurrent.locks包中定义的一个接口,它维护了一对相关的锁:一个用于读操作的锁(ReadLock)和一个用于写操作的锁(WriteLock)。其核心思想是区分读操作和写操作,并对它们施加不同的并发控制策略:

  • 读锁(ReadLock):是共享锁。在没有写锁被持有的情况下,多个线程可以同时获取读锁。这意味着,只要没有线程正在修改数据,任意数量的线程都可以并发地读取数据,从而显著提高并发性能。
  • 写锁(WriteLock):是排他锁。只有当没有任何读锁或写锁被持有时,写锁才能被一个线程获取。一旦写锁被持有,所有后续的读锁和写锁请求都将被阻塞,直到写锁被释放。这确保了在数据修改期间,数据的一致性和完整性。

2.2 ReadWriteLock 的优势

相较于传统的排他锁,ReadWriteLock在读多写少的场景下具有显著优势:

  • 提高并发性:允许多个读线程同时访问共享资源,充分利用多核处理器的能力,提高系统的吞吐量。
  • 避免写饥饿:虽然读锁可以并发获取,但ReadWriteLock的实现通常会确保写操作最终能够获得锁,避免写线程长时间等待而无法执行(尽管在某些非公平实现中,写线程仍可能面临饥饿问题)。
  • 简化编程模型:通过明确区分读写操作,使开发者能够更直观地设计并发访问逻辑,降低了并发编程的复杂性。

2.3 ReentrantReadWriteLock:Java内置实现

ReentrantReadWriteLockReadWriteLock接口的一个具体实现,它提供了可重入的读写锁功能。可重入性意味着,如果一个线程已经持有了读锁,它可以再次获取读锁;同样,如果一个线程持有了写锁,它可以再次获取写锁,并且在持有写锁的情况下,也可以获取读锁(锁降级)。

ReentrantReadWriteLock的内部实现基于AQS(AbstractQueuedSynchronizer)框架,通过一个int类型的状态变量来表示读锁和写锁的持有情况。状态变量的高16位用于表示读锁的计数,低16位用于表示写锁的计数。这种设计使得读写锁的获取和释放操作能够高效地进行。

特性:

  • 可重入性:读锁和写锁都支持可重入。一个线程在持有读锁的情况下可以再次获取读锁,持有写锁的情况下可以再次获取写锁。此外,持有写锁的线程可以获取读锁(锁降级),但持有读锁的线程不能直接获取写锁(锁升级,会导致死锁)。
  • 公平性选择ReentrantReadWriteLock支持公平(fair)和非公平(nonfair)两种模式。在公平模式下,等待时间最长的线程将优先获取锁;在非公平模式下,则允许插队,这通常能带来更高的吞吐量,但可能导致饥饿问题。
  • 锁降级:写锁可以降级为读锁。即一个线程在持有写锁的情况下,可以先获取读锁,然后释放写锁。这在更新数据后需要读取最新数据的场景中非常有用,可以避免在读写切换过程中释放所有锁导致其他线程修改数据。

3. Redisson 分布式读写锁(RReadWriteLock)

在分布式系统中,单机ReadWriteLock无法满足跨JVM进程的并发控制需求。Redisson作为一款基于Redis的Java驻内存数据网格(In-Memory Data Grid)和分布式对象框架,提供了RReadWriteLock来实现分布式环境下的读写锁。RReadWriteLock实现了java.util.concurrent.locks.ReadWriteLock接口,因此其API与单机版保持一致,使得从单机到分布式的迁移变得平滑。

3.1 Redisson RReadWriteLock 的实现原理

Redisson的RReadWriteLock底层基于Redis的原子操作和发布/订阅机制实现。其核心原理如下:

  • 写锁:当一个客户端尝试获取写锁时,Redisson会在Redis中设置一个带有过期时间的键(通常是一个哈希表),表示写锁被持有。如果该键已经存在,则表示写锁已被其他客户端持有,当前客户端将进入等待队列。为了保证原子性,写锁的获取和释放通常通过Lua脚本在Redis服务器端执行。
  • 读锁:当一个客户端尝试获取读锁时,Redisson会在Redis中记录读锁的持有者信息(通常也是一个哈希表中的字段)。多个客户端可以同时记录读锁信息。当写锁被持有或有写锁在等待时,读锁的获取将被阻塞。读锁的释放同样通过Lua脚本实现。
  • 锁重入与锁降级:Redisson通过在Redis中维护每个客户端的锁重入计数来实现可重入性。锁降级(写锁降级为读锁)也通过原子操作实现,确保在降级过程中不会出现数据不一致。
  • 看门狗机制:为了防止客户端崩溃导致锁无法释放,Redisson提供了看门狗(Watchdog)机制。当客户端成功获取锁后,Redisson会启动一个定时任务,定期延长锁的过期时间,直到锁被释放。如果客户端崩溃,看门狗任务停止,锁会在过期时间后自动释放。

3.2 Redisson RReadWriteLock 的优势

  • 分布式支持:解决了传统ReadWriteLock无法在分布式环境下使用的限制,实现了跨JVM进程的并发控制。
  • 高可用性:基于Redis集群或主从复制,Redisson的读写锁具有较高的可用性。
  • 性能优化:通过Redis的内存操作和原子性,以及Redisson的优化,提供了高性能的分布式锁服务。
  • API兼容性:实现了java.util.concurrent.locks.ReadWriteLock接口,降低了学习成本和迁移成本。

4. 代码案例:ReentrantReadWriteLock 与 Redisson RReadWriteLock

为了更好地理解读写锁的使用,我们将分别展示ReentrantReadWriteLockRReadWriteLock的代码示例。假设我们有一个简单的计数器,需要支持并发读写。

4.1 单机环境:使用 ReentrantReadWriteLock

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class CounterWithReadWriteLock {private final ReadWriteLock rwLock = new ReentrantReadWriteLock();private int count = 0;public int getCount() {rwLock.readLock().lock(); // 获取读锁try {System.out.println(Thread.currentThread().getName() + " 正在读取,当前值为: " + count);// 模拟读取耗时操作Thread.sleep(50);return count;} catch (InterruptedException e) {Thread.currentThread().interrupt();return -1;} finally {rwLock.readLock().unlock(); // 释放读锁}}public void increment() {rwLock.writeLock().lock(); // 获取写锁try {int oldValue = count;count++;System.out.println(Thread.currentThread().getName() + " 正在写入,值从 " + oldValue + " 变为: " + count);// 模拟写入耗时操作Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {rwLock.writeLock().unlock(); // 释放写锁}}public static void main(String[] args) {CounterWithReadWriteLock counter = new CounterWithReadWriteLock();// 创建多个读线程for (int i = 0; i < 5; i++) {new Thread(() -> {for (int j = 0; j < 3; j++) {counter.getCount();}}, "Reader-" + i).start();}// 创建一个写线程new Thread(() -> {for (int i = 0; i < 2; i++) {counter.increment();}}, "Writer-0").start();// 创建另一个写线程new Thread(() -> {for (int i = 0; i < 2; i++) {counter.increment();}}, "Writer-1").start();}
}

代码说明:

  • rwLock.readLock().lock():获取读锁。多个读线程可以同时进入getCount()方法。
  • rwLock.writeLock().lock():获取写锁。当写线程进入increment()方法时,所有读线程和写线程都将被阻塞。
  • finally块确保锁的正确释放,避免死锁。

运行上述代码,您会观察到多个“Reader”线程可以同时打印读取信息,而“Writer”线程在写入时会阻塞其他所有读写操作,体现了读写锁的特性。

4.2 分布式环境:使用 Redisson RReadWriteLock

首先,确保您的项目中已引入Redisson的Maven依赖:

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.27.1</version> <!-- 使用最新稳定版本 -->
</dependency>

然后,是使用RReadWriteLock的示例代码:

import org.redisson.Redisson;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;public class DistributedCounterWithRedissonReadWriteLock {private static RedissonClient redissonClient;private static final String LOCK_KEY = "myDistributedCounterLock";private static int count = 0; // 模拟共享资源,实际生产中应存储在Redis或其他共享存储中public static void initRedisson() {Config config = new Config();// 单机模式,根据实际Redis部署情况配置config.useSingleServer().setAddress("redis://127.0.0.1:6379");// 如果Redis有密码,可以设置:.setPassword("your_password");redissonClient = Redisson.create(config);System.out.println("Redisson客户端初始化成功。");}public static void shutdownRedisson() {if (redissonClient != null) {redissonClient.shutdown();System.out.println("Redisson客户端已关闭。");}}public static int getCount() {RReadWriteLock rwLock = redissonClient.getReadWriteLock(LOCK_KEY);rwLock.readLock().lock(); // 获取读锁try {System.out.println(Thread.currentThread().getName() + " 正在读取,当前值为: " + count);// 模拟读取耗时操作Thread.sleep(50);return count;} catch (InterruptedException e) {Thread.currentThread().interrupt();return -1;} finally {rwLock.readLock().unlock(); // 释放读锁}}public static void increment() {RReadWriteLock rwLock = redissonClient.getReadWriteLock(LOCK_KEY);rwLock.writeLock().lock(); // 获取写锁try {int oldValue = count;count++;System.out.println(Thread.currentThread().getName() + " 正在写入,值从 " + oldValue + " 变为: " + count);// 模拟写入耗时操作Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {rwLock.writeLock().unlock(); // 释放写锁}}public static void main(String[] args) throws InterruptedException {initRedisson();// 模拟多个JVM进程或多个线程并发访问// 为了演示方便,这里在一个JVM中创建多个线程// 实际分布式场景中,这些线程可能运行在不同的服务器上// 创建多个读线程for (int i = 0; i < 5; i++) {new Thread(() -> {for (int j = 0; j < 3; j++) {getCount();}}, "Distributed-Reader-" + i).start();}// 创建一个写线程new Thread(() -> {for (int i = 0; i < 2; i++) {increment();}}, "Distributed-Writer-0").start();// 创建另一个写线程new Thread(() -> {for (int i = 0; i < 2; i++) {increment();}}, "Distributed-Writer-1").start();// 等待所有线程执行完毕Thread.sleep(5000); // 适当等待,确保所有线程有机会执行shutdownRedisson();}
}

代码说明:

  • initRedisson():初始化Redisson客户端,连接到Redis服务器。请根据您的Redis部署情况修改setAddress
  • redissonClient.getReadWriteLock(LOCK_KEY):通过一个唯一的键名获取分布式读写锁实例。
  • rwLock.readLock().lock()rwLock.writeLock().lock():与单机版API完全一致,Redisson在底层处理了分布式同步的复杂性。
  • count变量在此示例中仍为JVM内部变量,但在实际分布式应用中,count的值应存储在Redis或其他共享存储中,并通过Redisson的分布式对象(如RAtomicLong)进行操作,以确保真正的分布式一致性。

5. 最佳实践与注意事项

  • 选择合适的锁:读写锁并非适用于所有场景。在写操作频繁的场景下,读写锁的开销可能大于其带来的收益,此时传统的排他锁或更细粒度的锁可能更合适。
  • 最小化锁的范围:无论是读锁还是写锁,都应尽可能地缩小其作用范围,只在真正需要保护共享资源的代码块中加锁,以减少锁的持有时间,提高并发性。
  • 避免锁升级:在持有读锁的情况下尝试获取写锁会导致死锁。如果需要从读模式切换到写模式,应先释放读锁,再获取写锁(这可能导致其他线程在中间修改数据),或者使用锁降级(写锁降级为读锁)的模式。
  • 处理中断:在获取锁时,应考虑处理线程中断异常(InterruptedException),以确保程序的健壮性。
  • Redisson配置:在分布式环境下使用Redisson时,正确配置Redisson客户端至关重要,包括Redis地址、密码、连接池大小等。对于生产环境,建议使用Redis集群或哨兵模式以保证高可用。
  • 共享资源同步:使用RedissonRReadWriteLock时,请记住它只解决了锁的同步问题,共享资源本身(如示例中的count)也需要存储在分布式存储中,并使用Redisson提供的分布式数据结构来保证其在不同节点间的一致性。

6. 总结

ReadWriteLock是Java并发编程中一个强大的工具,它通过区分读写操作,在读多写少的场景下显著提升了系统的并发性能。ReentrantReadWriteLock提供了单机环境下的高效实现,而Redisson的RReadWriteLock则将读写锁的能力扩展到了分布式系统,使得跨JVM进程的并发控制成为可能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/86109.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/86109.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/86109.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity Addressable使用之检测更新流程

补充知识 关键文件说明 Addressable打包后会生成多种文件&#xff0c;主要包括 .hash、.json 和 .bundle 文件&#xff0c;它们各自有不同的作用。 .hash 文件&#xff08;哈希文件&#xff09; 作用&#xff1a; 用于 版本对比&#xff0c;检查资源是否有更新。存储的是 资…

Elasticsearch 中实现推荐搜索(方案设想)

1. 存储商品数据的数据类型 为了支持推荐搜索&#xff0c;商品数据通常需要包含以下字段&#xff1a; 商品索引结构 PUT /products {"mappings": {"properties": {"product_id": {"type": "keyword" // 商品 ID},"…

Aerotech系列(4)Aerotech.A3200名空间

IconTypeDescriptionAxisMask Represents a selection of axes Controller Represents a controller Allows configuring and c

React Router 是怎么实现灵活导航的?

&#x1f399; 欢迎来到《前端达人 React播客书单》第 21 期。 视频版&#xff08;播客风格更精彩&#xff09; 今天我们不讲 Hook&#xff0c;来拆解前端开发中另一个高频组件&#xff1a;React Router 的进阶导航模式。 你可能用过 <Link> 或 <Route>&#xff0…

Modbus TCP转Profibus DP网关与JF - 600MT 称重变送器轻松实现数据互换

Modbus TCP转Profibus DP网关与JF - 600MT 称重变送器轻松实现数据互换 在工业自动化领域&#xff0c;不同设备之间的通信与数据交互至关重要。Modbus TCP转Profibus DP网关作为连接不同协议设备的关键桥梁&#xff0c;发挥着不可或缺的作用。本文将以JF - 600MT称重变送器与3…

聊聊 SQL 注入那些事儿

相信大家对于学校们糟糕的网络环境和运维手段都早有体会&#xff0c;在此就不多做吐槽了。今天我们来聊一聊SQL注入相关的内容。 何谓SQL注入&#xff1f; SQL注入是一种非常常见的数据库攻击手段&#xff0c;SQL注入漏洞也是网络世界中最普遍的漏洞之一。大家也许都听过某某学…

多传感器融合

目录 多传感器融合 多传感器融合的方向 传感器融合方案介绍 LOAM LIO-SAM LVI-SAM 多线激光雷达性质 什么是运动畸变 两步优化的帧间里程记 IMU 器件介绍及选型建议 IMU 标定方法简介 视觉里程计 VS 激光里程计 LVI-SAM 激光视觉融合思路简介 多传感器融合工程实践经验与技巧 多…

Auto-GPT vs ReAct:两种智能体思路对决

目录 Auto-GPT vs ReAct&#xff1a;两种智能体思路对决 &#x1f9e0; 一、智能体的演化背景 &#x1f9e9; 二、Auto-GPT&#xff1a;自循环的执行体 &#x1f50d; 三、ReAct&#xff1a;推理 行动的交错协同 ⚔️ 四、对比总结 &#x1f6e0; 五、你该选谁&#xff…

本地部署大模型性能测试,DeepSeek-R1-0528-Qwen-8B 依然是我的不二之选

大家好&#xff0c;我是 ai 学习的老章 介绍一个大模型并发性能测试工具 看一下我高频使用的&#xff0c;在2*4090显卡上部署的 DeepSeek-R1-0528-Qwen-8B 性能如何 _我_特别喜欢的三个DeepSeek版本 DeepSeek-R1-0528 蒸馏 Qwen3:8B 大模型&#xff0c;双 4090 本地部署&am…

华为云Flexus+DeepSeek征文|华为云 Dify 高可用部署教程:CCE 容器集群一键构建企业级智能应用

前言 在数字化转型加速的企业级应用场景中&#xff0c;构建高可用智能平台已成为业务创新的核心驱动力。本文深度解析基于华为云CCE容器服务的Dify智能应用部署实践&#xff0c;揭示如何通过云原生架构与AI技术的深度融合&#xff0c;实现企业知识管理、智能客服等场景的敏捷落…

Linux 多进程间通信(IPC)详解

在 Linux 系统中,多进程通信(Inter-Process Communication, IPC) 是实现多个进程之间数据交换和同步的重要机制。由于每个进程拥有独立的地址空间,因此需要借助特定的系统机制来实现信息共享。 📌 Linux 下常见的 6 种进程间通信方式 管道(Pipe)命名管道(FIFO)消息队…

服务器数据恢复——异常断电导致服务器故障的数据恢复案例

服务器数据恢复环境&#xff1a; 某服务器上有一组由12块硬盘组建的raid5磁盘阵列。 机房供电不稳定导致机房中该服务器非正常断电&#xff0c;重启服务器后管理员发现服务器无法正常使用。 意外断电可能会导致服务器上的raid模块损坏。 服务器数据恢复过程&#xff1a; 1、将故…

微信小程序中 rpx与px的区别

在微信小程序中的rpx比px方便的多 <!--pages/welcome/welcome.wxml--> <!--rpx替换px--> <image style"width:200rpx;height: 200rpx"src"/images/avatar/3.png"></image> <text>你好&#xff0c;冻梨</text> <but…

python3实现QQ官方机器人回调验证

考虑到第三方的机器人现在越来越难维持了&#xff0c;来捣鼓一下官方的机器人。虽然官方藏着掖着不肯开放很多功能&#xff0c;但起码能用。官方机器人的优点是稳定&#xff0c;只要申请成功&#xff0c;且你自己不乱搞&#xff0c;基本不存在被封的可能&#xff0c;缺点是藤子…

基于Vue3+TS的自定义指令开发与业务场景应用

文章目录 1. 前言2. 基础概念与优势​3. Vue3TS自定义指令的创建与注册​3.1. 创建自定义指令​3.2. 注册自定义指令​ 4. 实际场景示例​4.1. 权限指令控制​4.2. 图片懒加载指令​ 5. 优化与注意事项​ 1. 前言 在 Vue3 的开发生态中&#xff0c;自定义指令是一项极为灵活且…

Elasticsearch 索引文档的流程

Elasticsearch 索引文档的流程是一个分布式、多阶段的过程&#xff0c;涉及客户端请求、路由、主副本同步及持久化等步骤&#xff0c;具体流程如下&#xff1a; 一、客户端请求与路由 1.1 文档接收与路由计算‌ 客户端通过 REST API 发送文档写入请求&#xff0c;需指…

【unity】批量剔除图片四周空白像素的工具

摘要&#xff1a;Unity图片空白像素批量处理工具 该工具提供两种方式批量剔除图片空白像素&#xff1a; 静态处理类&#xff1a;提供TrimTexture方法&#xff0c;可读取纹理像素数据&#xff0c;计算非透明区域边界&#xff0c;生成裁剪后的新纹理&#xff1b;SaveTexture方法…

可编辑64页PPT | 基于DeepSeek的数据治理方案

荐言摘要&#xff1a;在数据量爆炸式增长且业务需求日益复杂的当下&#xff0c;企业数据治理面临着数据分散、标准混乱、价值挖掘难等诸多挑战。我们基于DeepSeek强大的智能能力&#xff0c;为企业量身打造创新数据治理方案。 DeepSeek凭借其卓越的自然语言处理和深度学习技术…

启用AWS VPC流日志保存到CloudWatch日志组

目标 启用VPC流日志 启用流日志 选择vpc&#xff0c;开始启用流日志&#xff0c;如下图&#xff1a; 设置名称和日志组&#xff0c;创建流日志&#xff0c;如下图&#xff1a; 参考 AWS云中的VPC启用流日志保存S3&#xff08;AWS中国云&#xff09;创建发布到 CloudWatc…

游戏引擎学习路径与技术栈指南

游戏引擎架构全景图&#xff08;基于GAMES104 V2.2思维导图&#xff09; graph TDA[基础架构] --> A1[面向数据管理]A --> A2[任务系统]A1 --> A11[ECS架构]A1 --> A12[内存优化]A2 --> A21[Job System]A2 --> A22[依赖调度]B[工具链] --> B1[编辑器框架]…