2024年Java最全ReentrantReadWriteLock源码深度剖析(1),Java开发入门教程

最后

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
在这里插入图片描述

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

/**

  • ThreadLocal subclass, Easiest to explicitly define for sake

  • of deserialization mechanics

*/

/** 简单的自定义的 ThreadLocal 来用进行记录 readLock 获取的次数 */

static final class ThreadLocalHoldCounter extends ThreadLocal{

@Override

protected HoldCounter initialValue() {

return new HoldCounter();

}

}

/**

  • The number of reentrant read locks held by current thread.

  • Initialized only in constructor and readObject

  • Removed whenever a thread’s read hold count drops to 0

*/

/**

  • readLock 获取记录容器 ThreadLocal(ThreadLocal 的使用过程中当 HoldCounter.count == 0 时

要进行 remove , 不然很有可能导致 内存的泄露)

*/

private transient ThreadLocalHoldCounter readHolds;

/**

  • 最后一次获取 readLock 的 HoldCounter 的缓存

  • (PS: 还是上面的问题 有了 readHolds 为什么还需要 cachedHoldCounter呢?

大非常大的场景中, 这次进行release readLock的线程就是上次 acquire 的线程,

这样直接通过cachedHoldCounter来进行获取, 节省了通过 readHolds 的 lookup 的过程)

*/

private transient HoldCounter cachedHoldCounter;

/**

  • 下面两个是用来进行记录 第一次获取 readLock 的线程的信息

  • 准确的说是第一次获取 readLock 并且 没有 release 的线程, 一旦线程进行 release readLock,

则 firstReader会被置位 null

*/

private transient Thread firstReader = null;

private transient int firstReaderHoldCount;

针对获取 readLock 的线程的获取次数需要分3中情况

  1. 线程的tid 及获取次数 count 存放在 HoldCounter 里面, 最后放在ThreadLocal 中

  2. 从cachedHoldCounter获取存入的信息, 额, 这里不是有 ThreadLocal, 干嘛还需要cachedHoldCounter呢?

原因是这样的, 但多数情况在进行线程 acquire readLock后不久就会进行相应的release,

而从 cachedHoldCounter 获取, 省去了从 ThreadLocal 中 lookup 的操作(其实就是节省资源, ThreadLocal 中的查找需要遍历数组)

  1. firstReader firstReaderHoldCount 这两个属性是用来记录第一次获取锁的线程,

及重入的次数(这里说第一次有点不准确, 因为当线程进行释放 readLock 后, firstReader 会被置空,

当再有新的线程获取 readLock 后, firstReader 就会被赋值新的线程)

这里有一个疑问,既然readHolds可以保存所有线程的重入计数,咋还使用了firstReader和firstReaderHoldCount单独保存第一个读线程重入计数。cachedHoldCounter保存最后一个读线程的重入计数。

源码多次使用firstReader和cachedHoldCounter来进行重入计数判断,如果不是才使用readHolds先读取在设置重入计数。 比如只有一个读线程获取读锁,那么也就没有必要设置ThreadLocal变量readHolds。这里顺便说一下ThreadLocal的缺点: 1. 容易造成内存泄漏。thread local 是实际是两级以上的hashtable,一旦你还用线程池的话,用的不好可能永远把内存占着。造成java VM上的内存泄漏。 2. 代码的耦合度高,且测试不易。

3.2 Sync 的抽象方法

/**

  • 当线程进行获取 readLock 时的策略(这个策略依赖于 aqs 中 sync queue 里面的Node存在的情况来定),

  • @return

*/

abstract boolean readerShouldBlock();

/**

  • Returns true if the current thread, when trying to acquire

  • the write lock, and otherwise eligible to do so, should block

  • because of policy for overtaking other waiting threads.

*/

/**

  • 当线程进行获取 readLock 时的策略(这个策略依赖于 aqs 中 sync queue 里面的Node存在的情况来定)

  • @return

*/

abstract boolean writerShouldBlock();

这两个方法主要用于子类实现lock的获取策略

4. 内部类 Sync 的 tryRelease 方法

这个方法主要用于独占锁释放时操作 AQS里面的state状态

