ReentrantLock核心原理分析
ReentrantLock
显式锁,相对于synchronized隐式锁而言,要想彻底了解ReentrantLock的实现原理,那么就必须理解这三个概念:可重入、公平、非公平
其实前面通过Condition
的详细介绍,应该已经基本了解ReentrantLock
内部的一个具体结构(AQS+Condition),通过同步队列+等待队列,实现了线程间资源的竞争、等待与唤醒。下面在把这张图贴一下:
相信通过这张图,对ReentrantLock的结构就有了一个基本的认知,无非就是通过Lock引用了一个AQS(中的state状态),通过改变竞争这个state达到锁资源的获取。
简单看下核心代码结构
public class ReentrantLock implements Lock {
private final Sync sync; //AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
// ...
}
// 非公平
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);
}
}
// 公平
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;
}
}
// 获取锁
void lock();
// 获取锁,可中断
void lockInterruptibly() throws InterruptedException;
// 获取锁,非公平
boolean tryLock();
// 获取锁,设置超时
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 锁是否被当前线程占有
boolean isHeldByCurrentThread()
Condition newCondition();
}
以上就是ReentrantLock
的核心代码结构了,其中Sync、NonfairSync、FairSync如何使用后面详细分析。
那么接下来详细分析一下上面涉及到的三个概念:可重入、公平锁、非公平锁。
1. 锁重入
其实可重入特性对于显示的ReentrantLock
来说特别容易理解,那就是如果某个线程获取到锁(持有AQS的state)时,再次获取锁不会阻塞。那就是当state状态不为0时,如果当前持有锁的线程是自身(通过isHeldByCurrentThread() 判断)
,那就继续操作state(state++)。
同理,重入之后,需要对等释放state(state--)次数,直到state==0才能释放锁
看下代码
// 尝试获取锁
public boolean tryLock() {
// 非公平获取
return sync.nonfairTryAcquire(1);
}
// 锁获取逻辑
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果等于0,说明还没有其他线程获取锁资源,直接CAS设置获取
if (c == 0) {
if (compareAndSetState(0, acquires)) {
// 将当前锁设置为自己
setExclusiveOwnerThread(current);
return true;
}
}
// 否则判断获取当前持有锁的线程是否是自己,如果是,则进行重入,state+=acquires
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;
}
过程非常简单,无非就是将state进行加法设置。
接下来继续看看释放锁过程
// 触发释放逻辑
public void unlock() {
sync.release(1);
}
// 操作释放
protected final boolean tryRelease(int releases) {
// 先执行state的减法操作(其实可以下面的判断是否是当前线程放在前面)
int c = getState() - releases;
// 如果持有锁的线程不是当前线程,抛异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 因为涉及重入特性,也就是只有state一直减到0才能算完全释放
boolean free = false;
// 所以如果c==0,那就释放占有标识,同时设置state=0,同时返回true。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
释放逻辑也一目了然,无非就是:如果当前线程是持有锁线程,就尝试state减法操作,如果减到0了就认为彻底释放了锁,否则依然持有。
到这里,获取锁、释放锁、锁重入的逻辑就讲完了。
2. 非公平锁
// 默认创造的是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 当fair==true时,得到的是公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
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);
}
}
非公平锁的获取逻辑在介绍锁重入的时候介绍过了,无非就是直接判断state状态
是否可被获取,可以的话就尝试获取,没有其他逻辑。这里不再啰嗦了,重点放在公平锁的实现逻辑上。
3. 公平锁
直接看下代码逻辑
// 公平锁
static final class FairSync extends Sync {
// 获取锁资源
final void lock() {
acquire(1);
}
// 通过AQS绕一圈回到这里,获取所资源的真正逻辑
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果state==0,还需要对同步队列进行进一步判断(这里不是等待队列哦,因为等待队列中的线程需要等待唤醒的,只有进入同步队列的线程才有资格获取锁)
if (c == 0) {
// 判断当前节点前面是否还有节点,如果没有,就尝试CAS设置state=0,如果成功则直接设置锁占有标识,代表获取锁成功,返回true
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
// 否则不做任何操作,else都没有
}
// 如果state != 0, 则判断当前线程是否是自己,如果不是的话就代表当前锁被其他线程占有,那就算了,否则直接进行锁重入操作
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
这里相对于非公平锁主要的差异就在于多了一个同步队列的校验
:
如果state==0,还需要对同步队列进行进一步判断(这里不是等待队列哦,因为等待队列中的线程需要等待唤醒的,只有进入同步队列的线程才有资格获取锁),判断当前节点前面是否还有节点,如果没有,就尝试CAS设置state=0,如果成功则直接设置锁占有标识,代表获取锁成功,返回true
只有在同步队列的第一个节点,才能是下一个可获取锁资格的线程节点,因此保证了所获取的公平性,其实也很简单哈。
4. 引申
非公平锁
只需要校验state,也代表着释放锁的线程也可以立刻获取锁,这有可能导致其他线程很难获取到锁的现象,这就是"锁饥饿"
公平锁
在非公平锁的基础上增加了同步队列的校验,保证只有队列的首节点才能获取锁,从而会对性能具有一定的影响,因为线程肯定会发生切换。
网友评论