AQS 雜談

一個需求

我想要在代碼層面實現一把鎖。

一個變量

假如是一把排他鎖,獲取鎖,相當於爭奪一份公共資源,即:一個共享變量。

那麼就可以是一個Boolean類型的變量,false就是鎖空閒等待獲取,true就是已經被鎖,爲了在獲取鎖的時候保證原子性,可以使用AtomicBoolean.

這樣就實現了不可重入鎖,那如果想重入呢,就不能用非錯即對的Boolean類型了,可以用integer類型。

相應的,爲了保證獲取鎖的原子性,可以使用AtomicInteger。
而AtomicInteger是一個比較複雜的工具類,而這裏只需要用到一個cas的set方法,所以呢,可以單獨從AtomicInteger裏面把set的方法抽出來,減少對AtomicInteger的依賴的同時,也增加了鎖的靈活性。

阻塞/喚醒

在多個線程在獲取鎖的過程中,沒有獲取到鎖的線程怎麼辦呢?
直接返回失敗丟掉嗎?也不是不可以,起碼有這樣的使用場景,但比較少。更多的是不能丟掉,比如說客戶端的一個請求,要先獲取鎖再獲取資源,沒獲取到鎖就丟掉嗎?顯然不行,這個時候最好的方式就是等待繼續獲取鎖。

這個時候就有了幾個問題,在哪裏等待?如何等?

比較通用的方式就是使用隊列,如果獲取不到鎖,就放到隊列中去阻塞等待。當鎖被釋放之後,立即去喚醒。

那如何等待呢?又怎麼喚醒呢?

  • wait/notify?只能在同步方法或者同步代碼塊中使用,而且notify()方法只能隨機喚醒一個 wait 線程,並不能喚醒特定線程;

  • sleep? 只能睡眠當前線程,睡眠指定時間,無法特定時間喚醒;

這個時候有個專門處理這個問題的工具類就需要被造出來了,LockSupport!
LockSupport的註釋:

Basic thread blocking primitives for creating locks and other synchronization classes.
翻譯:用於創建鎖和其他同步類的基本線程阻塞原語。

看起來,這個類就是幹這個的。它的實現也是使用Unsafe類。

而 aqs(AbstractQueuedSynchronizer)也大致就是這樣的結構。

共享變量:

/**
 * The synchronization state.
 */
private volatile int state;

雙向鏈表隊列:

    static final class Node {
        volatile Node prev;
        volatile Node next;
    }

應用

在JDK中有下面幾個使用場景:

ReentrantLock中根據AQS實現了可重入鎖。
ThreadPoolExecutor中的Work類,實現了不可重入鎖。
CountDownLatch,允許一個或多個線程等待,直到在其他線程中執行的一組操作完成爲止。

引用

下面是幾個比較好的博文:
AbstractQueuedSynchronizer源碼解讀
AQS
阻塞和喚醒線程——LockSupport功能簡介及原理淺析
ThreadPoolExecutor核心實現原理和源碼解析<二>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章