1. 程式人生 > >並發系列(3)之 CLH、MCS 隊列鎖簡介

並發系列(3)之 CLH、MCS 隊列鎖簡介

bubuko googl reference 簡介 nts dset spi .get 不同

這篇博客主要是作為 AbstractQueuedSynchronizer 的背景知識介紹;平時接觸也非常的少,如果你不感興趣可以跳過;但是了解一下能更加的清楚 AQS 的設計思路;

一、自旋鎖簡介

通常情況下解決多線程共享資源邏輯一致性問題有兩種方式:

  • 互斥鎖:當發現資源被占用的時候,會阻塞自己直到資源解除占用,然後再次嘗試獲取;
  • 自旋鎖:當發現占用時,一直嘗試獲取鎖(線程沒有被掛起的過程,也就沒有線程調度切換的消耗);

對於這兩種方式沒有優劣之分,只有是否適合當前的場景;具體的對比就不在繼續深入了,如果你很感興趣可以查看 《多處理器編程的藝術》 提取碼:rznn ;


但是如果競爭非常激烈的時候,使用自旋鎖就會產生一些額外的問題:

  • 可能導致一些線程始終無法獲取鎖(爭搶的時候必然是當前活躍線程獲得鎖的幾率大),也就是饑餓現象;
  • 因為自旋鎖會依賴一個共享的鎖標識,所以競爭激烈的時候,鎖標識的同步也需要消耗大量的資源;
  • 如果要用自旋鎖實現公平鎖(即先到先獲取),此時就還需要額外的變量,也會比較麻煩;

解決這些問題其中的一種辦法就是使用隊列鎖,簡單來講就是讓這些線程排隊獲取;下面我們介紹常用的兩種,即 CLH 鎖MCS 鎖


二、CLH 鎖

CLH 是 Craig、Landin 和 Hagersten 三位作者的縮寫,具體內容在 《Building FIFO and Priority-Queuing Spin Locks from Atomic Swap》 論文中有詳細介紹,大家可以自行查看;我們 JDK 中 java.util.concurrent.locks.AbstractQueuedSynchronizer

就是根據 CLH 鎖的變種實現的;

簡單實現:

public class CLH implements Lock {
  private final ThreadLocal<Node> preNode = ThreadLocal.withInitial(() -> null);
  private final ThreadLocal<Node> node = ThreadLocal.withInitial(Node::new);
  private final AtomicReference<Node> tail = new AtomicReference<>(new Node());

  private static class Node {
    private volatile boolean locked;
  }

  @Override
  public void lock() {
    final Node node = this.node.get();
    node.locked = true;
    Node pre = this.tail.getAndSet(node);
    this.preNode.set(pre);
    while (pre.locked) ;
  }

  @Override
  public void unlock() {
    final Node node = this.node.get();
    node.locked = false;
    this.node.set(this.preNode.get());
  }
}


技術分享圖片


三、MCS 鎖

同樣 MCS 是 John M. Mellor-Crummey 和 Michael L. Scott 名字的縮寫,具體內容可以在 《Algorithms for Scalable Synchronization on Shared-Memory Multiprocessors》 論文中查看;

簡單實現:

public class MCS implements Lock {
  private final ThreadLocal<Node> node = ThreadLocal.withInitial(Node::new);
  private final AtomicReference<Node> tail = new AtomicReference<>();

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

  @Override
  public void lock() {
    Node node = this.node.get();
    node.locked = true;
    Node pre = tail.getAndSet(node);
    if (pre != null) {
      pre.next = node;
      while (node.locked) ;
    }
  }

  @Override
  public void unlock() {
    Node node = this.node.get();
    if (node.next == null) {
      if (tail.compareAndSet(node, null)) {
        return;
      }
      while (node.next == null) ;
    }
    node.next.locked = false;
    node.next = null;
  }
}


技術分享圖片


總結

  • 以上的代碼我已經測試過,大家可以直接拿下來自行實驗;
  • CLH 鎖和 MCS 鎖區別主要有兩點:1. 鏈表結構的區別;2. 自旋對象的區別,CLH 是在前驅節點上自旋,而 MCS 是在自身節點上自旋;這裏第二點才是最重要的,主要體現在 SMP(Symmetric Multi-Processor)NUMA(Non-Uniform Memory Access) 不同的處理器架構上;這裏大家可以自行 Google;

並發系列(3)之 CLH、MCS 隊列鎖簡介