ReentrantLock原理(源码解析)
概括
ReentrantLock,是Lock接口的实现类,我们查看其内的一些方法
我们可以发现其内大部分方法都是通过sync对象来实现的,查看sync:
如上图,sync是一个抽象类,继承了AQS,而AQS的主要实现则是通过一个共享变量以及先进先出的队列实现,如下:
AQS实现
由volatile修饰的state,表示共享资源的状态,当其为0时表示资源空闲,>0时表示资源被锁定
而若有线程想要获取锁,就调用以上方法尝试使用CAS修改state变量,修改成功则表示成功获取锁,如果获取锁失败,线程就会被包装成结点加入先进先出的队列中,在AQS中该队列是通过双向链表实现的,如以下方法:
构造方法
我们已经知道大概知道了AQS的基本实现原理,而sync又继承了AQS。 查看其构造方法:
scss
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
其包括两个构造方法,第一个构造方法不传入参数值,默认创建非公平锁,而第二个构造方法会根据传入的参数决定创建非公平锁还是公平锁,我们来看看什么是公平锁和非公平锁。
可以很清楚的看到非公平锁和公平锁都继承了Sync,并且它们存在两个相同的方法不同的具体实现。
lock方法
当调用lock方法时,会调用sync变量的lock方法,而Lock只是一个抽象类,调用时会根据不同的子类执行不同的方法
公平锁执行lock方法
公平锁会执行如下的方法:
scss
public final void acquire(int arg) {
if (
// 尝试获取锁。。。(1)
!tryAcquire(arg) &&
//如果获取锁失败将执行该方法.。。。(2)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
查看(1):
java
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
//获取状态码state
int c = getState();
//如果状态码为0表示锁是空闲的
if (c == 0) {
// 判断等待队列是否为null
if (!hasQueuedPredecessors() &&
//如果为null线程通过CAS修改state变量
compareAndSetState(0, acquires)) {
//如果修改成功设置持有锁的线程返回true
setExclusiveOwnerThread(current);
return true;
}
}
// 如果当前的线程就是持有锁的线程,就表示该线程重入这个锁
else if (current == getExclusiveOwnerThread()) {
//修改state变量
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded")
//通过CAS将state修改为重入的次数
setState(nextc);
return true;
}
return false;
}
}
我们进入以上代码的一些方法中查看:
该方法主要是判断队列中有没有其他要竞争锁的线程,如果存在当前线程就需要入队按照竞争锁的顺序来获取锁。
ini
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return
//如果等待队列的头节点就是尾结点,因为头节点是当前持有锁的线程,那么此时没有其他竞争锁的线程,当前线程有资格竞争锁
h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
设置当前持有锁的线程:
arduino
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
如果方法1返回true,则表明线程成功获取到了锁,如果返回false,则表示竞争锁失败,就会执行方法2,方法2中的参数调用了一个方法该方法会将当前线程包装成一个结点放到等待队列的尾部
ini
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
然后执行以下方法,该方法会根据线程访问的顺序也即等待队列的顺序竞争锁
ini
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//线程进入自旋状态
for (;;) {
//获取当前入队结点的前驱结点
final Node p = node.predecessor();
//当前驱结点为头节点时(即前驱结点当前持有了锁),当前线程才有获取锁的资格,当前线程通过CAS获取锁
if (p == head && tryAcquire(arg)) {
//当前线程获取锁成功后,将当前结点设置为头节点,返回false
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//判断前驱结点的状态,如果状态为撤销,直接跳过
if (shouldParkAfterFailedAcquire(p, node) &&
//判断线程是否被中断
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
scss
//通过方法二的返回值,判断当前线程的中断位,可以响应中断
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
公平锁lock流程图
非公平锁执行lock方法
scss
final void lock() {
//直接CAS修改state状态,尝试获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//竞争锁失败
acquire(1);
}
竞争锁失败后的逻辑与公平锁的逻辑差不多,只有第一个方法是不同的
scss
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
与公平锁不同的是,其发现state为0时直接CAS修改state获取锁
ini
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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;
}
当线程获取不到锁时,会与线程竞争公平锁的逻辑相同,会包装为结点放入等待队列,锁定前驱结点,当前驱结点获取锁时才会尝试再次获取锁,而不是一直在等待队列中尝试获取锁,这样太浪费性能了。
线程获取公平锁和非公平锁的区别
线程调用lock方法获取公平锁时,当知道锁未被持有时,首先就会通过CAS修改state变量尝试获取锁,而非空锁则会判断等待队列中是否存在等待锁的线程,如果不存在会尝试获取锁。后面的逻辑两种则相同,加入等待队列,锁定前驱结点,当前驱节点持有锁时,会尝试获取锁。
至于超时获取锁的方法,大家可以看看源码,只比lock方法增加了一个超时的逻辑。
释放锁
释放锁的逻辑相当简单,用state变量减去当前线程重入锁的此时,使用CAS设置state变量即可
java
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
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;
}