一、情景
假设当前有三个线程A、B、C
,分别取调用公平锁的
lock.lock();
假设线程A
一马当先,先获取到锁,此时state == 1
。
然后线程B
,也来到了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()) {
//重入锁的代码
...
}
return false;
}
公平锁与非公平锁的区别就是在tryAcquire
中会判断是否有先驱节点,也就是方法hasQueuedPredecessors
public final boolean hasQueuedPredecessors() {
//其实这个赋值顺序也是很有讲究的,倒过来有可能会导致空指针
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
此时tail
和head
都null
,所以肯定这个方法返回false
线程B
回到tryAcquire
中执行cas_state
方法,由于A
还没有释放锁,所以肯定获取不到,最终返回false
,需要加入同步队列。
在addWaiter
中,由于tail == null
直接进入enq
方法。
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
//初始化
if (compareAndSetHead(new Node()))......①
tail = head;........................②
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
①和②便是重点。
Scenario 1
当线程B
执行到①,此时head
有值,但是tail
还是为null
此时线程C
也执行到hasQueuedPredecessors
Node t = null;
Node h = new Node();
此时 h != t && ((s = h.next) == null) 为true
因此线程C
不能插队,也要加入等待队列。
Scenario 2
当线程B
执行到②,此时head
有值,且head == tail
此时线程C
也执行到hasQueuedPredecessors
Node t ==h
此时 h != t 为false 短路直接返回
因此线程C
可以插队,去执行cas_state
方法
假设在执行cas
方法之前,线程A
已经释放了锁,那么线程C
就可以插队,先于B
抢到锁。
tail 和 head 赋值小tips
如果head
先于tail
赋值
public final boolean hasQueuedPredecessors() {
Node h = head; //如果此时head还没有初始化,获得的是null,赋值完后失去时间片
Node t = tail; //此时head完成初始化,且tail != null
Node s;
return h != t && // h != t 成立 没有短路
//h == null 因此h.next会产生NPE
((s = h.next) == null || s.thread != Thread.currentThread());
}
总结
ReentrantLock中的公平锁只有在等待队列中存在等待节点(不包括虚节点)的时候,才是真正意义上的公平锁。