我编写了以下代码(摘自《多处理器编程的艺术》一书):

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,但是一个,所以一个线程应该有可能取得进展!
  • 在第二种情况下,除了一个线程外,所有线程都在A行上循环,另一个线程在B行上。数据收集线程显示qnode.next不为null。
  • 没有饥饿的线程,因为我检查了它们正在“ Activity 等待”中插入简单的计数器(并且它们正在增加)。

  • 该测试是在一个简单的PriorityQueue上完成的。

    最佳答案

    此代码的问题在于,它尝试使用ThreadLocal来实现线程限制。但是,由于在链接列表中链接了QNodes并通过next引用和tail引用来操作实例,因此它破坏了线程限制,并且由于在QNode字段上没有其他同步机制,因此无法保证更改在线程之间可见。

    尽管在qnode.next.locked = false;中调用了unlock(),但继续在A行中循环是看到过时值的结果。

    同样,尽管在pred.next = qnode;中调用了lock(),但继续在B行循环是看到过时值的结果。

    在这两种情况下,另一个线程的QNode的字段都会发生突变。在前一种情况下qnode.next,在后一种情况下pred是另一个线程的QNodes

    09-11 17:48