JVM的ReentrantReadWriteLock鎖瞭解一下

個人博客請訪問 http://www.x0100.top    

概念

顧名思義,ReentrantReadWriteLock名爲“可重入讀寫鎖”,它維護兩個鎖:讀鎖和寫鎖。在沒有寫鎖的情況下,讀鎖允許多個線程同時訪問,而寫鎖是獨佔的,

ReentrantReadWriteLock主要特性有以下幾個:

  • 公平性。支持公平鎖和非公平鎖,默認是非公平鎖。非公平鎖比公平鎖有更高的吞吐量。

  • 可重入。允許讀鎖可寫鎖可重入。寫鎖可以獲得讀鎖,讀鎖不能獲得寫鎖。讀寫鎖最多支持65535個遞歸寫入鎖和65535個遞歸讀取鎖(這裏有個需要注意的地方,ReentrantLock的可重入次數爲2^32-1,而ReentrantReadWriteLock可重入次數爲2^16-1=65535,爲啥呢?這是因爲ReentrantReadWriteLock將AQS中的state域分成了兩部分,讀鎖和寫鎖各佔16位,具體可以往下看)。

  • 鎖降級。在獲得寫鎖的情況下再獲得讀鎖(這和寫鎖是獨佔的並不衝突,這裏是指一個線程可以活得寫鎖再獲得讀鎖,獨佔是指多個線程之間),然後釋放寫鎖稱爲鎖降級,反之稱爲鎖升級。允許寫鎖降低爲讀鎖,反之不允許。

使用

ReentrantReadWriteLock可以用來提高某些集合的併發性能。當集合比較大,並且讀比寫頻繁時,可以使用該類。

讀寫鎖的簡單示例如下:

class Cache{
    static Map<String, Object> map = new HashMap<String, Object>();
    static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    static Lock r = rwLock.readLock();
    static Lock w = rwLock.writeLock();
    //獲取一個key對應的value
    public static final Object get(String key){
        r.lock();
        try {
            return map.get(key);
        }finally {
            r.unlock();
        }
    }
    //設置key對應的value值
    public static final Object put(String key, Object value){
        w.lock();
        try {
            return map.put(key, value);
        }finally {
            w.unlock();
        }
    }
    //清空所有內容
    public static final void clear(){
        w.lock();
        try {
            map.clear();
        }finally {
            w.unlock();
        }
    }
}

鎖降級的示例如下:

class LockDec{
    static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    static Lock readLock = rwLock.readLock();
    static Lock writeLock = rwLock.writeLock();
    public volatile boolean update = false;
    public void processData(){
        readLock.lock();
        if (!update){
            //必須先釋放讀鎖
            readLock.unlock();
            //鎖降級從寫鎖獲取到開始
            writeLock.lock();
            try {
                if (!update){
                    //準備數據的流程
                    update = true;
                }
                readLock.lock();
            }finally {
                writeLock.unlock();
            }
            //鎖降級完成,寫鎖降級爲讀鎖
        }
        try {
            //使用數據的流程
        }finally {
            readLock.unlock();
        }
    }
}

當數據發生變更後 ,update變量被設置爲true,此時所有訪問processData()方法的線程都能感知到變化。

基本原理

ReentrantReadWriteLock底層也是使用AQS實現的,但是AQS中只有一個state域,這樣就有了幾個問題:

  • AQS只有一個狀態,那麼如何表示 多個讀鎖 與 單個寫鎖 呢?

  • ReentrantLock 裏,狀態值表示重入計數,現在如何在AQS裏表示每個讀鎖、寫鎖的重入次數呢?

  • 如何實現讀鎖、寫鎖的公平性呢?

ReentrantReadWriteLock的解決辦法總結起來有以下幾個:

  • 將state域按位分成兩部分,高位部分表示讀鎖,低位表示寫鎖,由於寫鎖只有一個,所以寫鎖的重入計數也解決了,這也會導致寫鎖可重入的次數減小(前面提到的2^16-1)。

  • 讀鎖是共享鎖,可以同時有多個,那麼只靠一個state來計算鎖的重入次數是不行的。ReentrantReadWriteLock是通過一個HoldCounter的類來實現的,這個類中有一個count計數器,同時該類通過ThreadLocal關鍵字被修飾爲線程私有變量,那麼每個線程都保留一份對讀鎖的重入次數。

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