【问题标题】:Why CLH Lock need prev-Node in java为什么 CLH 锁需要 java 中的 prev-Node
【发布时间】:2017-09-23 12:02:42
【问题描述】:

这是一个典型的java中的CLH-Lock:

public class CLHLock{  

    private final AtomicReference tail;

    // why we need this node?  
    private final ThreadLocal myPred;  

    private final ThreadLocal myNode;  

    public CLHLock() {  
        tail = new AtomicReference(new QNode());  
        myNode = new ThreadLocal() {  
            protected QNode initialValue() {  
                return new QNode();  
            }  
        };  

        myPred = new ThreadLocal();  
    }  

    public void lock() {  
        QNode node = myNode.get();  
        node.locked = true;  
        QNode pred = tail.getAndSet(node); 

        // this.myPred == pred 
        myPred.set(pred);  
        while (pred.locked) {  
        }  
    }  

    public void unlock() {  
        QNode node = myNode.get();  
        node.locked = false;  

        // this.myNode == this.myPred
        myNode.set(myPred.get());  
    }  

    private static class QNode {  
        volatile boolean locked;  
    }  
}

为什么我们需要myPred节点,只有两个地方用到了这个变量:

  1. this.prev.set(pred);
  2. 列表项this.node.set(this.prev.get());

我们完成后,this.prev == this.node == pred ?

也许我们可以这样实现:

public class CLHLock {
    // Node tail
    private final AtomicReference<QNode> tail = new AtomicReference<>(new QNode());

    // ThreadLocal
    private final ThreadLocal<QNode> node = ThreadLocal.withInitial(QNode::new);

    public void lock() {
        QNode now = node.get();
        now.locked = true;

        // spin on pre-node
        QNode pre = tail.getAndSet(now);
        while (pre.locked) {
        }
    }

    public void unlock() {
        QNode now = node.get();
        now.locked = false;
    }

    class QNode {
        volatile boolean locked = false;
    }
}

以上两者有什么区别?

【问题讨论】:

    标签: java multithreading concurrency synchronized spinlock


    【解决方案1】:

    第二次实现容易死锁。

    假设您有两个线程,T1 和 T2。 T1 拥有锁,T2 等待 T1 释放它。

    T1.node.locked 为真,T2.node.locked 为真,tail 指向T2.node,T2 在pre.locked 上旋转,这是 T1 的节点。

    现在 T1 释放锁(将 T1.node.locked 设置为 false),然后在 T2 被抢占时尝试再次获取它。 T1.node.locked 再次变为真,但尾部是 T2.node,所以 T1 现在正在等待 T2。而 T2 仍在等待 T1 的同一个节点,该节点现在已锁定!死锁。

    第一个实现通过重用不是当前的,而是前一个(前任)节点来保护你免受它的影响,所以这种情况是不可能的:前任是空的(那么没有什么可重用的)或者不是,然后它的节点在它被重用时被重用解锁。

    【讨论】:

      【解决方案2】:

      第二次实施不起作用。 假设线程一先拥有锁,然后它释放锁。尾巴是线程一的节点现在。但是线程一再次获得锁,会发现线程一将永远旋转。无论如何只需稍微改变解锁方法, pred 节点将不是必需的。

      public void unlock() {  
          QNode node = myNode.get();  
          node.locked = false;  
      
          // this.myNode == this.myPred
          myNode.set(new Node());  
      }
      

      它也会起作用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-10-05
        • 1970-01-01
        • 2021-10-23
        • 2021-12-23
        • 2018-02-15
        • 1970-01-01
        • 2016-01-14
        • 2019-11-04
        相关资源
        最近更新 更多