ReentrantLock原理分析
、
概述
可重入的自选锁,支持公平和非公平两种模式
public void lock() {
sync.lock();
}
Sync的实现有NonfairSync 和 FairSync
各自实现 lock 和 tryAcquire来实现不同的加锁机制
加锁原理
非公平锁
使用无参构造的默认就是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
@ReservedStackAccess
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
@ReservedStackAccess
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,其实if中的cas代码和nonfairTryAcquire中的开头是一样的。也就是说一个线程首次尝试获取非公平锁的时候其实是尝试了两次。并非一次失败后就阻塞。
nonfairTryAcquire中忽略了CLH队列的状态,直接通过CAS尝试获取锁,因此是非公平的。
公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
@ReservedStackAccess
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;
}
}
和公平锁最大的区别在于hasQueuedPredecessors,
这个方法限制了如果当前线程不是排队的首位就直接失败,继续进入阻塞队列
非公平锁真的不公平吗
先说结论:线程的首次获取锁是非公平的,但对于进入阻塞队列的线程来说,他们之间又是公平的。
事实上,每次争抢锁的参赛者是所有首次获取锁的线程(准确的说是首两次,原因上文有说)和唯一的一个被唤醒的线程。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
无论是公平锁还是非公平锁,在尝试获取锁失败后,最终都会调用acquireQueued方法。
@ReservedStackAccess
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquireQueued的方法大致分为以下几步
1.如果当前线程是头节点后的节点(可叫做预备节点),则尝试加锁
2. 如果1中加锁失败,则阻塞,等待被唤醒
而唤醒的逻辑在unlock中,每次只能唤醒一个线程
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
因此,即使是非公平锁,一旦被阻塞,那么阻塞中的那些线程互相之间的竞争关系也是公平的。
关于这样设计的原因猜想:
设计者想在保证非公平锁的性能同时尽量减少线程饿死的场景。