【问题标题】:Problems in MCS Lock Implementation - JAVAMCS锁实现中的问题 - JAVA
【发布时间】:2018-01-15 16:37:28
【问题描述】:

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

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 不为空。
    • 没有饥饿线程,因为我检查了它们是否正在“活动等待”插入简单计数器(并且它们正在增加)。

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

【问题讨论】:

    标签: java multithreading locking


    【解决方案1】:

    这段代码的问题在于它试图使用ThreadLocal 来实现线程限制。然而,由于链接列表中的QNodes 并通过next 引用和tail 引用操作实例,它打破了该线程限制,并且在QNode 字段上没有其他同步机制的情况下,不能保证线程之间的更改可见性。

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

    类似地,尽管在 lock() 中调用了 pred.next = qnode;,但继续在 B 行循环是看到陈旧值的结果。

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

    【讨论】:

    • 如果我在 QNode 类中使用原子变量,我应该解决问题吗? AtomicBoolean 和 AtomicReference 之类的东西
    • 因为我知道的唯一“技巧”是使用 volatile 变量,但是在 QNode 类的这么多实例中使用它们会导致性能下降吗?
    • 使用原子变量将起作用,并且在这种情况下仅将它们标记为易失性。 Volatile 就足够了,因为改变值永远不会依赖于先前的值。至于性能,原子变量在激烈的争用下表现不佳,在这种情况下,易失性可能会胜出。当典型的使用在低竞争环境中时,原子变量可能会胜出。一如既往:确保并衡量。
    • 谢谢,这只是一个练习,但我会尝试检查在我的测试中 volatile 变量是否会优于 Atomic 变量。
    • 使用 volatile 变量的测试比原子测试花费的时间减少了大约 40%。运行更长的测试,差异减少到 15%,但 volatile var 仍然获胜。 (使用 MCS 锁在我的 8 线程的 macbook 上进行所有测试)
    【解决方案2】:

    如果您在 thread_1 中运行 set pred.next,那么 在 thread_2 中检查 qnode.next 没有同步,thread_2 可能永远无法获得您在 thread_1 中设置的值,因为 memory barrier

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-26
      • 1970-01-01
      • 1970-01-01
      • 2014-08-19
      • 1970-01-01
      • 2019-09-01
      • 2013-04-11
      • 1970-01-01
      相关资源
      最近更新 更多