背景
SMP(Symmetric Multi-Processor)
對稱多處理器結構,它是相對非對稱多處理技術而言的、應用十分廣泛的並行技術。
- 在這種架構中,一臺計算機由多個CPU組成,並共享記憶體和其他資源,所有的CPU都可以平等地訪問記憶體、I/O和外部中斷。
- 雖然同時使用多個CPU,但是從管理的角度來看,它們的表現就像一臺單機一樣。
- 作業系統將任務佇列對稱地分佈於多個CPU之上,從而極大地提高了整個系統的資料處理能力。
- 但是隨著CPU數量的增加,每個CPU都要訪問相同的記憶體資源,共享資源可能會成為系統瓶頸,導致CPU資源浪費。
NUMA(Non-Uniform Memory Access)
非一致儲存訪問,將CPU分為CPU模組,每個CPU模組由多個CPU組成,並且具有獨立的本地記憶體、I/O槽口等,模組之間可以通過互聯模組相互訪問。
-
訪問本地記憶體(本CPU模組的記憶體)的速度將遠遠高於訪問遠端記憶體(其他CPU模組的記憶體)的速度,這也是非一致儲存訪問的由來。
-
NUMA較好地解決SMP的擴充套件問題,當CPU數量增加時,因為訪問遠地記憶體的延時遠遠超過本地記憶體,系統效能無法線性增加。
CLH鎖
CLH是一種基於單向連結串列的高效能、公平的自旋鎖。申請加鎖的執行緒通過前驅節點的變數進行自旋。在前置節點解鎖後,當前節點會結束自旋,並進行加鎖。
- 在SMP架構下,CLH更具有優勢。
- 在NUMA架構下,如果當前節點與前驅節點不在同一CPU模組下,跨CPU模組會帶來額外的系統開銷,而MCS鎖更適用於NUMA架構。
加鎖邏輯
-
獲取當前執行緒的鎖節點,如果為空,則進行初始化;
-
同步方法獲取連結串列的尾節點,並將當前節點置為尾節點,此時原來的尾節點為當前節點的前置節點。
-
如果尾節點為空,表示當前節點是第一個節點,直接加鎖成功。
-
如果尾節點不為空,則基於前置節點的鎖值(locked==true)進行自旋,直到前置節點的鎖值變為false。
解鎖邏輯
-
獲取當前執行緒對應的鎖節點,如果節點為空或者鎖值為false,則無需解鎖,直接返回;
-
同步方法為尾節點賦空值,賦值不成功表示當前節點不是尾節點,則需要將當前節點的locked=false解鎖節點。如果當前節點是尾節點,則無需為該節點設定。
public class CLHLock {
private final AtomicReference<Node> tail;
private final ThreadLocal<Node> myNode;
private final ThreadLocal<Node> myPred;
public CLHLock() {
tail = new AtomicReference<>(new Node());
myNode = ThreadLocal.withInitial(() -> new Node());
myPred = ThreadLocal.withInitial(() -> null);
}
public void lock(){
Node node = myNode.get();
node.locked = true;
Node pred = tail.getAndSet(node);
myPred.set(pred);
while (pred.locked){}
}
public void unLock(){
Node node = myNode.get();
node.locked=false;
myNode.set(myPred.get());
}
static class Node {
volatile boolean locked = false;
}
}
MCS鎖
MSC與CLH最大的不同並不是連結串列是顯示還是隱式,而是執行緒自旋的規則不同:CLH是在前趨結點的locked域上自旋等待,而MCS是在自己的結點的locked域上自旋等待。正因為如此,它解決了CLH在NUMA系統架構中獲取locked域狀態記憶體過遠的問題。
MCS鎖具體實現規則:
-
a. 佇列初始化時沒有結點,tail=null
-
b. 執行緒A想要獲取鎖,將自己置於隊尾,由於它是第一個結點,它的locked域為false
-
c. 執行緒B和C相繼加入佇列,a->next=b,b->next=c,B和C沒有獲取鎖,處於等待狀態,所以locked域為true,尾指標指向執行緒C對應的結點
-
d. 執行緒A釋放鎖後,順著它的next指標找到了執行緒B,並把B的locked域設定為false,這一動作會觸發執行緒B獲取鎖。
public class MCSLock {
private final AtomicReference<Node> tail;
private final ThreadLocal<Node> myNode;
public MCSLock() {
tail = new AtomicReference<>();
myNode = ThreadLocal.withInitial(() -> new Node());
}
public void lock() {
Node node = myNode.get();
Node pred = tail.getAndSet(node);
if (pred != null) {
node.locked = true;
pred.next = node;
while (node.locked) {
}
}
}
public void unLock() {
Node node = myNode.get();
if (node.next == null) {
if (tail.compareAndSet(node, null)) {
return;
}
while (node.next == null) {
}
}
node.next.locked = false;
node.next = null;
}
class Node {
volatile boolean locked = false;
Node next = null;
}
public static void main(String[] args) {
MCSLock lock = new MCSLock();
Runnable task = new Runnable() {
private int a;
@Override
public void run() {
lock.lock();
for (int i = 0; i < 10; i++) {
a++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(a);
lock.unLock();
}
};
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
}
}