這篇博客主要是作為 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;