CLH算法實現
CLH隊列中的結點QNode中含有一個locked字段,該字段若為true表示該線程需要獲取鎖,且不釋放鎖,為false表示線程釋放了鎖。結點之間是通過隱形的鏈表相連,之所以叫隱形的鏈表是因為這些結點之間沒有明顯的next指針,而是通過myPred所指向的結點的變化情況來影響myNode的行為。CLHLock上還有一個尾指針,始終指向隊列的最后一個結點。CLHLock的類圖如下所示:

當一個線程需要獲取鎖時,會創建一個新的QNode,將其中的locked設置為true表示需要獲取鎖,然后線程對tail域調用getAndSet方法,使自己成為隊列的尾部,同時獲取一個指向其前趨的引用myPred,然后該線程就在前趨結點的locked字段上旋轉,直到前趨結點釋放鎖。當一個線程需要釋放鎖時,將當前結點的locked域設置為false,同時回收前趨結點。如下圖所示,線程A需要獲取鎖,其myNode域為true,些時tail指向線程A的結點,然后線程B也加入到線程A后面,tail指向線程B的結點。然后線程A和B都在它的myPred域上旋轉,一量它的myPred結點的locked字段變為false,它就可以獲取鎖掃行。明顯線程A的myPred locked域為false,此時線程A獲取到了鎖。

整個CLH的代碼如下,其中用到了ThreadLocal類,將QNode綁定到每一個線程上,同時用到了AtomicReference,對尾指針的修改正是調用它的getAndSet()操作來實現的,它能夠保證以原子方式更新對象引用。
1 public class CLHLock { 2
3 AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode()); 4 ThreadLocal<QNode> myPred; 5 ThreadLocal<QNode> myNode; 6
7 public static class QNode { 8 //注意這個地方 如果不加volatile則會導致線程永遠死循環 9 //關於volatile的用法在我的另外一篇文章 http://www.cnblogs.com/daxin/p/3364014.html
10 public volatile boolean locked = false; 11 } 12
13 public CLHLock() { 14 myNode = new ThreadLocal<QNode>() { 15 protected QNode initialValue() { 16 return new QNode(); 17 } 18 }; 19 myPred = new ThreadLocal<QNode>() { 20 protected QNode initialValue() { 21 return null; 22 } 23 }; 24 } 25
26 public void lock() { 27 QNode qnode = myNode.get(); 28 qnode.locked = true; 29 QNode pred = tail.getAndSet(qnode); 30 myPred.set(pred); 31 while (pred.locked) { 32 //非阻塞算法
33 } 34 } 35
36 public void unlock() { 37 QNode qnode = myNode.get(); 38 qnode.locked = false; 39 myNode.set(myPred.get()); 40 } 41 }
從代碼中可以看出lock方法中有一個while循環,這 是在等待前趨結點的locked域變為false,這是一個自旋等待的過程。unlock方法很簡單,只需要將自己的locked域設置為false即可。
CLH優缺點
CLH隊列鎖的優點是空間復雜度低(如果有n個線程,L個鎖,每個線程每次只獲取一個鎖,那么需要的存儲空間是O(L+n),n個線程有n個myNode,L個鎖有L個tail),CLH的一種變體被應用在了JAVA並發框架中。唯一的缺點是在NUMA系統結構下性能很差,在這種系統結構下,每個線程有自己的內存,如果前趨結點的內存位置比較遠,自旋判斷前趨結點的locked域,性能將大打折扣,但是在SMP系統結構下該法還是非常有效的。一種解決NUMA系統結構的思路是MCS隊列鎖。