Java并发编程:读写锁与普通互斥锁的深度对比

在Java并发编程中,锁是实现线程安全的重要工具。其中,普通互斥锁(如synchronizedReentrantLock)和读写锁(ReentrantReadWriteLock)是两种常用的同步机制。本文将从多个维度深入分析它们的区别、适用场景及性能差异,并通过示例代码展示如何在实际项目中合理选择。

一、核心概念对比

1. 普通互斥锁(Mutex)

普通互斥锁是最基本的同步机制,它遵循"排他性"原则:

  • 同一时间仅允许一个线程访问共享资源,无论该线程是读操作还是写操作。
  • 典型实现:
    • synchronized关键字
    • ReentrantLock

示例代码

private final Lock mutex = new ReentrantLock();
private List<String> sharedList = new ArrayList<>();public void write(String data) {mutex.lock();try {sharedList.add(data);} finally {mutex.unlock();}
}public String read(int index) {mutex.lock();try {return sharedList.get(index);} finally {mutex.unlock();}
}

2. 读写锁(ReadWriteLock)

读写锁将锁分为"读锁"和"写锁",并提供更细粒度的访问控制:

  • 读锁(共享锁):允许多个线程同时获取读锁,并发读取共享资源。
  • 写锁(排他锁):同一时间仅允许一个线程获取写锁,且写锁存在时不允许任何线程获取读锁。
  • 典型实现:ReentrantReadWriteLock

示例代码

private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private List<String> sharedList = new ArrayList<>();public void write(String data) {writeLock.lock();try {sharedList.add(data);} finally {writeLock.unlock();}
}public String read(int index) {readLock.lock();try {return sharedList.get(index);} finally {readLock.unlock();}
}

二、关键区别详解

1. 锁的粒度与并发度

维度普通互斥锁读写锁
锁粒度粗粒度(不区分读写)细粒度(区分读写)
并发度同一时间仅一个线程访问同一时间可多个线程读或一个线程写
吞吐量低(尤其读多写少场景)高(读多写少场景显著提升)

2. 适用场景对比

场景普通互斥锁读写锁
读写操作频率接近✅ 简单高效❌ 状态管理开销可能更高
读操作远多于写操作❌ 吞吐量瓶颈✅ 并发读性能显著提升
写操作占主导✅ 实现简单❌ 需处理写锁饥饿问题
需保证强一致性✅ 读写均互斥❌ 写锁释放前可能有读线程

3. 饥饿问题

  • 普通互斥锁:公平模式下较少出现饥饿,但非公平模式可能导致某些线程长时间无法获取锁。
  • 读写锁:默认非公平模式下,写锁可能因读锁持续被获取而长时间等待(写锁饥饿)。

解决方案

// 创建公平读写锁,按请求顺序分配锁
private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);

三、性能对比测试

1. 测试环境

  • 硬件:Intel i7-8700K CPU @ 3.70GHz,16GB RAM
  • JDK:Java 17
  • 测试工具:JMH
  • 测试场景:模拟100线程并发访问,读:写比例分别为9:1、5:5、1:9

2. 测试结果

读:写比例普通互斥锁吞吐量(ops/sec)读写锁吞吐量(ops/sec)性能提升
9:154,231187,629~246%
5:582,14595,312~16%
1:978,32162,419-20%

3. 结果分析

  • 读多写少场景:读写锁通过允许多线程并发读,显著提升吞吐量。
  • 读写均衡场景:读写锁的性能优势减弱,因其状态管理开销高于普通互斥锁。
  • 写多场景:读写锁的性能甚至低于普通互斥锁,因此时写锁的排他性导致锁竞争加剧。

四、读写锁的进阶特性

1. 锁降级(Write→Read)

写锁可降级为读锁,保证数据可见性:

