news 2026/5/2 14:08:45

深入浅出Java并发读写锁ReentrantReadWriteLock(读锁)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入浅出Java并发读写锁ReentrantReadWriteLock(读锁)

读锁详解

读锁的获取

看完了写锁,再来看看读锁,读锁不是独占式锁,即同一时刻该锁可以被多个读线程获取,也就是一种共享式锁。按照之前对 AQS 的介绍,实现共享式同步组件的同步语义需要通过重写 AQS 的 tryAcquireShared 方法和 tryReleaseShared 方法。读锁的获取实现方法为:

protected final int tryAcquireShared(int unused) { /* * Walkthrough: * 1. If write lock held by another thread, fail. * 2. Otherwise, this thread is eligible for * lock wrt state, so ask if it should block * because of queue policy. If not, try * to grant by CASing state and updating count. * Note that step does not check for reentrant * acquires, which is postponed to full version * to avoid having to check hold count in * the more typical non-reentrant case. * 3. If step 2 fails either because thread * apparently not eligible or CAS fails or count * saturated, chain to version with full retry loop. */ Thread current = Thread.currentThread(); int c = getState(); //1. 如果写锁已经被获取并且获取写锁的线程不是当前线程的话,当前 // 线程获取读锁失败返回-1 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; int r = sharedCount(c); if (!readerShouldBlock() && r < MAX_COUNT && //2. 当前线程获取读锁 compareAndSetState(c, c + SHARED_UNIT)) { //3. 下面的代码主要是新增的一些功能,比如getReadHoldCount()方法 //返回当前获取读锁的次数 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; } //4. 处理在第二步中CAS操作失败的自旋已经实现重入性 return fullTryAcquireShared(current); }

代码的逻辑请看注释,需要注意的是当写锁被其他线程获取后,读锁获取失败,否则获取成功,会利用 CAS 更新同步状态。

另外,当前同步状态需要加上 SHARED_UNIT((1 << SHARED_SHIFT),即 0x00010000)的原因,我们在上面也说过了,同步状态的高 16 位用来表示读锁被获取的次数。

如果 CAS 失败或者已经获取读锁的线程再次获取读锁时,是靠 fullTryAcquireShared 方法实现的。

读锁的释放

读锁释放的实现主要通过方法 tryReleaseShared,源码如下,主要逻辑请看注释:

protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); // 前面还是为了实现getReadHoldCount等新功能 if (firstReader == current) { // assert firstReaderHoldCount > 0; if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); int count = rh.count; if (count <= 1) { readHolds.remove(); if (count <= 0) throw unmatchedUnlockException(); } --rh.count; } for (;;) { int c = getState(); // 读锁释放 将同步状态减去读状态即可 int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) // Releasing the read lock has no effect on readers, // but it may allow waiting writers to proceed if // both read and write locks are now free. return nextc == 0; } }

锁降级

读写锁支持锁降级,遵循按照获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁,不支持锁升级,关于锁降级,下面的示例代码摘自 ReentrantWriteReadLock 源码:

void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { // Must release read lock before acquiring write lock rwl.readLock().unlock(); rwl.writeLock().lock(); try { // Recheck state because another thread might have // acquired write lock and changed state before we did. if (!cacheValid) { data = ... cacheValid = true; } // Downgrade by acquiring read lock before releasing write lock rwl.readLock().lock(); } finally { rwl.writeLock().unlock(); // Unlock write, still hold read } } try { use(data); } finally { rwl.readLock().unlock(); } }

这里的流程可以解释如下:

  • 获取读锁:首先尝试获取读锁来检查某个缓存是否有效。
  • 检查缓存:如果缓存无效,则需要释放读锁,因为在获取写锁之前必须释放读锁。
  • 获取写锁:获取写锁以便更新缓存。此时,可能还需要重新检查缓存状态,因为在释放读锁和获取写锁之间可能有其他线程修改了状态。
  • 更新缓存:如果确认缓存无效,更新缓存并将其标记为有效。
  • 写锁降级为读锁:在释放写锁之前,获取读锁,从而实现写锁到读锁的降级。这样,在释放写锁后,其他线程可以并发读取,但不能写入。
  • 使用数据:现在可以安全地使用缓存数据了。
  • 释放读锁:完成操作后释放读锁。

这个流程结合了读锁和写锁的优点,确保了数据的一致性和可用性,同时允许在可能的情况下进行并发读取。使用读写锁的代码可能看起来比使用简单的互斥锁更复杂,但它提供了更精细的并发控制,可能会提高多线程应用程序的性能。

使用读写锁

ReentrantReadWriteLock 的使用非常简单,下面的代码展示了如何使用 ReentrantReadWriteLock 来实现一个线程安全的计数器:

public class Counter { private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); private int count = 0; public int getCount() { r.lock(); try { return count; } finally { r.unlock(); } } public void inc() { w.lock(); try { count++; } finally { w.unlock(); } } }

