JUC源码分析-读写锁-ReentrantReadWriteLock

概述

ReentrantReadWriteLock 有2个锁,读锁和写锁。读读不加锁,读写,写读,写写都加锁。

读锁是共享锁,写锁是排他锁。 读锁和写锁的状态值被保存在int的值中,前16位表示 共享读锁状态值,后16位表示排他写锁状态值。

数据结构和核心参数

分析一下有关的属性

static final int SHARED_SHIFT = 16;//共享锁占据16位

static final int SHARED_UNIT = (1 << SHARED_SHIFT);//65536 1个共享锁状态值的单位,有1个共享锁就加上这个值。

static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;//65535 写锁最大值

static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//65535 排他锁状态值掩码

 

/** Returns the number of shared holds represented in count */

static int sharedCount(int c) { return c >>> SHARED_SHIFT; }//获取共享锁状态值

/** Returns the number of exclusive holds represented in count */

static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }//获取互斥锁状态值

类结构分析

 

ReentrantReadWriteLock依赖 WriteLock和ReadLock字段实现,ReadLock 和WriteLock依赖Sync字段实现,ReadLock,WriteLock,Sync都是ReentrantReadWriteLock的内部类,Sync继承了AQS。

源码分析

读锁加锁

public void lock() {

sync.acquireShared(1);

}

public final void acquireShared(int arg) {

if (tryAcquireShared(arg) < 0)//子类实现

doAcquireShared(arg);//AQS 方法

}

ReentrantReadWriteLock.sync- tryAcquireShared

protected final int tryAcquireShared(int unused) {

Thread current = Thread.currentThread();

int c = getState();

if (exclusiveCount(c) != 0 &&//如果 写锁数不等于0 且写锁当前占用线程不等于当前线程,返回-1 ,获取读锁失败。(不等于当前线程,不能加锁)

getExclusiveOwnerThread() != current)

return -1;

int r = sharedCount(c);//获取读锁数

if (!readerShouldBlock() &&//根据锁等待队列的情况判断此次读是否需要加锁

r < MAX_COUNT &&//读锁数小于最大值

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 != current.getId())

cachedHoldCounter = rh = readHolds.get();

else if (rh.count == 0)

readHolds.set(rh);

rh.count++;

}

return 1;//获取锁成功

}//readerShouldBlock()返回了true ,或加读单位失败 尝试获取锁

return fullTryAcquireShared(current);

}static final class NonfairSync extends Sync {

private static final long serialVersionUID = -8159625535654395037L;

final boolean writerShouldBlock() {

return false; // writers can always barge

}

 

final boolean readerShouldBlock() {

return apparentlyFirstQueuedIsExclusive();

}

}

//等待锁队列中有节点用,并且第一个节点是互斥模式

final boolean apparentlyFirstQueuedIsExclusive() {

Node h, s;

return (h = head) != null &&

(s = h.next) != null &&

!s.isShared() &&

s.thread != null;

}

 

/**

完整版本的读锁获取, 在tryAcquireShared中没有处理CAS 错过和重入读锁(线程相同 尝试加读锁)

*/

final int fullTryAcquireShared(Thread current) {

 

HoldCounter rh = null;

for (;;) {

int c = getState();//获取状态值

if (exclusiveCount(c) != 0) {//写锁数不等于0

if (getExclusiveOwnerThread() != current)

return -1;//线程不同,获取锁失败。

// else we hold the exclusive lock; blocking here

// would cause deadlock.

//线程相同的情况,应该尝试获取读锁。

} else if (readerShouldBlock()) {//根据锁队列判断是需要等待。

// Make sure we're not acquiring read lock reentrantly

if (firstReader == current) {//线程相同,要去获取锁

// assert firstReaderHoldCount > 0;

} else {

if (rh == null) {//获取并初始化锁记录

rh = cachedHoldCounter;

if (rh == null || rh.tid != current.getId()) {

rh = readHolds.get();

if (rh.count == 0)

readHolds.remove();

}

}

if (rh.count == 0)//当前线程没获取过锁 ,不可以重入

return -1;

}

}

if (sharedCount(c) == MAX_COUNT)//超过最大限制抛出异常

throw new Error("Maximum lock count exceeded");

if (compareAndSetState(c, c + SHARED_UNIT)) {//尝试加读锁

//记录锁次数

if (sharedCount(c) == 0) {

firstReader = current;

firstReaderHoldCount = 1;

} else if (firstReader == current) {

firstReaderHoldCount++;

} else {

if (rh == null)

rh = cachedHoldCounter;

if (rh == null || rh.tid != current.getId())

rh = readHolds.get();

else if (rh.count == 0)

readHolds.set(rh);

rh.count++;

cachedHoldCounter = rh; // cache for release

}

return 1;//成功获取锁

}

}

}

获取读锁逻辑总结

  1. 如果写锁被其他线程持有,失败
  2. 尝试获取读锁,根据锁等待队列策略不需要读锁阻塞,尝试CAS修改读锁状态值获取锁。
  3. 完整版的获取读锁,处理CAS失败和锁重入,先检查写锁数,根据队列策略是否需要阻塞,然后 尝试CAS修改读锁状态值获取锁。

 

