基本介绍
对于ReentrantLock(重入锁),是常用的Lock接口的一个实现,最主要的是了解他的重入性和公平锁/非公平锁,还有用他于synchronized机械能对比,下面进行具体介绍。
重入性实现原理
重入性有2个基本特点:
- 在线程获取锁的时候,如果锁已经存在且锁还是当前线程的,那可以直接再次获取成功
- 由于锁可以被同一线程获取n次,在释放时同样要释放n次才能把锁完全释放开。
许多同步组件都是通过重写AQS的方法来实现自己的同步功能的,下面以ReentrantLock的非公平锁的源码来解析其重入的实现。核心方法nonfairTryAcquire
:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//1. 如果该锁未被任何线程占有,该锁能被当前线程获取
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//2.若被占有,检查占有线程是否是当前线程
else if (current == getExclusiveOwnerThread()) {
// 3. 再次获取,计数加一
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
首先是判断当前是否存在锁,如果不存在,那自然可以获取。
如果存在,而且占有锁的还是当前这个线程,那就可以重入,同步状态(锁的计数器)+1。
对于释放,也是同样的道理,核心方法tryRelease
:
protected final boolean tryRelease(int releases) {
//1. 同步状态减1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//2. 只有当同步状态为0时,锁成功被释放,返回true
free = true;
setExclusiveOwnerThread(null);
}
// 3. 锁未被完全释放,返回false
setState(c);
return free;
}
用一个变量free来表示锁是否被完全释放,只有当同步状态(锁的计数器)为0时才会设置为true,否则还是上锁的状态。
公平锁和非公平锁
设置公平性
ReentrantLock支持公平锁和非公平锁,默认是非公平的(无参构造方法),也可以用一个带有boolean值的构造方法来指定公平性。
// 默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// true为公平锁,false为非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平性的意思就是,如果是公平锁,那么线程获取锁的顺序就是按它们请求时的顺序,维护来一个队列进行排队,FIFO;如果是非公平锁,则没有进行排队,线程发起请求时就立刻可以去尝试获取锁。
源码解析
上面的nonfairTryAcquire
就是非公平锁的源码来,下面我们看一下公平锁tryAcquire
的:
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;
}
}
其实大部分逻辑和非公平锁是相似的,不同在于公平锁在判断到当前资源的同步状态为0后,还多判断来一次hasQueuedPredecessors()
,这个方法用于判断当前节点在同步队列中是否有前驱节点,也就是当前线程是否位于同步队列的队头(判断时取反)。如果是才能进行下面的逻辑来获取资源的锁,否则根据公平性是会获取失败的。
对比
- 公平锁每次都只能让位于同步队列中的第一个线程进行获取资源,保证请求资源时间上的绝对顺序;而非公平锁可能产生刚刚释放锁的线程又立刻拿到锁了,导致其他线程迟迟拿不到,造成了“
饥饿
”现象。 - 公平锁为了保证时间上的顺序,往往后涉及频繁的上下文切换,造成比较大的开销。而非公平锁则会降低一定的上下文切换,提高了性能,所以ReentrantLock默认是非公平的,保证系统更大的吞吐量。