96SEO 2026-04-24 04:22 0
在Java并发编程的浩瀚海洋里ReentrantReadWriteLock无疑是一艘坚固的战舰。hen多朋友在面试或者实际开发中dou接触过它,但真正沉下心来去剖析它源码的人,恐怕就不多了。说实话,读源码是一件枯燥但又充满惊喜的事情,就像是在拆解一个精密的钟表,每一个齿轮的咬合dou让人叹为观止。今天我们就抛开那些晦涩的教条,用一种geng轻松、geng贴近实战的视角,把这个类的“五脏六腑”dou翻出来kankan。

我们要聊的不仅仅是代码,而是代码背后的设计哲学。为什么它要这么设计?它是如何在一个int变量里同时塞进读锁和写锁的状态的?又是如何保证公平性的?别急,我们慢慢来。
一、 整体架构:不仅仅是简单的锁我们得先搞清楚ReentrantReadWriteLock的家族谱系。这个类实现了ReadWriteLock接口,这意味着它必须遵守读写锁的规范:一把读锁,一把写锁。但是Ru果你点开它的源码,会发现它其实是一个“组合拳”大师。
它内部定义了五个核心的内部类,它们之间错综复杂却又井然有序:
Sync这是一个抽象类,继承自大名鼎鼎的AbstractQueuedSynchronizer。Ke以说它是整个锁机制的基石,所有的状态管理、队列排队dou依赖它。
FairSync和NonfairSync这两个是Sync的具体实现,分别代表了公平锁和非公平锁策略。
ReadLock和WriteLock这两个实现了Lock接口,分别对应我们日常使用的读锁和写锁,它们内部持有了Sync的引用。
kan到这里你可Neng会想:为什么要搞这么复杂?其实这种分层设计正是JDK源码的精髓所在把策略和具体实现分离开来维护起来才不会乱成一锅粥。
二、 状态的魔法:一个变量存两个锁接下来我们聊聊Zui让人头疼,也Zui让人着迷的部分:状态管理。在AQS中,通常用一个state变量来表示锁的状态。但是读写锁有两个锁啊?难道定义两个变量?
当然不是!JDK的大师们想出了一个绝妙的办法:位运算。他们把一个32位的整数state切成了两半。
具体来说高16位用来存“读锁”的持有数量,低16位用来存“写锁”的重入次数。这就像是在一个房间里划了两个区域,左边住着读锁,右边住着写锁,互不干扰。
源码里定义了几个常量来辅助这种切分:
// 高16位为读锁,低16位为写锁
static final int SHARED_SHIFT = 16;
// 读锁单位,也就是1左移16位
static final int SHARED_UNIT = ;
// 读锁Zui大数量
static final int MAX_COUNT = - 1;
// 写锁Zui大数量,也是低16位的掩码
static final int EXCLUSIVE_MASK = - 1;
那么我们怎么知道现在有几个读锁,几个写锁呢?这就用到了两个静态工具方法:
// 返回读锁的持有数量,直接无符号右移16位
static int sharedCount { return c>> SHARED_SHIFT; }
// 返回写锁的持有数量,直接与掩码Zuo与运算
static int exclusiveCount { return c & EXCLUSIVE_MASK; }
是不是hen巧妙?通过c & EXCLUSIVE_MASK,我们Ke以把高16位全部抹去,只留下低16位的写锁信息。这种设计不仅节省了内存,还保证了操作的原子性,毕竟CAS操作只Neng针对一个变量嘛。
写锁是独占锁,它的性格比较霸道:只要我拿到了锁,其他人dou别想进来;Ru果别人拿着锁,我也只Neng在门口等着。我们来kankan它的核心获取逻辑tryAcquire。
protected final boolean tryAcquire {
Thread current = Thread.currentThread;
int c = getState;
int w = exclusiveCount; // 拿到写锁的个数
// 1. Ru果状态不为0,说明锁被占用了
if {
//
// Ru果写锁个数为0,或者当前线程不是独占线程
// 这两种情况dou直接失败
if )
return false;
// 检查重入次数是否溢出
if > MAX_COUNT)
throw new Error;
// 重入获取,直接修改状态
setState;
return true;
}
// 2. Ru果状态为0,说明没人占锁
// 判断是否需要阻塞,然后尝试CAS抢锁
if ||
!compareAndSetState)
return false;
// 抢锁成功,设置独占线程
setExclusiveOwnerThread;
return true;
}
这段代码的逻辑非常清晰: 1. 有人占锁时Ru果是读锁占着,或者写锁占着但不是自己,直接走人;Ru果是自己占着,那就计数加1。 2. 没人占锁时kankan是不是公平策略。Ru果是非公平锁,直接冲上去CAS抢;Ru果是公平锁,得kankan队列里有没有排队的“老大哥”,有就得让着点。
释放写锁的逻辑tryRelease就简单多了就是减去重入次数,Ru果减完变成0了就把独占线程清空。
读锁是共享锁,它的性格比较随和:只要没有写锁在捣乱,大家douKe以一起读。但是读锁的计数逻辑比写锁复杂得多,因为它要记录每个线程到底重入了多少次。
这里引入了一个HoldCounter类,它专门用来记录某个线程的重入次数。为了性Neng优化,还引入了firstReader和cachedHoldCounter。
我们来kankantryAcquireShared的精妙之处:
protected final int tryAcquireShared {
Thread current = Thread.currentThread;
int c = getState;
// 1. Ru果写锁被占用,且占用者不是当前线程,直接失败
//
if != 0 &&
getExclusiveOwnerThread != current)
return -1;
int r = sharedCount; // 读锁数量
// 2. 检查是否需要阻塞,且读锁数未溢出,且CAS修改状态成功
if &&
r
这里有个细节特别有意思:HoldCounter里存的是tid而不是Thread对象。你知道为什么吗?Ru果存Thread引用,万一线程结束了这个引用还一直被锁持有,那线程对象就永远无法被垃圾回收了这可是典型的内存泄漏隐患!用tid就完美避开了这个问题。
我们在构造ReentrantReadWriteLock时Ke以传一个fair参数。
public ReentrantReadWriteLock {
sync = fair ? new FairSync : new NonfairSync;
readerLock = new ReadLock;
writerLock = new WriteLock;
}
Ru果是非公平锁,写锁writerShouldBlock直接返回false,意思是“不管有没有人排队,我先试试抢,抢不到再说”。读锁readerShouldBlock在非公平模式下为了防止写锁被“饿死”,通常会判断队列头节点是不是在等待写锁。
Ru果是公平锁,那就老实多了。writerShouldBlock会调用hasQueuedPredecessors,kankan队列里有没有比自己来得早的线程。
public final boolean hasQueuedPredecessors {
Node t = tail;
Node h = head;
Node s;
return h != t &&
== null || s.thread != Thread.currentThread);
}
这就是所谓的“先来后到”,虽然效率稍微低点,但大家dou心里平衡。
六、 实战演练:眼见为实说了这么多理论,我们来跑个代码kankan效果。下面这个例子模拟了两个读线程和一个写线程的竞争。
public class ReentrantReadWriteLockTest {
public static void main {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock;
ReadThread read1 = new ReadThread;
ReadThread read2 = new ReadThread;
WriteThread write1 = new WriteThread;
read1.start;
read2.start;
write1.start;
}
}
class WriteThread extends Thread {
private ReentrantReadWriteLock lock;
public WriteThread {
super;
this.lock = lock;
}
@Override
public void run {
System.out.println.getName + " start trying to lock");
try {
lock.writeLock.lock;
System.out.println.getName + " 获取写锁成功");
Thread.sleep; // 模拟耗时操作
} catch {
e.printStackTrace;
} finally {
lock.writeLock.unlock;
System.out.println.getName + " 释放写锁成功");
}
}
}
class ReadThread extends Thread {
private ReentrantReadWriteLock lock;
public ReadThread {
super;
this.lock = lock;
}
@Override
public void run {
System.out.println.getName + " trying to lock");
try {
lock.readLock.lock;
System.out.println.getName + " 获取读锁成功");
Thread.sleep;
} catch {
e.printStackTrace;
} finally {
lock.readLock.unlock;
System.out.println.getName + " 释放读锁成功");
}
}
}
从运行结果中,我们Ke以hen直观地kan到:read1和read2几乎同时获取到了读锁,它们是并行的。而write1必须等所有读锁dou释放完,才Neng拿到写锁。这就是读写锁Zui核心的魅力:读读不互斥,读写互斥,写写互斥。
解析ReentrantReadWriteLock的源码,不仅仅是学会怎么用这个类,geng重要的是学习Doug Lea大神的编码智慧。
比如为了性Neng,他使用了位运算压缩状态;为了减少ThreadLocal的查找开销,他设计了firstReader和cachedHoldCounter;为了防止GC问题,他使用了tid代替Thread引用。这些细节,平时可Neng不起眼,但每一个微小的优化douNeng带来巨大的收益。
技术这条路,没有捷径。多kan源码,多思考“为什么”,你会发现自己的技术视野会慢慢打开。希望这篇文章Neng帮你把ReentrantReadWriteLock这块硬骨头啃下来下次面试的时候,当面试官问起这个类,你Ke以自信地告诉他:“这玩意儿的源码,我熟!”
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback