最近在看關于線程鎖的算法,做一個整理。資料出自于《The Art of Multiprocessor Programming》,算是一個讀書筆記吧。示范代碼基于JAVA。
第一章是Craig, Landin, and Hagersten (CLH) locks。先上CLHLock的實現邏輯:
public class CLHLock implements Lock {
private final AtomicReference tail;
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();
}
@Override
public void lock() {
QNode node = myNode.get();
node.locked = true;
QNode pred = tail.getAndSet(node);
myPred.set(pred);
while (pred.locked) {
}
}
@Override
public void unlock() {
QNode node = myNode.get();
node.locked = false;
myNode.set(myPred.get());
}
private static class QNode {
volatile boolean locked;
}
}
CLH Lock是一個自旋鎖,能確保無饑餓性,提供先來先服務的公平性。
鎖本身被表示成QNode的虛擬鏈表。共享的tail保存申請的最后的一個申請lock的線程的myNode域。每個申請lock的線程通過一個本地線程變量pred指向它的前驅。另外一個本地線程變量myNode的locked保存本身的狀態,true:正在申請鎖或已申請到鎖;false:已釋放鎖。每個線程通過監視自身的前驅狀態,自旋等待鎖被釋放。釋放鎖時,把自身myNode的locked設為false,使后繼線程獲的鎖,并回收前驅的QNode,等待下次使用,因為自身的QNode可能被tail獲后繼引用,而前驅不再使用老的QNode。
該算法也是空間有效的,如果有n個線程,L個鎖,每個線程每次只獲取一個鎖,那么需要的存儲空間是O(L+n),n個線程有n個myNode,L個鎖有L個tail。這種算法有一個缺點是在NUMA系統架構下性能表現很差,因為它自旋的locked是前驅線程的,如果內存位置較遠,那么性能會受到損失。但是在SMP這種cache一致性的系統架構上表現良好。






