我编写了以下代码(摘自《多处理器编程的艺术》一书):
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上完成的。
最佳答案
此代码的问题在于,它尝试使用ThreadLocal
来实现线程限制。但是,由于在链接列表中链接了QNodes
并通过next
引用和tail
引用来操作实例,因此它破坏了线程限制,并且由于在QNode
字段上没有其他同步机制,因此无法保证更改在线程之间可见。
尽管在qnode.next.locked = false;
中调用了unlock()
,但继续在A行中循环是看到过时值的结果。
同样,尽管在pred.next = qnode;
中调用了lock()
,但继续在B行循环是看到过时值的结果。
在这两种情况下,另一个线程的QNode
的字段都会发生突变。在前一种情况下qnode.next
,在后一种情况下pred
是另一个线程的QNodes
。