標籤:
上一篇文章提到AQS是基於CLH lock queue
,那麼什麼是CLH lock queue,說複雜很複雜說簡單也簡單, 所謂大道至簡:
CLH lock queue其實就是一個FIFO的隊列,隊列中的每個結點(線程)只要等待其前繼釋放鎖就可以了。
AbstractQueuedSynchronizer
是通過一個內部類Node
來實現CLH lock queue的一個變種,但基本原理是類似的。
在介紹Node
類之前,我們來介紹下Spin Lock
,通常就是用CLH lock queue
來實現自旋鎖,所謂自旋鎖簡單來說就是線程通過迴圈來等待而不是睡眠。 Talk 再多不如 show code:
class ClhSpinLock { private final ThreadLocal<Node> prev; private final ThreadLocal<Node> node; private final AtomicReference<Node> tail = new AtomicReference<Node>(new Node()); public ClhSpinLock() { this.node = new ThreadLocal<Node>() { protected Node initialValue() { return new Node(); } }; this.prev = new ThreadLocal<Node>() { protected Node initialValue() { return null; } }; } public void lock() { final Node node = this.node.get(); node.locked = true; // 一個CAS操作即可將當前線程對應的節點加入到隊列中, // 並且同時獲得了前繼節點的引用,然後就是等待前繼釋放鎖 Node pred = this.tail.getAndSet(node); this.prev.set(pred); while (pred.locked) {// 進入自旋 } } public void unlock() { final Node node = this.node.get(); node.locked = false; this.node.set(this.prev.get()); } private static class Node { private volatile boolean locked; }}
上面的代碼中線程巧妙的通過ThreadLocal
儲存了當前結點和前繼結點的引用,自旋就是lock中的while迴圈。 總的來說這種實現的好處是保證所有等待線程的公平競爭,而且沒有競爭同一個變數,因為每個線程只要等待自己的前繼釋放就好了。 而自旋的好處是線程不需要睡眠和喚醒,減小了系統調用的開銷。
public static void main(String[] args) { final ClhSpinLock lock = new ClhSpinLock(); lock.lock(); for (int i = 0; i < 10; i++) { new Thread(new Runnable() { @Override public void run() { lock.lock(); System.out.println(Thread.currentThread().getId() + " acquired the lock!"); lock.unlock(); } }).start(); Thread.sleep(100); } System.out.println("main thread unlock!"); lock.unlock();}
上面代碼的啟動並執行結果應該跟上一篇文章中的完全一樣。
ClhSpinLock
的Node類實現很簡單只有一個布爾值,AbstractQueuedSynchronizer$Node
的實現稍微複雜點,大概是這樣的:
+------+ prev +-----+ +-----+head | | <---- | | <---- | | tail +------+ +-----+ +-----+
- head:頭指標
- tail:尾指標
- prev:指向前繼的指標
- next:這個指標圖中沒有畫出來,它跟prev相反,指向後繼
關鍵不同就是next指標,這是因為AQS中線程不是一直在自旋的,而可能會反覆的睡眠和喚醒,這就需要前繼釋放鎖的時候通過next 指標找到其後繼將其喚醒,也就是AQS的等待隊列中後繼是被前繼喚醒的。AQS結合了自旋和睡眠/喚醒兩種方法的優點。
其中線程的睡眠和喚醒就是用到我下一篇文章將要講到的LockSupport
。
最後提一點,上面的ClhSpinLock
類中還有一個關鍵的點就是lock
方法中注釋的地方:
一個CAS操作即可將當前線程對應的節點加入到隊列中,並擷取到其前繼。
實際上可以說整個AQS架構都是建立在CAS的基礎上的,這些原子操作是多線程競爭的核心地帶,AQS中很多繞來繞去的代碼都是為了 減少競爭。我會在後面AbstractQueuedSynchronizer
源碼分析中做詳細介紹。
Java並發包源碼學習之AQS架構(二)CLH lock queue和自旋鎖