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

這篇博客主要是做爲 AbstractQueuedSynchronizer 的背景知識介紹;平時接觸也很是的少,若是你不感興趣能夠跳過;可是瞭解一下能更加的清楚 AQS 的設計思路;java

1、自旋鎖簡介

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

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

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


可是若是競爭很是激烈的時候,使用自旋鎖就會產生一些額外的問題:編程

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

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


2、CLH 鎖

CLH 是 Craig、Landin 和 Hagersten 三位做者的縮寫,具體內容在 《Building FIFO and Priority-Queuing Spin Locks from Atomic Swap》 論文中有詳細介紹,你們能夠自行查看;咱們 JDK 中 java.util.concurrent.locks.AbstractQueuedSynchronizer 就是根據 CLH 鎖的變種實現的;架構

簡單實現:ide

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());
  }
}


clh


3、MCS 鎖

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

簡單實現:ui

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


總結

  • 以上的代碼我已經測試過,你們能夠直接拿下來自行實驗;
  • CLH 鎖和 MCS 鎖區別主要有兩點:1. 鏈表結構的區別;2. 自旋對象的區別,CLH 是在前驅節點上自旋,而 MCS 是在自身節點上自旋;這裏第二點纔是最重要的,主要體如今 SMP(Symmetric Multi-Processor)NUMA(Non-Uniform Memory Access) 不一樣的處理器架構上;這裏你們能夠自行 Google;
相關文章
相關標籤/搜索