为什么CLH锁需要在Java上一个节点
问题描述:
这是一个典型的CLH锁在java中:为什么CLH锁需要在Java上一个节点
public class CLHLock{
private final AtomicReference tail;
// why we need this node?
private final ThreadLocal myPred;
private final ThreadLocal myNode;
public CLHLock() {
tail = new AtomicReference(new QNode());
myNode = new ThreadLocal() {
protected QNode initialValue() {
return new QNode();
}
};
myPred = new ThreadLocal();
}
public void lock() {
QNode node = myNode.get();
node.locked = true;
QNode pred = tail.getAndSet(node);
// this.myPred == pred
myPred.set(pred);
while (pred.locked) {
}
}
public void unlock() {
QNode node = myNode.get();
node.locked = false;
// this.myNode == this.myPred
myNode.set(myPred.get());
}
private static class QNode {
volatile boolean locked;
}
}
我们为什么需要myPred
节点,只有两个地方使用这个变量:
this.prev.set(pred);
- 列表项
this.node.set(this.prev.get());
,当我们做了,this.prev == this.node == pred
?
也许我们可以实现这样的:
public class CLHLock {
// Node tail
private final AtomicReference<QNode> tail = new AtomicReference<>(new QNode());
// ThreadLocal
private final ThreadLocal<QNode> node = ThreadLocal.withInitial(QNode::new);
public void lock() {
QNode now = node.get();
now.locked = true;
// spin on pre-node
QNode pre = tail.getAndSet(now);
while (pre.locked) {
}
}
public void unlock() {
QNode now = node.get();
now.locked = false;
}
class QNode {
volatile boolean locked = false;
}
}
是什么上述两者的区别?
答
第二个实现是死锁倾向。
假设你有两个线程T1和T2。 T1拥有锁,T2等待T1释放它。
T1.node.locked
是真实的,T2.node.locked
是真实的,尾指向T2.node
和T2是纺纱上pre.locked
,这是T1的节点。
现在T1释放锁定(设置T1.node.locked
为false),并在此之后尝试再次获取它,同时T2被抢占。 T1.node.locked
再次变为真,但尾部为T2.node
,所以T1现在正在等待T2。而T2仍在等待现在被锁定的T1的同一个节点!僵局。
第一个实现通过重复使用不是当前的,但前一个(前驱)节点来保护你,因此这种情况是不可能的:或者前驱是null(那么没有什么可重用)或者不是,那么当节点被重用时它变得解锁。