我写了以下代码(摘自书籍《多处理器编程的艺术》):
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次操作时,就会发生死锁。我插入了一些打印来调试代码和另一个收集工作线程数据的线程。我发现:
- 有时死锁在A行,有时在B行。
- 在第一种情况下,所有线程都在A行循环。收集数据的其他线程显示,线程正在循环的变量全部为true,除了一个,因此线程应该有可能取得进展!
- 在第二种情况下,除了一个线程在B行循环外,所有线程都在A行循环。收集数据的线程显示qnode.next不为空。
- 没有挨饿线程,因为我检查它们是“主动等待”,插入简单计数器(并且它们逐渐增加)。
测试是在简单的优先队列上进行的。