reentrantlock非公平锁不会随机挂起线程?_ReentrantLock源码详细解读

f1935dc660ddd39579775632579172a2.png

引言

ReentrantLock是面试中的高频考点,其中实现原理还是很有必要了解的。它与synchronized类似,都是互斥锁,但具有更好的扩展性。ReentrantLock是基于AQS实现的,遗忘的同学可以回顾一下AQS源码详细解读。

文章导读

  • ReentrantLock继承树及重要方法
  • 非公平锁及公平锁的获取
  • tryLock(),lockInterruptibly()
  • 释放资源
  • ReentrantLock相关面试题
  • 总结

一、ReentrantLock的内部类概述与重要方法

1.1 继承关系概述
首先看一下继承关系图,对它整体的构造有一个初步的认识。

9fa219428c959bf7843fa50cd222c743.png
ReentrantLock内部类继承树

我们发现,ReentrantLock实现了公平锁和非公平锁。都通过他们的父类Sync来调度。

  • Sync:是提供AQS实现的工具,类似于适配器,提供了抽象的lock(),便于快速创建非公平锁。
  • FairSync(公平锁):线程获取锁的顺序和调用lock()的顺序一样,FIFO。
  • NoFairSync(非公平锁):线程获取锁的顺序和调用lock()的顺序无关,抢到CPU的时间片即可调度。

1.2 ReentrantLock中的重要方法

构造方法:无参构造方法,默认创建非公平锁;有参构造方法,并且fair==true时,创建公平锁。

//维护了一个Sync,对于锁的操作都交给sync来处理

获取资源资源(锁)的方法:可以看出,请求都是交给Sync来调度的。

//请求锁资源,会阻塞且不处理中断请求,

释放资源(锁)的方法:不管是公平还是非公平锁,都会调用AQS.release(1),给当前线程持有锁的数量-1。

public 

二、获取资源(锁)

我们主要看非公平锁与公平锁获取资源的方法,因为释放资源的逻辑是一样的。

2.1 Sync获取资源

sync中定义了获取资源的总入口。具体的调用还是看实现类是什么。

abstract 

2.2 非公平锁获取资源

lock():获取锁时调用AQS的CAS方法,是阻塞的。如果获取成功,则把当前线程设置为锁的持有者;如果获取失败,则通过AQS.acquire()获取锁。通过AQS源码详细解读,了解到acquire()中使用了模板模式,调用子类的tryAcquire()尝试获取锁,如果tryAcquire()返回false,则进入等待队列自旋获取,再判断前驱的waitStatus,判断是否需要被阻塞等。这里就不一一赘述了,感兴趣的可以看上一篇文章。

final 

tryAcquire():走的是Sync.nofairTryAcquire()。

protected 

nonfairTryAcquire(int acquires):如果锁空闲,则用CAS修改state;如果锁被占用,则判断占有者是不是自己,实现可重入。最终没有获取锁到就返回false。

final 

2.3 公平锁获取资源

lock():也是阻塞的。与非公平锁的区别是,不能直接通过CAS修改state,而是直接走AQS.acquire()。

final 

tryAquire():与非公平锁类似,AQS.acquire()会调用这个钩子方法。只不过多判断了hasQueuedPredecessors(),判断当前节点在等待队列中是否有前驱节点,如果有,则说明有线程比当前线程更早的请求资源,根据公平性,当前线程请求资源失败;如果当前节点没有前驱节点,才有做后面的逻辑判断的必要性。

protected 

三、其他获取锁的方法

ReentrantLock有3中获取锁的方法,lock(),tryLock(),lockInterruptibly()。

3.1 tryLock()--尝试获取资源

tryLock():走的还是sync的方法,在指定时间内获取锁,直接返回结果。

public 

tryAcquireNanos():如果调用tryLock的规定时间内尝试方法,就会调用该方法,先判断是否中断,然后尝试获取资源,否则进入AQS.doAcquireNanos()(这个方法在上篇文章有解释)。在规定时间内自旋拿资源,拿不到则挂起再判断是否被中断。

public 

3.2 lockInterruptibly()--获取锁时响应中断

lockInterruptibly():交给了调度者sync执行。

public 

acquireInterruptibly():当尝试获取锁失败后,就进行阻塞可中断的获取锁的过程。调用AQS.doAcquireInterruptibly()(这个方法在上篇文章也有详细解释)。

public 

四、释放资源(锁)

公平锁与非公平锁的释放都是一样的。通过前面的阅读,可以知道,ReentrantLock.release()调用的是sync.release(1)。本质还是进入AQS.release(1),下面看看其中的tryRelease()这个钩子方法如何实现。

Sync释放资源

tryRelease():尝试释放锁,彻底释放后返回true。

protected 

五、ReentrantLock的相关面试题

1)ReentrantLock是如何实现可重入的?
不管是公平锁还是非公平锁,在获取锁时调用的tryAcquire()方法,获取成功后会setExclusiveOwnerThread(current)。将本线程设置为主人,之后每次调用tryAcquire()时,发现当前线程就是主人,直接返回true。

2)简述公平锁与非公平锁的区别?
从定义角度:
获取锁的顺序与请求锁的时间顺序一致就是公平锁,反之则为非公平锁。公平锁每次都是从同步队列中的第一个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程能再次获取到锁。
公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。
从源码角度:
当锁资源已经被占用时,请求每次有请求到达,就在等待队列中排队。此时如果锁资源被释放了,刚好新来一个线程,若是非公平锁则会直接CAS获取锁,成功则返回,不成功则加入到等待队列自旋获取,自旋过程中当前驱是对头,并且tryAcquire成功时则获取成功。
若是公平锁,则当前线程必须等待,锁必须给等待队列第一个线程,如果第一个线程被阻塞了,唤醒也是需要时间的,醒了才能拿锁。

3)AQS中有哪些资源访问模式?区别?
独占模式和共享模式。
只有一个线程才能持有这个锁就是独占模式,由Node节点中的nextWait来标识。
ReentrantLock就是一个独占锁;而WriteAndReadLock的读锁则能由多个线程同时获取,但它的写锁则只能由一个线程持有,因此它使用了两种模式。

4)为什么ReentrantLock.lock()方法不能被其他线程中断?
因为当前线程前面可能还有等待线程,在AQS.acquireQueued()的循环里,线程会再次被阻塞。parkAndCheckInterrupt()返回的是Thread.interrupted(),不仅返回中断状态,还会清除中断状态,保证阻塞线程忽略中断。

总结

其实看完AQS源码后,ReentrantLock就是个弟弟。在实现上其实并不复杂,实现了AQS的独占模式。希望看完之后能对可重入锁,响应中断锁有更深入的理解。

如果喜欢我的文章,欢迎关注我的专栏~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值