/**

  • Note that tryRelease and tryAcquire can be called by

  • Conditions. So it is possible that their arguments contain

  • both read and write holds that are all released during a

  • condition wait and re-established in tryAcquire

*/

/**

  • 在进行 release 锁 时, 调用子类的方法 tryRelease(主要是增对 aqs 的 state 的一下赋值操作) (PS: 这个操作只有exclusive的lock才会调用到)

  • @param releases

  • @return

*/

protected final boolean tryRelease(int releases){

if(!isHeldExclusively()){ // 1 监测当前的线程进行释放锁的线程是否是获取独占锁的线程

throw new IllegalMonitorStateException();

}

int nextc = getState() - releases; // 2. 进行 state 的释放操作

boolean free = exclusiveCount(nextc) == 0; // 3. 判断 exclusive lock 是否释放完(因为这里支持 lock 的 reentrant)

if(free){ // 4. 锁释放掉后 清除掉 独占锁 exclusiveOwnerThread 的标志

setExclusiveOwnerThread(null);

}

setState(nextc); // 5. 直接修改 state 的值 (PS: 这里没有竞争的出现, 因为调用 tryRelease方法的都是独占锁, 互斥, 所以没有 readLock 的获取, 相反 readLock 对 state 的修改就需要 CAS 操作)

return free;

}

整个操作流程比较简单, 有两个注意点

  1. 这里的 state 操作没用 CAS, 为啥? 主要这是独占的(此刻没有其他的线程获取 readLock, 所以没有竞争的更改 state的情况)

  2. 方法最后返回的是 free, free 指的是writeLock是否完全释放完, 因为这里有 锁重入的情况, 而在完全释放好之后才会有后续的唤醒操作

5. 内部类 Sync 的 tryAcquire 方法

tryAcquire方法是 AQS 中 排他获取锁 模板方法acquire里面的策略方法

/**

  • AQS 中 排他获取锁 模板方法acquire里面的策略方法 tryAcquire 的实现

  • @param acquires

  • @return

*/

protected final boolean tryAcquire(int acquires){

/**

  • Walkthrough:

    1. If read count nonzero or write count nonzero
  •  and owner is a different thread, fail
    
    1. If count would saturate, fail. (This can only
  •  happen if count is already nonzero.)
    
    1. Otherwise this thread is eligible for lock if
  •  it is either a reentrant acquire or
    
  •  queue policy allows it. If so, update state
    
  •  and set owner.
    

*/

Thread current = Thread.currentThread();

int c = getState();

int w = exclusiveCount©; // 1. 获取现在writeLock 的获取的次数

if(c != 0){

// Note: if c != 0 and w == 0 then shared count != 0

if(w == 0 || current != getExclusiveOwnerThread()){ // 2. 并发的情况来了, 这里有两种情况 (1) c != 0 && w == 0 -> 说明现在只有读锁的存在, 则直接 return, return后一般就是进入 aqs 的 sync queue 里面进行等待获取 (2) c != 0 && w != 0 && current != getExclusiveOwnerThread() 压根就是其他的线程获取 read/writeLock, 读锁是排他的, 所以这里也直接 return -> 进入 aqs 的 sync queue 队列

return false;

}

if(w + exclusiveCount(acquires) > MAX_COUNT){ // 3. 计算是否获取writeLock的次数 饱和了(saturate)

throw new Error(“Maximum lock count exceeded”);

}

// Reentrant acquire

setState(c + acquires); // 4. 进行 state值得修改 (这里也不需要 CAS 为什么? 读锁是排他的, 没有其他线程和他竞争修改)

return true;

}

if(writerShouldBlock() || !compareAndSetState(c, c + acquires)){ // 5. 代码运行到这里 (c == 0) 这时可能代码刚刚到这边时, 就有可能其他的线程获取读锁, 所以 c == 0 不一定了, 所以需要再次调用 writerShouldBlock查看, 并且用 CAS 来进行 state 值得更改

return false;

}

setExclusiveOwnerThread(current); // 6. 设置 exclusiveOwnerThread writeLock 获取成功

return true;

}

  1. c != 0 代表有线程获取 read/writeLock, 这里有两种情况 (1) c != 0 && w == 0 -> 说明现在只有读锁的存在, 则直接 return, return后一般就是进入 aqs 的 sync queue 里面进行等待获取 (2) c != 0 && w != 0 && current != getExclusiveOwnerThread() 压根就是其他的线程获取 read/writeLock, 读锁是排他的, 所以这里也直接 return -> 进入 aqs 的 sync queue 队列

  2. writerShouldBlock 是判断是否需要进入 AQS 的 Sync Queue 队列

