14

jdk 源码系列之 ReentrantLock

 3 years ago
source link: https://xie.infoq.cn/article/3758d499e885706b7aa83b097
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近将 ReentrantLock 学了一遍同时也把源码读了一遍,记录下学习的过程

JDK 源码系列

使用

使用锁机制,来保障线程安全

Lock lock =newReentrantLock();

lock.lock();

try{
// 受此锁保护的资源块
}finally{
lock.unlock();
}

或者你可以使用 tryLock() 方法,在多线程中,当一个线程释放锁的时候,就尝试去获取锁。

Lock lock =newReentrantLock();
if(lock.tryLock()) {
try{
// 受此锁保护的资源块
}finally{
lock.unlock();
}
}else{
// 进行其他操作,未被锁保护
}

这种方法,在确保获取锁的时候才会解锁,并且在未获锁时不会尝试去解锁。

如果尝试去获取锁的时候太长,也可以给获取锁的这个过程加上时间,超时则直接中断线程。

publicstaticvoidmain(String[] args)throwsInterruptedException{
Lock lock =newReentrantLock();
if(lock.tryLock(5, TimeUnit.SECONDS)) {
try{
// manipulate protected state

}
finally{
lock.unlock();
}
}else{
// perform alternative actions
}

}

}

如果希望当前锁的模块不是立刻执行,也可以调用 await 机制

Lock lock = new ReentrantLock();
lock.lock();
try {
// manipulate protected state
lock.newCondition().await(5, TimeUnit.SECONDS);
}
finally {
lock.unlock();
}

有时候,当遇到过长的业务流程,导致持有的时间太长了,可以考虑打断锁的机制,释放锁。

Lock lock =newReentrantLock();
lock.lock();
try{
// manipulate protected state
longstartTime = System.currentTimeMillis();
longendTime = System.currentTimeMillis();
if(endTime - startTime >10) {
lock.lockInterruptibly();
try{
}finally{
lock.unlock();
}
}

lock.newCondition().await(5, TimeUnit.SECONDS);
}
finally{
lock.unlock();
}

应用场景比较

2mEzym6.png!mobile

源码

先看类

