功能介绍
ReentrantLock可重入锁,Lock锁的具体实现类,具有与synchronized锁相同的基本行为和语义的可重入互斥锁,但具有扩展功能。ReentrantLock锁由最后成功锁定且未释放的线程拥有。当锁不属于任何线程时,调用锁的线程将返回并成功获取锁。如果当前线程已经拥有锁,该方法将立即返回。
简单来说就是和synchronized关键字具有相同功能,可对共享资源进行锁定,以确保共享资源访问的安全性。
如何使用
class X {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock();
}
}
复制代码
这里使用源码中的示例代码进行演示,使用也很简单lock.lock()进行上锁的开始,之后的语句为需要同步的语句,直到lock.unlock()锁定结束,与synchronized不同的是ReentrantLock是需要手动释放锁的,否则会造成死锁
源码分析
接下来我们直接根据示例代码进行源码分析吧
public ReentrantLock() {
sync = new NonfairSync();
}
复制代码
首先是直接使用无参构造创建ReentrantLock的实例对象,根据源码我们可以发现无参构造默认使用的是非公平锁NonfairSync的实例对象对成员变量sync进行赋值
下面我们具体看下ReentrantLock类的相关结构以及其非公平锁和公平锁的内部类结构
内部结构
private final Sync sync;
复制代码
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock(); // 抽象的锁定方法,待子类去实现
// 非公平锁的获取方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread(); // 获取当前线程
int c = getState(); // 获取state状态值 0--锁未被占用 >0代表锁已被占用,具体的数值为重入的次数
if (c == 0) { // 锁未被任何线程占有,进行抢占
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); // 将当前线程设置为独占线程,后续为重入做判断
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 锁已被占有,判断是否是相同线程,是否可重入
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// 释放锁的方法
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
复制代码
首先是持有继承了AQS的抽象内部类Sync,通过之前章节的认识,我们知道AQS主要做了获取锁的同步队列的相关维护
在Sync类中定义了锁定的抽象发放,提供非公平锁的获取方法以及锁的释放
非公平锁的获取代码比较简单,主要就是判断当前锁是否已被别的线程持有,具体流程如下图所示
需要说明的是在这里我们可以明白其重入性的原理:
在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功
复制代码
锁的释放的话也是首先判断当前线程是否是持有锁的线程,其次判断state状态,如果不为0表示已多次重入持有锁,需要将所有重入持有的锁全部释放后,才可对锁进行真正释放
下面我们再接着看Sync的具体子实现类公平锁、非公平锁的相关细节以及其如何实现公平性的
// 非公平锁
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
复制代码
NonFairSync主要实现了lock的具体语义,并且在抢占锁的时候调用非公平锁的获取方法,主要就是我们上一章节说的AQS的acquireQueued方法,在里面我们也说了他的非公平性,不清楚的朋友可以看一下了解一下AQS独占锁源码解析
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
复制代码
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 仅当同步队列头尾不相等,且头结点的下一个节点不是当前节点时返回true
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
复制代码
FairSync主要实现了lock的语义,以及在acquire方法中进行回调tryAcquire方法,tryAcquire方法基本上和非公平锁的获取方法一致,唯一区别点就是hasQueuedPredecessors方法,判断当前节点是否还有前置节点,且不为头结点,如果是的话就进入队列进行排队,如果不是的话便进行锁的获取,这边是其公平性的实现
总结
今天就只说明了下ReentrantLock非公平锁及公平锁的独占锁获取,在我们了解的AQS的原理之后看这个还是很轻松的,至于其可中断获取锁以及超时获取锁大家都可以自己根据源码来进行学习,在了解了上一章节AQS中断锁、超时获取锁的源码后,相信看这部分内容就会非常轻松
本文通过阅读源码以及自身的理解所写,其中若有不正之处烦请指正,感谢
作者:fjian_fresh
链接:https://juejin.cn/post/7003708703681544223
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。