6. Sync 的 tryReleaseShared 方法

这个方法是 readLock 进行释放lock时调用的

/**

  • AQS 里面 releaseShared 的实现

  • @param unused

  • @return

*/

protected final boolean tryReleaseShared(int unused){

Thread current = Thread.currentThread();

if(firstReader == current){ // 1. 判断现在进行 release 的线程是否是 firstReader

// assert firstReaderHoldCount > 0

if(firstReaderHoldCount == 1){ // 2. 只获取一次 readLock 直接置空 firstReader

firstReader = null;

}else{

firstReaderHoldCount–; // 3. 将 firstReaderHoldCount 减 1

}

}else{

HoldCounter rh = cachedHoldCounter; // 4. 先通过 cachedHoldCounter 来取值

if(rh == null || rh.tid != getThreadId(current)){ // 5. cachedHoldCounter 代表的是上次获取 readLock 的线程, 若这次进行 release 的线程不是, 再通过 readHolds 进行 lookup 查找

rh = readHolds.get();

}

int count = rh.count;

if(count <= 1){

readHolds.remove(); // 6. count <= 1 时要进行 ThreadLocal 的 remove , 不然容易内存泄露

if(count <= 0){

throw unmatchedUnlockException(); // 7. 并发多次释放就有可能出现

}

}

–rh.count; // 9. HoldCounter.count 减 1

}

for(;😉{ // 10. 这里是一个 loop CAS 操作, 因为可能其他的线程此刻也在进行 release操作

int c = getState();

int nextc = c - SHARED_UNIT; // 11. 这里是 readLock 的减 1, 也就是 aqs里面state的高 16 上进行 减 1, 所以 减 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; // 12. 返回值是判断 是否还有 readLock 没有释放完, 当释放完了会进行 后继节点的 唤醒( readLock 在进行获取成功时也进行传播式的唤醒后继的 获取 readLock 的节点)

}

}

}

整个过程主要是 firstReader, cachedHoldCounter, readHolds 数据操作, 需要注意的是当 count == 1 时需要进行 readHolds.remove(), 不然会导致内存的泄漏

7. Sync 的 tryAcquireShared 方法

AQS 中 acquireShared 的子方法,主要是进行改变 aqs 的state的值进行获取 readLock

/**

  • AQS 中 acquireShared 的子方法

  • 主要是进行改变 aqs 的state的值进行获取 readLock

  • @param unused

  • @return

*/