publicclassReentrantLockimplementsLock,java.io.Serializable{
privatestaticfinallongserialVersionUID =7373984872572414699L;

向上继承了 Lock 接口,以及 Serializable ,都是实现了 Lock 的方法。

  • void lock() 获取锁

  • void lockInterruptibly() 中断锁机制

  • boolean tryLock() 其他线程有释放锁,才调用获取锁

  • boolean tryLock(long time, TimeUnit unit) 给定时间内线程是空闲时间,且其他线程有释放锁,才调用获取锁

  • void unlock() 释放锁

  • Condition newCondition() 给锁绑定条件

实例化锁

Lock lock =newReentrantLock();

Lock lock1 =newReentrantLock(false);

Lock lock2 =newReentrantLock(true);

这里,有两个构造,一个是无参构造,一个是传入布尔值。

/**
* Creates an instance of {@codeReentrantLock}.
* This is equivalent to using {@codeReentrantLock(false)}.
*/
publicReentrantLock(){
// 创建一个非公平锁
sync =newNonfairSync();
}

/**
* Creates an instance of {@codeReentrantLock} with the
* given fairness policy.
*
*@paramfair {@codetrue} if this lock should use a fair ordering policy
*/
publicReentrantLock(booleanfair){
// true 公平锁 false 非公平锁
sync = fair ?newFairSync() :newNonfairSync();
}

这里默认是构造一个非公平锁,也可以直接设置创建什么类型锁,比如公平锁、非公平锁。

我们先看看公平锁。

/**
* Sync object for fair locks
*/
staticfinalclassFairSyncextendsSync{
privatestaticfinallongserialVersionUID = -3000897897090466540L;

finalvoidlock(){
// 当使用 lock.lock() 的时候调到 同时传入1
acquire(1);
}

/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
// 这里 acquires = 1指的是 上锁的状态 0则是未上锁
protectedfinalbooleantryAcquire(intacquires){
// 获取当前线程对象
finalThread current = Thread.currentThread();
// 是否上锁,首次默认是0
intc = getState();
if(c ==0) {
// hasQueuedPredecessors 是否是当前线程,使用了双向链表的数据结构,做一个 queue,这里的意思大概是到了这个线程可以加锁了。不用排队了
if(!hasQueuedPredecessors() &&
// CAS 比较后修改成功
compareAndSetState(0, acquires)) {
// 将当前线程设置成独占线程
setExclusiveOwnerThread(current);
// 获取锁成功
returntrue;
}
}
// 独占线程
elseif(current == getExclusiveOwnerThread()) {
// nextc = 1
intnextc = c + acquires;
if(nextc <0)
thrownewError("Maximum lock count exceeded");
// 同步锁的状态 = 1
setState(nextc);
returntrue;
}
// 其他线程则未获取到锁
returnfalse;
}
}

/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link#tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@linkLock#lock}.
*
*@paramarg the acquire argument. This value is conveyed to
* {@link#tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
publicfinalvoidacquire(intarg){
// 尝试上锁
if(!tryAcquire(arg) &&
// 先将线程当前线程添加进队列最后一个,然后在判断是否到他上锁了
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 没有则直接打断线程
selfInterrupt();
}


/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
*@paramnode the node
*@paramarg the acquire argument
*@return{@codetrue} if interrupted while waiting
*/
// 持有锁的队列里面的线程
finalbooleanacquireQueued(finalNode node,intarg){
booleanfailed =true;
try{
booleaninterrupted =false;
// 一个死循环你判断里面当前谁获得锁
for(;;) {
// 指向下一个节点
finalNode p = node.predecessor();
// 队列的第一个,且获取锁,将当前线程变成独占线程
if(p == head && tryAcquire(arg)) {
setHead(node);
p.next =null;// help GC
failed =false;
// 如果获得到锁,则不需要打断当前线程返回一个 false
returninterrupted;
}
// 线程阻塞了
if(shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 中断获取锁的机制
interrupted =true;
}
}finally{
// 如果获得到锁 则不需要取消加锁操作、反之则取消
if(failed)
cancelAcquire(node);
}
}

公平锁,当前的线程会进入一个队列中最后一个,等待到他的上锁。如果当前的线程表示获取到锁,且也是队列的第一个位置。会将自己变成锁的独占线程,只有自己才持有这个锁的对象。同时这里进行了 CAS 的原子交换,设置状态为1。其中 0 为 未上锁状态,1为上锁状态。

非公平锁

/**
* Sync object for non-fair locks
*/
staticfinalclassNonfairSyncextendsSync{
privatestaticfinallongserialVersionUID =7316153563782823691L;

/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
finalvoidlock(){
// 先进行抢占锁,如果如可以修改状态,则直接变成上锁状态
if(compareAndSetState(0,1))
// 设置独占线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 加锁
acquire(1);
}

protectedfinalbooleantryAcquire(intacquires){
returnnonfairTryAcquire(acquires);
}
}

/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
finalbooleannonfairTryAcquire(intacquires){
// 这个和公平锁里面差不多
finalThread current = Thread.currentThread();
intc = getState();
if(c ==0) {
if(compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
returntrue;
}
}
elseif(current == getExclusiveOwnerThread()) {
intnextc = c + acquires;
if(nextc <0)// overflow
thrownewError("Maximum lock count exceeded");
setState(nextc);
returntrue;
}
returnfalse;
}

非公平锁,先进行抢占锁,先查看线程是否释放了锁,如果释放了,当前线程则直接上锁,这样的好处就是减少了线程之间的等待,加快了上锁的机制,避免了排队时竞争所导致的延时,提高了性能,如果没有释放锁,则进入到队列的最后一个等待上锁。

小结一下。

e22iyur.png!mobile

释放锁机制。

/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@linkIllegalMonitorStateException} is thrown.
*
*@throwsIllegalMonitorStateException if the current thread does not
* hold this lock
*/
publicvoidunlock(){
// 携带 1
sync.release(1);
}


publicfinalbooleanrelease(intarg){
// 释放成功则为 true 否则是 false
if(tryRelease(arg)) {
Node h = head;
if(h !=null&& h.waitStatus !=0)
// 唤醒队列后面的线程
unparkSuccessor(h);
// 释放成功
returntrue;
}
// 释放失败
returnfalse;
}



protectedfinalbooleantryRelease(intreleases){

// c = 0
intc = getState() - releases;
// 如果不是当前线程和不是独占线程
if(Thread.currentThread() != getExclusiveOwnerThread())
// 直接抛出错
thrownewIllegalMonitorStateException();
booleanfree =false;
if(c ==0) {
// 释放锁
free =true;
// 独占线程设置null
setExclusiveOwnerThread(null);
}
// 0
setState(c);
returnfree;
}

锁的释放,比较简单。将锁状态重新设置回 0,同时独占线程也设置null,之后唤醒后面的队列里面的线程,完成释放。

源码总结

ReentrantLock 创建的时候,默认是非公平锁,不过你也可以在构造的时候,也可以创建一个公平锁。 其中通过 CAS 改变 state 的状态来改变锁的数值, 0 表示有锁可以获取,1 表示锁已被获取,来设置锁的独占线程。

在公平锁的机制中,请求锁的线程会直接排到一个队列中(通过一个双向链表来模拟的队列)的最后一个,去获取锁。

非公平锁的机制中,请求锁的线程首先会先通过 CAS 来改变 state 的锁状态,如果可以改变(0 -> 1),则直接获取到锁,将自身设置成独占锁。这样的好处就减少了一些进队列、加载队列、唤醒线程等性能消耗。如果未能修改到 state 的状态,也会变成公平锁的机制,进入到队列的最后一个,等待到它去获取锁。

锁的释放,将 state 重新设置回 0,同时独占线程(你也可以认为这是持有锁的线程对象)设置null,之后唤醒排在它下个的线程。这一系列步骤做完,则宣告锁的释放。

优缺点

非公平锁 ,确实性能比较高。不过也有一个显而易见的缺点,我们可以想象一下,当你在排队吃饭的时候,轮到你吃饭的时候,这时候突然来一个人插在你前面,提前打饭了,导致你打饭时间变长了,如果这时候在有几个人在也突然插到你前打饭,又会继续导致你打饭时间变得更长。那如果放到线程里面,突然其他线程提前获取到了锁,那会导致当前线程获取到锁时间变长,而导致线程阻塞,迟迟未获取到锁。

所以得根据业务去选择合适的锁类型,进行上锁,尽可能的避免有一些重要的业务因为上锁而阻塞到。

声明

作者: Sinsy 本文链接: https://blog.sincehub.cn/2020/11/10/jdk-reentrantLock/

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文声明。 如您有任何商业合作或者授权方面的协商,请给我留言:[email protected]

引用


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK