ReentrantLock原理

思考:
lock的初衷是什么?实现线程安全的原子性和有序性?锁定指定代码块,使得代码块的原子性得到保障,同时代码块内的代码禁止指令重排

juc中的locks包下的Lock接口的主要实现类有ReentrantLock等
Lock接口的实现类
ReentrantLock主要是利用CAS和AQS来实现

CAS:乐观锁的一种实现,在乐观锁与悲观锁中有介绍CAS算法

AQS:AbstractQueuedSynchronizer抽象队列同步器
AQS框架
AQS使用的是一个FIFO队列(first in first out)表示排队等待锁的线程,初始化的队列头节点称作"哨兵节点"或者"哑节点"
头节点不关联任何线程,除头结点外的节点中都会关联一个线程。

ReentrantLock使用时一定要是定义在类变量中,而不是方法内部

ReentrantLock的结构为:
ReentrantLock
Filed:
Sync sync

内部类:
Sync(抽象静态类extends AbstractQueuedSynchronizer)
NonfairSync(静态final类 extends Sync)
FairSync(静态final类 extends Sync)

1.加锁
在这里插入图片描述
调用类变量sync的lock()方法,sync有两个子类,即NonfairSync,FairSync

非公平锁NonfairSync

流程:
1.读取表示锁状态的变量(读取NonfairSync继承自父类AbstractQueueSynchronizer中的静态变量stateoffset指向的state类变量)
在这里插入图片描述
2.如果锁状态变量的值为0,那么当前线程尝试将变量值设置为1(通过CAS操作完成,CAS机制能保证在set时操作的原子性:Unsafe.getUnsafe().compateAndSwapInt(this,stateoffset, 0, 1)),当多个线程同时将那状态变量由0设置为1时,仅有一个线程能成功,其他线程都失败,失败后进入队列自旋并阻塞当前线程
2.1当线程成功将锁状态变量由0设置为1,则表示获取了锁
2.2如果该线程(或者说节点)已位于队列中,则将其出列(并将下一个节点设置为AQS队列的头节点)
2.1.3然后当前线程从lock方法中返回,对共享资源进行访问
2.2若失败,则当前线程将自身放入等待锁的队列中并阻塞自身,此时线程一直被阻塞在lock方法中,没有从该方法中返回(被唤醒后仍然在lock方法中,并从下一条语句继续执行,即又回到步骤1)
3.如果表示状态的变量的值为1,那么将当前线程放入等待队列中,然后将自身阻塞
在这里插入图片描述
分析:
NonfairSync,既然是非公平锁,那么在调用lock()时,会直接去尝试获得锁,而不是排队进行等候,这对于已经存在于队列中的线程来说是不公平的,如果没能获得锁,那么会调用acquire方法,进入同步队列,将线程加入到队尾,并阻塞自身
在这里插入图片描述
final boolean nonfairTryAcquire(int acquires):(tryAcquire的非公平锁实现)
1.首先会再次查询当前state值 并尝试用当前线程获取锁,如果成功获取的话返回true
2.当state不等于0时,会判断当前独占锁的线程是否 等于 当前线程,如果相等,则state += 1
并返回true(即实现了可重入锁)
3.否则返回false
在这里插入图片描述
nonfairTryAcquire(int acquires)方法还有一点需要注意的是,当通过state判断当前锁已经被线程获取,且获取了锁的线程正是当前线程,那么会对state进行一个+1的操作,实现可重入锁。但是在修改最新的state值的时候用的方法是setState()方法,而非CAS,也就是说这段代码实现了偏向锁的功能

Node addWaiter(Node mode):
1.获取当前AbstractQueuedSynchronizer的tail节点
2.当tail节点不为null时,将tail设为当前线程node的pre,然后再将当前线程node设置为AbstractQueuedSynchronizer的tail,然后将之前的tail(也就是pre)的next设置为当前线程node,并返回node
3.如果当前AbstractQueuedSynchronizer的tail节点为null,调用enq方法,在enq方法中,无限循环将当前节点设置为tail或者将当前节点插入到AbstractQueuedSynchronizer中成为新的tail
在这里插入图片描述
addWaiter(Node mode)分析:负责把当前无法获取锁的线程包装为一个Node,加入到AbstractQueuedSynchronizer队尾,其中mode参数是可为独占锁或共享锁,默认为null,独占锁,将当前线程Node追加到队尾分两步:
1.如果已经存在队尾,则通过CAS将当前线程Node设置为队尾
2.如果队尾为Null,或者通过CAS设置队尾失败,调用enq(),无限循环,将当前线程node设置为队尾