读锁释放

protected final boolean tryReleaseShared(int unused) {

Thread current = Thread.currentThread();

//清空当前线程记录,锁数量减1

if (firstReader == current) {

// assert firstReaderHoldCount > 0;

if (firstReaderHoldCount == 1)

firstReader = null;

else

firstReaderHoldCount--;

} else {

HoldCounter rh = cachedHoldCounter;

if (rh == null || rh.tid != current.getId())

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;

}

}

逻辑总结:

清空当前线程记录,锁数量减1

重试 直到成功减去一个单位的读锁,如果修改后的读锁数为0返回true,释放锁。

 

 

写锁加锁

ReentrantReadWriteLock$NonfairSync(ReentrantReadWriteLock$Sync).tryAcquire(int)

protected final boolean tryAcquire(int acquires) {

思路概览:

  1. 如果读锁非0,或写锁非0且占用者非当前线程,失败
  2. 如果写锁数量饱和,失败
  3. 否则 如果是重入获取或队列策略允许,修改状态并保存 线程。成功

Thread current = Thread.currentThread();

int c = getState();

int w = exclusiveCount(c);

if (c != 0) {

// (Note: if c != 0 and w == 0 then shared count != 0) 如果 c != 0 and w == 0 这时 shared count != 0

if (w == 0 || current != getExclusiveOwnerThread())如果 读锁数为0,或写锁不为0且写锁的线程不是当前线程 获取写锁失败

return false;

if (w + exclusiveCount(acquires) > MAX_COUNT)//校验

throw new Error("Maximum lock count exceeded");

// Reentrant acquire

setState(c + acquires);//写锁数为0或写锁的线程是当前线程。修改状态值,获取写锁成功

return true;

}

if (writerShouldBlock() || 判断是否应该阻塞 默认的非公平锁返回false

!compareAndSetState(c, c + acquires)//尝试加写锁

return false;

setExclusiveOwnerThread(current);//加写锁成功,记录线程。

return true;

}

writerShouldBlock()

非公平锁模式返回false,不根据队列排队,直接争抢。

公平锁 检查队列中是否有合格的节点,有则返回false.

 

static final class FairSync extends Sync {

private static final long serialVersionUID = -2274990926593161451L;

final boolean writerShouldBlock() {

return hasQueuedPredecessors();

}

final boolean readerShouldBlock() {

return hasQueuedPredecessors();

}

}

 

队列中有节点要入队或 首节点的工作线程不是当前线程返回true

public final boolean hasQueuedPredecessors() {

// The correctness of this depends on head being initialized

// before tail and on head.next being accurate if the current

// thread is first in queue.

Node t = tail; // Read fields in reverse initialization order

Node h = head;

Node s;

return h != t &&

((s = h.next) == null || s.thread != Thread.currentThread());

//(s = h.next) == null 表示刚初始化完成队列头尾,即将入队。

}

获取写锁逻辑分析:

  1. 如果读锁非0,或写锁非0且占用者非当前线程,失败
  2. 如果写锁数量饱和,失败
  3. 否则 如果是重入获取或队列策略允许,修改状态并保存 线程。成功

写锁释放

ReentrantReadWriteLock$NonfairSync(AbstractQueuedSynchronizer).release(int)

//CAS互斥锁释放模板,重点关注tryRelease由写锁类实现

public final boolean release(int arg) {

if (tryRelease(arg)) {

Node h = head;

if (h != null && h.waitStatus != 0)

unparkSuccessor(h);

return true;

}

return false;

}

ReentrantReadWriteLock$NonfairSync(ReentrantReadWriteLock$Sync).tryRelease(int)

protected final boolean tryRelease(int releases) {

if (!isHeldExclusively())//判断当前线程是否持有锁

throw new IllegalMonitorStateException();

int nextc = getState() - releases;//计算 要修改的状态值

boolean free = exclusiveCount(nextc) == 0;

if (free)//如果写锁数等于0,销毁线程记录

setExclusiveOwnerThread(null);

setState(nextc);//修改状态值

return free;//根据 写锁数是否等于0 返回true或false.

}

逻辑分析:

  1. 异常判断,如果要释放的写锁不占用当前线程,抛出异常
  2. 计算并修改状态值
  3. 如果写锁数等于0,销毁线程记录
  4. 根据 写锁数是否等于0 返回true或false.

重入锁规则:获取读锁和写锁 在有写锁的情况下,相同线程也可以获取锁。

总结:ReentrantReadWriteLock依赖 WriteLock和ReadLock字段实现,WriteLock和ReadLock依赖ReentrantReadWriteLock.Sync实现, ReentrantReadWriteLock.Sync继承了 AQS。读锁和写锁公用一个int类型的状态值,前16位表示共享锁即读锁,后16位表示互斥锁即写锁。

读写锁的获取有相似步骤 

  1. 如果存在互斥的锁,并且锁占用的线程不是当前线程,获取失败。
  2. 根据队列策略和是否是重入锁 尝试CAS加锁。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值