【问题标题】:Cache Not updating for MCS Lock in JavaJava 中的 MCS 锁定未更新缓存
【发布时间】:2012-10-30 16:56:21
【问题描述】:

对于 Java 中的 MCS 锁,我得到了一些非常奇怪的缓存行为。基本上,它最多可用于四个线程(我机器上的内核数),但会卡住更多线程。调试的时候发现程序卡在了线上

while (qnode.locked);

lock() 函数内部。调试时,我可以看到其中一个线程的 QNode 已锁定设置为 false,但我猜这是因为调试器导致缓存更新。我只是将“易失性”扔到所有变量上,绝望地尝试无济于事。这是我正在使用的类:

class MCSLock
{
private volatile AtomicReference<QNode> tail;
private volatile ThreadLocal<QNode> myNode;

public MCSLock()
{
    tail = new AtomicReference<QNode>(null);
    myNode = new ThreadLocal<QNode>()
            {
                protected QNode initialValue() { return new QNode(); }
            };
}

public void lock()
{
    QNode qnode = myNode.get();
    QNode pred = tail.getAndSet(qnode);
    if (pred != null)
    {
        qnode.locked = true;
        pred.next = qnode;
        while (qnode.locked);
    }
}

public void unlock()
{
    QNode qnode = myNode.get();
    if (qnode.next == null)
    {
        if (tail.compareAndSet(qnode, null)) return;
        while (qnode.next == null);
    }
    qnode.next.locked = false;
    qnode.next = null;
}

private class QNode
{
    volatile boolean locked = false;
    volatile QNode next = null;
}
}

【问题讨论】:

  • 如果您将QNode 更改为使用AtomicBooleanAtomicReference 是否有效?
  • 我对 Java 中的并发性知之甚少,无法自信地说出发生了什么,我怀疑几乎所有 SO 上的人都是如此。您可能希望将其发送到 concurrency-interest 邮件列表,Java 并发世界的伟大与美好汇聚于此。
  • @msandiford 遗憾的是,它没有:(
  • 刚刚测试过,它对我来说工作正常,最多 10 个线程。您也可以发布失败的测试代码吗?你用的是哪个JDK?
  • @msandiford 我的测试代码相当长,但这是一般的想法:使用 n 个线程计数 1 到 1200000,其中 n 从 1 到 6。如果您不使用大数字,或者您没有足够的争用,那么代码可能会起作用。这里的想法是确保它在压力下工作。

标签: java multithreading caching locking volatile


【解决方案1】:

我也遇到了同样的问题。 unlock() 方法有一个小错误。在将 false 值设置为下一个节点的锁定变量之前,我们需要等到该值变为 true 才能进入 lock 方法。 我添加了

 
while (!qnode.next.locked) {}
    

    qnode.next.locked = false;
    
进入 unlock() 方法,问题就跑了!

【讨论】:

  • 自从我上次使用 Java 以来已经有很长时间了,但我认为这没有必要。在 lock 函数内部,pred.next 直到 qnode.locked 设置为 true 之后才会设置。 IE。在qnode.next == null 上旋转应该 可以解决问题。当然,这是基于我的假设,即volatile 保证 Java 中的顺序一致性(IIRC 确实如此)。 IE。由于优化,这两个操作不会乱序发生
【解决方案2】:

您的代码有效!但是,我的猜测是您的系统不足以快速执行 >4 个线程。

在双核 i5 上运行,在 5 个线程上执行挂起

在四核超频 i7 上运行,按预期在 5 个线程上完成。哇!

【讨论】:

  • 这是一个很好的评论,有助于确认提问者的怀疑。然而,这并没有回答隐含的问题,即如何使项目仅使用 4 个可用的活动线程。考虑将其重新定位为对该问题的评论。
  • 嗯,这很奇怪。感谢您为我仔细检查!我在我的机器上用 4.6 GHz 的 OC'd i5 运行它,所以这里肯定发生了一些奇怪的事情。也许 i7 有更好、更一致的缓存行为?哦,好吧,至少这不是我的错:)
【解决方案3】:

while (qnode.locked); 在我看来什么也没做。也许你的意思是

    do {
        pred.next = qnode;
        // update qnode
       } while (qnode.locked);

【讨论】:

  • 不,那部分有效。这个想法是在该线程的 QNode 上旋转。 volatile 关键字应该强制缓存更新。我已经尝试过类似的方法,就像我说的,它适用于多达 4 个线程(包括 4 个线程)
  • 通常构造是while(!available) {lock.wait();},但您的代码不会放弃锁定。如果您构建自己的锁实现,我建议将 AbstractQueuedSynchronizer 子类化并将其用作辅助类。
  • 这里的重点是实现一个 MCS 锁。语法等是正确的,Java 只是没有更新缓存。还有,为什么要用锁来实现锁???
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-19
  • 1970-01-01
相关资源
最近更新 更多