protected final int tryAcquireShared(int unused){

/**

  • Walkthrough:

    1. If write lock held by another thread, fail;
    1. 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.
    
    1. 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. 判断是否有其他的线程获取了 writeLock, 有的话直接返回 -1 进行 aqs的 sync queue 里面

if(exclusiveCount© != 0 && getExclusiveOwnerThread() != current){

return -1;

}

int r = sharedCount©; // 2. 获取 readLock的获取次数

if(!readerShouldBlock() &&

r < MAX_COUNT &&

compareAndSetState(c, c + SHARED_UNIT)){ // 3. if 中的判断主要是 readLock获取的策略, 及 操作 CAS 更改 state 值是否OK

if(r == 0){ // 4. r == 0 没有线程获取 readLock 直接对 firstReader firstReaderHoldCount 进行初始化

firstReader = current;

firstReaderHoldCount = 1;

}else if(firstReader == current){ // 5. 第一个获取 readLock 的是 current 线程, 直接计数器加 1

firstReaderHoldCount++;

}else{

HoldCounter rh = cachedHoldCounter;

if(rh == null || rh.tid != getThreadId(current)){ // 6. 还是上面的逻辑, 先从 cachedHoldCounter, 数据不对的话, 再从readHolds拿数据

cachedHoldCounter = rh = readHolds.get();

}else if(rh.count == 0){ // 7. 为什么要 count == 0 时进行 ThreadLocal.set? 因为上面 tryReleaseShared方法 中当 count == 0 时, 进行了ThreadLocal.remove

readHolds.set(rh);

}

rh.count++; // 8. 统一的 count++

}

return 1;

}

return fullTryAcquireShared(current); // 9.代码调用 fullTryAcquireShared 大体情况是 aqs 的 sync queue 里面有其他的节点 或 sync queue 的 head.next 是个获取 writeLock 的节点, 或 CAS 操作 state 失败

}

在遇到writeLock被其他的线程占用时, 直接返回 -1; 而整个的操作无非是 firstReader cachedHoldCounter readHolds 的赋值操作;

当 遇到 readerShouldBlock == true 或 因竞争导致 “compareAndSetState(c, c + SHARED_UNIT)” 失败时会用兜底的函数 fullTryAcquireShared 来进行解决

8. Sync 的 fullTryAcquireShared 方法

fullTryAcquireShared 这个方法其实是 tryAcquireShared 的冗余(redundant)方法, 主要补足 readerShouldBlock 导致的获取等待 和 CAS 修改 AQS 中 state 值失败进行的修补工作

/**

  • Full version of acquire for reads, that handles CAS misses

  • and reentrant reads not dealt with in tryAcquireShared.

*/

/**

  • fullTryAcquireShared 这个方法其实是 tryAcquireShared 的冗余(redundant)方法, 主要补足 readerShouldBlock 导致的获取等待 和 CAS 修改 AQS 中 state 值失败进行的修补工作

*/

final int fullTryAcquireShared(Thread current){

/**

  • This code is part redundant with that in

  • tryAcquireShared but is simpler overall by not

  • complicating tryAcquireShared with interactions between

  • retries and lazily reading hold counts

*/

HoldCounter rh = null;

for(;😉{

int c= getState();

if(exclusiveCount© != 0){

if(getExclusiveOwnerThread() != current) // 1. 若此刻 有其他的线程获取了 writeLock 则直接进行 return 到 aqs 的 sync queue 里面

return -1;

// else we hold the exclusive lock; blocking here

// would cause deadlock

难道这样就够了吗?不,远远不够!

提前多熟悉阿里往年的面试题肯定是对面试有很大的帮助的,但是作为技术性职业,手里有实打实的技术才是你面对面试官最有用的利器,这是从内在散发出来的自信。

备战阿里时我花的最多的时间就是在学习技术上,占了我所有学习计划中的百分之70,这是一些我学习期间觉得还是很不错的一些学习笔记

我为什么要写这篇文章呢,其实我觉得学习是不能停下脚步的,在网络上和大家一起分享,一起讨论,不单单可以遇到更多一样的人,还可以扩大自己的眼界,学习到更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。

今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!

阿里一面就落马,恶补完这份“阿里面试宝典”后,上岸蚂蚁金服

阿里一面就落马,恶补完这份“阿里面试宝典”后,上岸蚂蚁金服

image.png

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

难道这样就够了吗?不,远远不够!

提前多熟悉阿里往年的面试题肯定是对面试有很大的帮助的,但是作为技术性职业,手里有实打实的技术才是你面对面试官最有用的利器,这是从内在散发出来的自信。

备战阿里时我花的最多的时间就是在学习技术上,占了我所有学习计划中的百分之70,这是一些我学习期间觉得还是很不错的一些学习笔记

我为什么要写这篇文章呢,其实我觉得学习是不能停下脚步的,在网络上和大家一起分享,一起讨论,不单单可以遇到更多一样的人,还可以扩大自己的眼界,学习到更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。

今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!

[外链图片转存中…(img-yIKv36tT-1714869231363)]

[外链图片转存中…(img-7PiPeb4Y-1714869231363)]

[外链图片转存中…(img-rDtTEYsZ-1714869231364)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值