ReentrantLock的lock、unlock源码分析

本文详细解析了Java并发库中的ReentrantLock加锁与解锁过程,包括公平锁与非公平锁的实现细节。通过CAS操作、线程节点的管理以及AQS队列的使用,阐述了锁的获取与释放机制,同时探讨了非公平锁相对于公平锁的性能优势。
摘要由CSDN通过智能技术生成

一、lock

 可以看出 加锁方法主要有这三步,最后那个selfInterrupt方法是中断线程,里边就一行代码中断线程,这里就不展开说了。

先简单说一下这三步:

1:通过CAS方式尝试获取锁

2:获取锁失败的情况下,为当前线程创建node节点,加入到队列末尾

3:加入队列后,判断前一节点是不是head节点,是的话尝试去获取锁;上一节点不是head节点,或获取锁失败,就将上一个节点的waitStatus改为-1,意思是告诉上一节点,你释放完锁后需要通知我,然后将自己线程park掉。

1:tryAcquire获取锁

这个方法在ReentrantLock中公平锁、非公平锁都对其进行了实现,公平锁的实现中只比非公平锁中多了一个方法hasQueuedPredecessors,来看一下源码

公平锁

1.1、先判断锁是不是空闲的,即c是不是等于0

1.2、重入锁:若锁已经被占用,判断一下占用锁的是不是当前线程,是的话将state加1(acquires传过来那个参数就是1,写死的)这里记录上锁次数,解锁时也要解对应次数才可以使锁空闲出来。

1.3、锁是空闲的,往下执行

hasQueuedPredecessors这个方法就是在获取锁之前先判断一下队列中有没有有效的阻塞线程(可能有些线程已经取消了),有的话直接去排队,没有的话就执行compareAndSetState方法,通过CAS的方式去竞争锁,竞争成功的活,就记录下当前线程,竞争失败的话去排队。

简单来看一下hasQueuedPredecessors这个方法

 这个方法看起来有点复杂,但是意思是很简单的,这里就直接解释吧

1.3.1:首先判断一下头节点是不是空的,如果为空,那肯定没队列,直接返回,让线程去竞争锁

1.3.2:头节点不为空也不一定就证明有排队的线程,还需要接着判断,这里的(s = h.next) == null还继续往下走有点意思啊,按理说你下一个节点都没了 我还遍历啥,这里我们后边分析第三步acquireQueued方法时会得出答案,这里虽然是空,但并不一定后续没节点,队列是双向的,head的next没有指向节点,但是不排除有节点的prev指向head,所以还需要遍历一下;s.waitStatus > 0这个判断是说head的下一个节点存在,但是这个线程已经被取消了,那这种情况我也需要继续往下找找有没有其他有效的阻塞线程。

1.3.3:这个for循环很简单,就是从队列尾部往前找有效的线程,一直遍历到最接近head节点的那个有效节点,把他挂到head的next上

非公平锁

  

 可以看到非公平锁只是比公平锁少了hasQueuedPredecessors这个方法,即非公平锁在抢锁之前不去判断有没有排队的线程,只要锁是空闲的就直接抢,抢到就执行,抢不到也一样会去加入队列。这里提一下,ReentrantLock中非公平锁抢锁是不公平的,但是被唤醒的顺序是公平的,因为非公平锁抢不不到锁时同样加入了队列。

只是在非公平锁中线程被唤醒不一定就是你来获取锁了,这时候如果有新的线程请求获取锁,同样会与刚被唤醒的线程发生争抢。

2:addWaiter加入队列

源码:

这个方法相对简单

1:先给当前线程创建一个node节点

2:尾节点为空,表明队列不存在,创建一个队列

3:将新创建的node节点的上一节点指向原来的尾节点(就是把node放到最后边)

4:通过CAS去修改尾节点(因为是多线程,这里可能有并发可能,所以通过CAS修改)将尾节点改为新创建的node,若修改成功,就将原来尾节点的next执行node,将新创建的node节点返回。

3:acquireQueued通知上一节点我在排队,然后阻塞自己

 源码:

这个方法主要分三步

1:判断当前节点的上一节点是不是head节点,如果是,那经过以上那么处理后,head可能已经把锁释放掉了,那我这里就再尝试获取一下锁,获取成功了就把自己 设置为head节点(这里边会把节点中记录的线程置空,上一节点的指向也置空),然后把自己的下一节点的指向置空了,看到没有,就是这里,它为了便于GC回收,他把下一节点的指向直接置空了,所以在hasQueuedPredecessors方法中,即使head节点的下一个节点是空的,它还是会去遍历队列,寻找有效节点。

2:shouldParkAfterFailedAcquire这个方法就很简单了,就是如果上一个节点的waitStatus不是-1,就通过CAS将它改为-1,这里有并发,所以通过CAS直到改成功为止,就是记录一下,让上一节点直到,后边有排队线程,你用完锁需要通知我。

3:parkAndCheckInterrupt这里边就是直接调用线程的park方法阻塞自己,等待被唤醒。

二、unlock

ReentrantLock的公平锁、非公平锁的解锁方法用的是同一个

源码:

也很简单,就这两步:

1:tryRelease尝试解锁

这个解锁就是直接减1,解锁一次减一次 ,和加锁次数对应,直到c=0,解锁才算真正完毕,置空占用锁线程。

2:解锁完成后,唤醒下一节点阻塞线程

这个唤醒方法也不复杂

2.1: 将自身的waitStatus置为0

2.2:如果下一个节点为空,就通过尾查法遍历队列,找到最接近的一个节点

2.3:通过线程的unpark方法唤醒线程。

拓展问答:

Q:非公平锁为什么使用AQS,而不是直接使用CAS加锁?

A:非公平锁之所以也使用队列,应该是为了考虑性能问题,高并发情况下,如果不进入队列,那么没有抢到锁的线程就要一直在那里自旋,使CPU频繁切换,造成CPU飙升。

Q:公平锁的性能为什么比非公平锁差?

A:通过上边的分析,这个就很明显了,因为公平锁在竞争锁之前都要遍历一下队列中有没有排队的线程,而非公平锁不管这些,来一个线程,只要锁是空闲的就直接抢,所以性能上要比公平锁高一些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值