ReadWriteLock源码阅读
简介
-
读写锁
同一个资源有读写操作,但是读操作要比写操作频繁(读多写少的情况)。保证数据一致性的话需要进行同步,那么需要对写、读操作互斥加锁,但是真实场景却是读不需要互斥、写需要进行互斥,就诞生了读写锁ReadWriteLock。
-
类结构
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing */ Lock writeLock(); }
-
实现类
关键实现类--ReentrantReadWriteLock
使用方法:
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); // 创建 -- 可以传true、false 实现公平锁和非公平锁 ReentrantReadWriteLock.ReadLock readLock = rwl.readLock(); // 读锁 ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock(); // 写锁
使用场景
上代码
class MyTest{
// 创建读写锁、进行初始化 -- 可以传true、false 实现公平锁和非公平锁 类似ReentrantLock
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true);
// ReadLock 实现的是共享锁
ReentrantReadWriteLock.ReadLock readLock = rwl.readLock();
// WriteLock 实现的是排他锁
ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock();
// 竞争资源
private Map<String,String> map = new HashMap<>();
/**
* 读操作加读锁
* 获取条件:
* 没有其他线程已获取写锁
* 没有写请求或有当前线程同时是写请求
*/
public String get(String key){
readLock.lock();
try{
return map.get(key);
}finally {
readLock.unlock();
}
}
/**
* 写操作加写锁
* 获取条件: 没有其他线程读、写
*/
public void set(String key , String value){
writeLock.lock();
try{
map.put(key,value);
}finally {
writeLock.unlock();
}
}
}
ReentrantReadWriteLock具有以下三个重要的特性:
- 公平、非公平选择性:构造传参为true、false实现公平非公平
- 可重入锁:同一线程可以多次获取,有一个count计数器
- 锁降级:获取写锁后可以再去获取读、然后释放写锁,完成锁降级
源码分析
众所周知,所有的锁都是通过AQS实现的,即state属性标记当前锁状态,Node队列为等待队列进行自旋等待或者Park中断等待唤醒;
state是如何标记读写锁的
从源码角度来进行分析state标志位如何标记读写锁
volatile int state; //state 为 int类型的整数 4个字节 32位
0000 0000 0000 0000 0000 0000 0000 0000
高16位代表读锁 | 低16位代表写锁
state = 0 代表锁空闲 写锁操作 +1 读锁操作 65535 + 1
// 高低16位、静态变量
static final int SHARED_SHIFT = 16;
// 上读锁需要在高16位操作,也就是需要添加一个17位的数
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 读、写锁的最大数量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// state & 这个数,能获取低16位(写锁)的数值
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 获取读锁 -- 右移动16位,高位补0 , 获取读锁状态
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 获取写锁 高16位补0, & EXCLUSIVE_MASK 即可
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
独占锁
获取锁
加锁过程分为两步,1:尝试获取锁,2:没获取到锁自选等待或者进入队列
- 写锁中尝试获取锁调用的是AQS中的tryAcquire(1)的方法,实现方法为ReentrantReadWriteLock.Synv.tryAcquire()
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
// 获取state属性值状态
int c = getState();
// 获取写锁被获取的次数
int w = exclusiveCount(c);
// state不为0 代表由读锁或者写锁
if (c != 0) {
// 写锁的获取次数为0 或 当前线程不等于独占线程 -- 都不可在获取锁
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 写锁获取的最大数量 则抛出异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 写锁写入成功
setState(c + acquires);
return true;
}
// state 如果为0 代表没有任何线程获取锁
// 判断是否有阻塞队列、然后通过CAS操作更新state数值
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 设置独占线程
setExclusiveOwnerThread(current);
return true;
}
-
未获取到锁的线程,会将自身自选等待或者加入等待,调用的是acquireQueued(final Node node, int arg) 方法
// 将当前线程封装为Node队列节点 private Node addWaiter(Node mode) { // Node节点创建 Node node = new Node(Thread.currentThread(), mode); // node添加为尾节点 Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; } // 自旋等待 final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } // shouldParkAfterFailedAcquire将前置节点设置为挂起 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // parkAndCheckInterrupt 当前线程挂起 interrupted = true; } } finally { if (failed) cancelAcquire(node); } } private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 前置节点状态 int ws = pred.waitStatus; // 前继节点完成资源的释放或者中断后,会通知当前节点的,回家等通知就好了,不用自旋频繁地来打听消息 if (ws == Node.SIGNAL) return true; /* 如果前继节点的ws值大于0,即为1,说明前继节点处于放弃状态(Cancelled) 那就继续往前遍历,直到当前节点的前继节点的ws值为0或负数 */ if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 将前继节点的ws值设置为Node.SIGNAL,以保证下次自旋时,shouldParkAfterFailedAcquire直接返回true compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
释放锁
释放锁相当于调用的是AQS中的release()方法,具体内部实现为ReentrantReadWriteLock.Sync.tryRelease()
public final boolean release(int arg) {
// arg = 1
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒后继节点
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// 解锁线程与当前线程校验
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// state 字段校验, 写锁是低16位,可以直接做运算
int nextc = getState() - releases;
// 如果写锁为0 返回true
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
// 设置state数值
setState(nextc);
return free;
}
共享锁
获取锁
读锁的获取调用的是AQS的acquireShared(),实现方式是ReentrantReadWriteLock.Syn.tryAcquireShared()方法
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 获取写锁的数量!=0 代表无线程写入 并且 记录上读锁的线程,如果没有读锁,返回null
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 获取读锁的数量
int r = sharedCount(c);
// 头节点是否共享
if (!readerShouldBlock() &&
r < MAX_COUNT &&
// CAS 搞16位+1
compareAndSetState(c, c + SHARED_UNIT)) {
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++;
}
// >0 代码执行
return 1;
}
// 自选的方式重新获取
return fullTryAcquireShared(current);
}
锁释放
共享锁释放为AQS中的releaseShared(), 具体实现为tryReleaseShared()
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 判断当前线程与读锁线程是否同一线程
if (firstReader == current) {
// 第一个获取读锁的线程释放锁
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;
}
// 读锁的释放是可以并发进行的,所以通过CAS来修改state属性值
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;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?