先上部分源码:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
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);
}
}
private volatile int state;
AQS维护一个FIFO队列,用于存储获取同步状态失败的线程组成的节点,先进行获取动作的排在队前。既然是FIFO队列,AQS是如何实现非公平锁的?
注意到:同步队列维持的是唤醒顺序,不是获取锁的顺序,当然这二者在某些情况下可能等价,比如要获取锁的进程均已进入同步队列,那么唤醒顺序就是获得锁的顺序。
但是,对于其他情况,因为lock调用的是acquire模板方法,源码如上,看到在新线程每次获取锁时,先尝试获取锁,只有当失败了才会进入同步队列,这个试的过程就会导致非公平锁的产生,假如有如下场景:
1.A线程持有锁并作为同步队列的head存在,其后依次为BCD三个节点
2.E线程准备获取锁
3.A线程释放锁,同步状态加1,并会唤醒同步队列的后继节点中的线程,也就是B中的线程。
4.BCD自加入同步队列后就一直在进行自旋(检查前驱是否为head并尝试获取锁否则找到安全点并休息),此时从B的角度看,无非又是一次重复多次的自旋。
5.在B自旋检查条件时(前驱是否为head并尝试获取锁),E正好来了,E一上来就直接是
if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
,
此时B执行if (p == head && tryAcquire(arg))
可能满足,E执行tryAcquire(arg)可能满足,就会出现争用。
6.如果E先执行成功(将volatile类型的state减1,那B就会因缓存一致性而无效其缓存而重新从内存中读取,发现state已满,不可获得),B失败,则产生非公平锁(B在同步队列等了好长时间却没有获得锁)
7.要实现公平锁,就要在tryAcquire(arg)中做文章,让新来的线程别瞎试,试之前先看同步队列有没有节点,没有再去尝试获取锁(BCD:没看见A后面我们几个排着队呢,你一新来的别瞎试,到后面排队)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)