我编写了以下代码(取自本书"多处理器编程的艺术"):
package Chapter7;
import java.util.concurrent.atomic.AtomicReference;
public class MCSLock implements Lock {
AtomicReference<QNode> tail;
ThreadLocal<QNode> myNode;
public MCSLock() {
tail = new AtomicReference<>(null);
myNode = new ThreadLocal<QNode>() {
@Override
protected QNode initialValue() {
return new QNode();
}
};
}
@Override
@SuppressWarnings("empty-statement")
public void lock() {
QNode qnode = myNode.get();
QNode pred = tail.getAndSet(qnode);
if (pred != null) {
qnode.locked = true;
pred.next = qnode;
while (qnode.locked); // line A
}
}
@Override
@SuppressWarnings("empty-statement")
public void unlock() {
QNode qnode = myNode.get();
if (qnode.next == null) {
if (tail.compareAndSet(qnode, null)) {
return;
}
while (qnode.next == null); // line B
}
qnode.next.locked = false;
qnode.next = null;
}
class QNode {
boolean locked = false;
QNode next = null;
}
}
如果我用少量线程和操作测试它,这似乎有效,但每当我尝试使用8个线程和每个受此锁保护的线程1000个操作时,它就会陷入死锁。我插入了来打印调试代码和另一个收集工作线程数据的线程。我发现:
测试是在简单的PriorityQueue上完成的。
答案 0 :(得分:2)
此代码的问题在于它尝试使用ThreadLocal
来实现线程限制。但是,由于链接列表中的QNodes
和通过next
引用和tail
引用操作实例,它会破坏该线程限制,并且在没有其他同步机制的情况下QNode
字段,线程之间无法保证更改的可见性。
继续在A行循环是看到陈旧价值的结果,尽管在qnode.next.locked = false;
中调用了unlock()
。
同样,尽管在pred.next = qnode;
中调用lock()
,但仍然在B行继续循环是看到陈旧价值的结果。
在这两种情况下,另一个线程的QNode
字段都会发生变异。在前一种情况qnode.next
中,后一种情况pred
是另一种情况的QNodes
。
答案 1 :(得分:1)
如果您在 thread_1 中运行设置pred.next
,那么
检查 thread_2 中的qnode.next
没有同步, thread_2
可能永远无法获得您在 thread_1 中设置的值,因为
的 memory barrier 强>