这里的队尾为null,调用enq()比较有意思,队尾为null,表示AQS队列还未初始化,假如有A,B,C三个线程同时竞争锁,A线程获得了锁,而B,C线程竞争失败,调用addWaiter()入队,由于AQS队列还未初始化,B,C线程同时调用enq()方法进行初始化,这里也会存在一个同步竞争的情况,那么如何保证enq()的初始化操作为原子操作?enq()方法里体现了经典的自旋+CAS组合来实现非阻塞的原子操作。由于CASHead()为原子操作,所有只有一个线程会创建head节点成功(且需要注意head节点为一个不带thred的空节点),假设线程B创建了head节点,然后B,C线程开始第二轮循环,此时两线程都进入else判断,假设B线程成功CASTail,那么c还需要在进行一轮循环将自己入队

Final boolean acquireQueued(final Nodenode,int arg):
1.如果当前线程节点的前继节点为队列的头节点,并且当前线程成功获取锁时,将当前线程节点设为头节点并将其前继节点移出队列,并方法返回flase(当方法返回false时,在外层方法中便不会调用interrupt())
2.如果当前线程的前继节点不是队列的头节点,且当前线程节点也没有成功获取锁时,判断如果当前线程节点的前继节点状态为SIGNAL(在判断过程中,当前继节点的状态为CANCELLED时,将前继节点从等待队列中移除并将前继节点的前继节点和当前线程节点关联起来,当前继节点为其他状态时将前继节点的状态设置为SIGNAL)且调用LockSupport.park(this)阻塞当前节点。当当前线程成功获取锁之后方法返回true
3.将以上1,2步骤进行无限循环,等待当前线程获取锁
4.如果在无限循环过程中出现了异常,以当前线程节点在等待队列中的位置为起点,向前后对整个队列进行更新,将队列中所有的状态为CANCELLED的节点移出队列,并将当前线程节点移出队列(同时在更新队列的过程中如果有效的前继节点为队列头结点或者其状态不为SIGNAL,将最近的一个有效的后继节点唤醒)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
acquireQueued(final Nodenode,int arg)分析:
acquireQueued()的主要作用是把已经追加到队列尾部的当前线程节点去尝试获取锁,若获取失败则挂起,且此方法为无限循环逻辑,至于为什么不会出现死循环,其原因在于parkAndCheckInterrupt()会把当前线程挂起,从而阻塞住线程的调用栈
线程在入队后能够挂起的前提是,当前线程节点的前继节点状态为SIGNAL
在这里插入图片描述
AbstractOwnableSynchronizer的Node的waitStatus有:
SHARED(共享的,new Node()),
EXCLUSIVE(独占的,null),
CANCELED(表示该线程节点已释放(超时,中断),已取消的节点不会再阻塞,1),
SIGNAL(表示后继node需要唤醒,即当该节点释放锁,就会通知标识为SIGNAL,-1),
它的含义是“Hi,前面的兄弟,如果你获取锁并且出队后,记得把我唤醒!”

CONDITION(条件等待标识该线程在condition队列中阻塞,,-2),
PROPAGATE( 表示该线程以及后继线程进行无条件传播,PROPAGATE状态的线程处于可运行的状态,-3)

总结:
总而言之ReentrantLock类的lock()方法的逻辑为,当前线程调用lock(),会直接去尝试获取锁,如果获取失败,会将自身加入到等待锁的AbstractQueuedSynchronizer队列尾部,同时会被阻塞在一个尝试获取锁的无限循环中
简单说来,AbstractQueuedSynchronizer会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态,经过调查线程的显式阻塞是通过调用LockSupport.park()完成,而LockSupport.park()则调用sun.misc.Unsafe.park()本地方法,再进一步,HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。

流程图:
在这里插入图片描述
2.解锁
在这里插入图片描述
在这里插入图片描述
Final boolean tryRelease(int releases)
1.首先判断当前线程是否是当前独占锁的线程,若不是,则抛出异常
2.将state计数-1,判断state是否等于0,即重入次数为0,那么表示释放锁成功,将独占锁的线程清空
3.更新state的值
4.返回是否释放锁成功
在这里插入图片描述
tryRelease()方法分析:
1.方法只对记录当前锁重入状态的state字段进行了更新,且当state==0时,将独占锁中的线程清空
2.方法并未对AQS中的节点进行相关操作

Final boolean release(int arg):
1.成功调用tryRelease()之后,只有当锁中线程被清空后,返回true执行以下逻辑
2.获取当前AQS的head节点,当head节点!=null 且waitStatus!= 0时,唤醒head节点的后继节点
在这里插入图片描述

公平锁FairSync

加锁:
在这里插入图片描述
和非公平锁的区别在于,FairSync不会直接去尝试获取锁,而是直接将当前线程进行入队,再尝试获取锁

全文结束

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值