MCS锁实现中的问题 - JAVA

时间:2018-01-15 16:37:29

标签: java multithreading locking

我编写了以下代码(取自本书"多处理器编程的艺术"):

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行循环。正在收集数据的另一个线程显示线程循环的变量都是真的但是一个,所以线程应该有可能取得进展!
    • 在第二种情况下,除了一个之外的所有线程都在A行循环,另一个在B行循环。数据收集线程显示qnode.next不为空。
    • 因为我检查过他们是&#34;活跃等待&#34;所以没有饥饿的主题。插入简单的计数器(并且它们正在增加)。

测试是在简单的PriorityQueue上完成的。

2 个答案:

答案 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