public void upgradeExample() {writeLock.lock();try {// 写操作...// 降级为读锁readLock.lock();try {// 释放写锁,但仍持有读锁writeLock.unlock();// 执行读操作...} finally {readLock.unlock();}} finally {if (writeLock.isHeldByCurrentThread()) {writeLock.unlock();}}
}

2. 锁升级(Read→Write)

不推荐直接升级读锁为写锁,可能导致死锁:

public void wrongUpgrade() {readLock.lock();try {// 错误示例:不可直接升级读锁为写锁// 会导致死锁(需先释放读锁)writeLock.lock(); try {// ...} finally {writeLock.unlock();}} finally {readLock.unlock();}
}

五、最佳实践建议

1. 选择策略

  • 优先考虑读写锁:当读操作占比超过70%时,读写锁通常能带来显著性能提升。
  • 谨慎使用公平模式:公平模式会降低吞吐量,仅在需严格避免饥饿时使用。
  • 避免锁升级:如需同时读写,建议先获取写锁,再降级为读锁。

2. 性能优化

  • 分段锁:对大型数据结构分区加锁(如ConcurrentHashMap的实现)。
  • 读写分离:将读操作和写操作分发到不同的服务实例。
  • 异步写回:对写操作性能敏感的场景,可将写操作异步化(如写入队列后立即返回)。

六、总结

普通互斥锁和读写锁各有其适用场景,合理选择能显著提升系统性能:

场景推荐锁类型关键理由
缓存系统(读多写少)ReentrantReadWriteLock并发读性能提升明显
计数器更新(写操作频繁)ReentrantLock读写锁状态管理开销反而降低性能
强一致性要求的金融系统synchronized/ReentrantLock避免读写锁的并发读带来的一致性问题
配置中心(读操作占绝对主导)StampedLock(乐观读)进一步提升无竞争读的性能

在实际开发中,建议通过JMH等工具进行性能基准测试,验证锁选择的合理性。同时,注意监控锁竞争情况(如通过JVM工具查看锁等待时间),及时调整锁策略。

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

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

相关文章

C#面向对象实践项目--贪吃蛇

目录 一、项目整体架构与核心逻辑 二、关键类的功能与关系 1. 游戏核心管理类&#xff1a;Game 2. 场景接口与基类 3. 具体场景类 4. 游戏元素类 5. 基础结构体与接口 三.类图 四、核心流程解析 五、项目可优化部分 一、项目整体架构与核心逻辑 该项目运用场景管理模…

传输层协议:网络通信的关键纽带

在计算机网络的复杂体系中&#xff0c;传输层协议扮演着举足轻重的角色&#xff0c;它如同桥梁一般&#xff0c;连接着应用层与网络层&#xff0c;为不同主机上的应用进程提供端到端的通信服务&#xff0c;确保数据能够准确、高效地在网络中传输。深入理解传输层协议&#xff0…

高效易用的 MAC 版 SVN 客户端:macSvn 使用体验

高效易用的 MAC 版 SVN 客户端&#xff1a;macSvn 使用体验 下载安装使用总结 最近有个项目要使用svn, 但是mac缺乏一款像 Windows 平台 TortoiseSVN 那样全面、高效且便捷的 SVN 客户端工具, 直到博主找到了该工具本文将结合实际使用体验&#xff0c;详细介绍 macSvn工具的核心…

LeetCode 热题 100 394. 字符串解码

LeetCode 热题 100 | 394. 字符串解码 大家好&#xff01;今天我们来探讨一道非常有趣的算法题目——LeetCode 394. 字符串解码。这道题考察了我们对栈这种数据结构的理解和应用能力&#xff0c;同时也涉及到了字符串的处理技巧。接下来&#xff0c;我将详细地为大家解析这道题…

详解一下RabbitMQ中的channel.Publish

函数定义&#xff08;来自 github.com/streadway/amqp&#xff09; func (ch *Channel) Publish(exchange string,key string,mandatory bool,immediate bool,msg Publishing, ) error这个方法的作用是&#xff1a;向指定的交换机 exchange 发送一条消息 msg&#xff0c;带上路…

docker使用sh脚本创建容器,保持容器正常运行,异常关闭后马上重启

docker run -d --name dadeName \--memory5120m \-p 40060:80 \-p 40061:3306 \-v "$data:$dockerData" \-v "$img:$dockerImg" \--restartalways \ # 关键参数&#xff1a;总是重启dade:120 \/bin/bash -c "/www/start.sh && tail -f /dev/…

3516cv610在sample_aiisp上多创一路编码流,方法

3516cv610在sample_aiisp上多创一路编码流&#xff0c;方法 首先确保 vpss grp0有视频流 最好保证 已经有一路视频流能推出来 多创一路编码流思路为 将 vpss grp0又绑定给 vpss_chn1 vpss_chn1有绑定给 venc_chn1 这样我们就多创了一路视频流。 这里思路完全正确 可以实现…

Leetcode 3566. Partition Array into Two Equal Product Subsets

Leetcode 3566. Partition Array into Two Equal Product Subsets 1. 解题思路2. 代码实现 题目链接&#xff1a;3566. Partition Array into Two Equal Product Subsets 1. 解题思路 这一题我的实现还是比较暴力的&#xff0c;首先显而易见的&#xff0c;若要满足题目要求&…

waitpid的waitstatus 含义源码解读

当我们在调用pid_t waitpid(pid_t pid, int *stat_loc, int options)时&#xff0c;其中第二个参数stat_loc会提供子进程退出的详细信息&#xff0c;为此posix还提供了一组宏来解析这个status. 在\glibc\bits\waitstatus.h /* If WIFEXITED(STATUS), the low-order 8 bits of …

MATLAB实战:传染病模型仿真实现

以下是一个使用MATLAB实现传染病模型&#xff08;SIR和SEIR&#xff09;仿真的完整解决方案&#xff0c;包含参数分析和干预措施模拟&#xff1a; %% 传染病模型仿真工具箱 % 包含SIR、SEIR模型&#xff0c;支持参数调整和干预措施模拟 % 使用ode45求解微分方程 function epi…

系统架构设计师(一):计算机系统基础知识

系统架构设计师&#xff08;一&#xff09;&#xff1a;计算机系统基础知识 引言计算机系统概述计算机硬件处理器处理器指令集常见处理器 存储器总线总线性能指标总线分类按照总线在计算机中所处的位置划分按照连接方式分类按照功能分类 接口接口分类 计算机软件文件系统文件类…

汽车安全:功能安全FuSa、预期功能安全SOTIF与网络安全Cybersecurity 解析

汽车安全的三重防线&#xff1a;深入解析FuSa、SOTIF与网络安全技术 现代汽车已成为装有数千个传感器的移动计算机&#xff0c;安全挑战比传统车辆复杂百倍。 随着汽车智能化、网联化飞速发展&#xff0c;汽车电子电气架构已从简单的分布式控制系统演变为复杂的移动计算平台。现…