一、情景
假设当前有三个线程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中的公平锁只有在等待队列中存在等待节点(不包括虚节点)的时候,才是真正意义上的公平锁。
————————————————
版权声明:本文为CSDN博主「NoobNoob」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39054532/article/details/116651035