当缓存无效时,会先释放读锁,然后获取写锁来更新缓存。一旦缓存被更新,就会进行写锁到读锁的降级,允许其他线程并发读取,但仍然排除写入。

这样的结构允许在确保数据一致性的同时,实现并发读取的优势,从而提高多线程环境下的性能。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/29 21:35:48

springboot家教管理系统的设计与实现—开题报告

目录 研究背景与意义系统目标技术选型功能模块设计创新点预期成果进度计划 项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作 研究背景与意义 随着在线教育需求增长&#xff0c;家教行业亟需数字化管理工具…

作者头像 李华
网站建设 2026/5/2 4:11:21

SEW变频器MCV40A0750-503-4-00 08269211

SEW-EURODRIVE MCV40A0750-503-4-00 (08269211) 变频器详细介绍1. 产品概述MCV40A0750-503-4-00 是 SEW-EURODRIVE 公司生产的 MOVIFIT MC 系列中的一款紧凑型、高性能变频器。该系列变频器以其模块化设计、强大的功能和灵活性而著称&#xff0c;广泛应用于各种工业自动化领域&…

作者头像 李华
网站建设 2026/4/23 15:20:28

SpringMVC中百M大文件上传如何分块处理?

内蒙古金融行业银行单位大文件传输解决方案 作为内蒙古金融行业某上市公司项目负责人&#xff0c;针对集团提出的大文件传输系统需求&#xff0c;本人经过详细调研与技术评估&#xff0c;现提供一套完整的解决方案&#xff0c;确保满足客户对超大文件传输、断点续传、数据安全…

作者头像 李华
网站建设 2026/4/28 10:05:45

Canvas兼容IE全攻略:各版本支持与应对方法

在企业级Web应用开发中&#xff0c;Canvas技术的兼容性问题是必须跨越的一道门槛。许多大型企业或机构内部仍在使用旧版Internet Explorer浏览器&#xff0c;因此&#xff0c;清晰了解Canvas在IE中的支持情况&#xff0c;并制定可行的应对策略&#xff0c;对于保障应用功能的普…

作者头像 李华
网站建设 2026/4/27 8:52:27

期货套保系统自动移仓流程设计与实现

移仓换月是期货套保业务中高频且关键的操作环节。传统人工移仓方式面临时点把握困难、操作繁琐、执行成本不可控等问题。本文将深入介绍期货套保系统中的自动移仓流程设计&#xff0c;帮助产业企业实现移仓操作的标准化与自动化。 一、移仓换月的业务背景与痛点 期货合约具有…

作者头像 李华
网站建设 2026/4/27 10:54:28

原圈科技领航:2026房企AI市场分析工具榜单,不懂将被淘汰

在房企AI市场分析领域&#xff0c;原圈科技的技术能力、行业适配度与服务稳定性使其被普遍视为领先的解决方案提供商。本文深度解析2026年房企必备的四大AI核心能力&#xff0c;剖析AI如何重塑营销生产力&#xff0c;并展示以原圈科技为代表的智能体矩阵应用如何实现从市场洞察…

作者头像 李华