Java高級特性加強-鎖

請戳GitHub原文: https://github.com/wangzhiwub...

大數據成神之路系列:

請戳GitHub原文: https://github.com/wangzhiwub...java

Java高級特性加強-集合git

Java高級特性加強-多線程github

Java高級特性加強-Synchronized面試

Java高級特性加強-volatile算法

Java高級特性加強-併發集合框架編程

Java高級特性加強-分佈式數組

Java高級特性加強-Zookeeper安全

Java高級特性加強-JVM網絡

Java高級特性加強-NIO數據結構

公衆號

  • 全網惟一一個從0開始幫助Java開發者轉作大數據領域的公衆號~
  • 公衆號大數據技術與架構或者搜索import_bigdata關注,大數據學習路線最新更新,已經有不少小夥伴加入了~

Java高級特性加強-鎖

本部分網絡上有大量的資源能夠參考,在這裏作了部分整理,感謝前輩的付出,每節文章末尾有引用列表,源碼推薦看JDK1.8之後的版本,注意甄別~

多線程

集合框架

NIO

Java併發容器

    • *

Java中的鎖分類

在讀不少併發文章中,會說起各類各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹就是各類鎖。介紹的內容以下:
公平鎖/非公平鎖
可重入鎖
獨享鎖/共享鎖
互斥鎖/讀寫鎖
樂觀鎖/悲觀鎖
分段鎖
偏向鎖/輕量級鎖/重量級鎖
自旋鎖
上面是不少鎖的名詞,這些分類並非全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計,下面總結的內容是對每一個鎖的名詞進行必定的解釋。
公平鎖/非公平鎖
公平鎖是指多個線程按照申請鎖的順序來獲取鎖。
非公平鎖是指多個線程獲取鎖的順序並非按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會形成優先級反轉或者飢餓現象。
對於Java ReentrantLock而言,經過構造函數指定該鎖是不是公平鎖,默認是非公平鎖。非公平鎖的優勢在於吞吐量比公平鎖大。
對於Synchronized而言,也是一種非公平鎖。因爲其並不像ReentrantLock是經過AQS的來實現線程調度,因此並無任何辦法使其變成公平鎖。

可重入鎖
可重入鎖又名遞歸鎖,是指在同一個線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖。說的有點抽象,下面會有一個代碼的示例。
對於Java ReentrantLock而言, 他的名字就能夠看出是一個可重入鎖,其名字是Re entrant Lock從新進入鎖。
對於Synchronized而言,也是一個可重入鎖。可重入鎖的一個好處是可必定程度避免死鎖。

synchronized void setA() throws Exception{
    Thread.sleep(1000);
    setB();
}

synchronized void setB() throws Exception{
    Thread.sleep(1000);
}

上面的代碼就是一個可重入鎖的一個特色,若是不是可重入鎖的話,setB可能不會被當前線程執行,可能形成死鎖。

獨享鎖/共享鎖
獨享鎖是指該鎖一次只能被一個線程所持有。
共享鎖是指該鎖可被多個線程所持有。

對於Java ReentrantLock而言,其是獨享鎖。可是對於Lock的另外一個實現類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨享鎖。
讀鎖的共享鎖可保證併發讀是很是高效的,讀寫,寫讀 ,寫寫的過程是互斥的。
獨享鎖與共享鎖也是經過AQS來實現的,經過實現不一樣的方法,來實現獨享或者共享。
對於Synchronized而言,固然是獨享鎖。

互斥鎖/讀寫鎖
上面講的獨享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實現。
互斥鎖在Java中的具體實現就是ReentrantLock
讀寫鎖在Java中的具體實現就是ReadWriteLock

樂觀鎖/悲觀鎖
樂觀鎖與悲觀鎖不是指具體的什麼類型的鎖,而是指看待併發同步的角度。
悲觀鎖認爲對於同一個數據的併發操做,必定是會發生修改的,哪怕沒有修改,也會認爲修改。所以對於同一個數據的併發操做,悲觀鎖採起加鎖的形式。悲觀的認爲,不加鎖的併發操做必定會出問題。
樂觀鎖則認爲對於同一個數據的併發操做,是不會發生修改的。在更新數據的時候,會採用嘗試更新,不斷從新的方式更新數據。樂觀的認爲,不加鎖的併發操做是沒有事情的。

從上面的描述咱們能夠看出,悲觀鎖適合寫操做很是多的場景,樂觀鎖適合讀操做很是多的場景,不加鎖會帶來大量的性能提高。
悲觀鎖在Java中的使用,就是利用各類鎖。
樂觀鎖在Java中的使用,是無鎖編程,經常採用的是CAS算法,典型的例子就是原子類,經過CAS自旋實現原子操做的更新。

分段鎖
分段鎖實際上是一種鎖的設計,並非具體的一種鎖,對於ConcurrentHashMap而言,其併發的實現就是經過分段鎖的形式來實現高效的併發操做。
咱們以ConcurrentHashMap來講一下分段鎖的含義以及設計思想,ConcurrentHashMap中的分段鎖稱爲Segment,它即相似於HashMap(JDK7與JDK8中HashMap的實現)的結構,即內部擁有一個Entry數組,數組中的每一個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。
當須要put元素的時候,並非對整個hashmap進行加鎖,而是先經過hashcode來知道他要放在那一個分段中,而後對這個分段進行加鎖,因此當多線程put的時候,只要不是放在一個分段中,就實現了真正的並行的插入。
可是,在統計size的時候,可就是獲取hashmap全局信息的時候,就須要獲取全部的分段鎖才能統計。
分段鎖的設計目的是細化鎖的粒度,當操做不須要更新整個數組的時候,就僅僅針對數組中的一項進行加鎖操做。

偏向鎖/輕量級鎖/重量級鎖
這三種鎖是指鎖的狀態,而且是針對Synchronized。在Java 5經過引入鎖升級的機制來實現高效Synchronized。這三種鎖的狀態是經過對象監視器在對象頭中的字段來代表的。
偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。下降獲取鎖的代價。
輕量級鎖是指當鎖是偏向鎖的時候,被另外一個線程所訪問,偏向鎖就會升級爲輕量級鎖,其餘線程會經過自旋的形式嘗試獲取鎖,不會阻塞,提升性能。
重量級鎖是指當鎖爲輕量級鎖的時候,另外一個線程雖然是自旋,但自旋不會一直持續下去,當自旋必定次數的時候,尚未獲取到鎖,就會進入阻塞,該鎖膨脹爲重量級鎖。重量級鎖會讓其餘申請的線程進入阻塞,性能下降。

自旋鎖
在Java中,自旋鎖是指嘗試獲取鎖的線程不會當即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減小線程上下文切換的消耗,缺點是循環會消耗CPU。

Lock接口

在Lock接口出現以前,Java程序是靠synchronized關鍵字實現鎖功能的。JDK1.5以後併發包中新增了Lock接口以及相關實現類來實現鎖功能。

雖然synchronized方法和語句的範圍機制使得使用監視器鎖更容易編程,而且有助於避免涉及鎖的許多常見編程錯誤,可是有時您須要以更靈活的方式處理鎖。例如,用於遍歷併發訪問的數據結構的一些算法須要使用「手動」或「鏈鎖定」:您獲取節點A的鎖定,而後獲取節點B,而後釋放A並獲取C,而後釋放B並得到D等。在這種場景中synchronized關鍵字就不那麼容易實現了,使用Lock接口容易不少。

Lock接口的實現類:
ReentrantLock , ReentrantReadWriteLock.ReadLock , ReentrantReadWriteLock.WriteLock

AbstractQueuedSynchronizer

當你查看源碼時你會驚訝的發現ReentrantLock並無多少代碼,另外有一個很明顯的特色是:基本上全部的方法的實現實際上都是調用了其靜態內存類Sync中的方法,而Sync類繼承了AbstractQueuedSynchronizer(AQS)。能夠看出要想理解ReentrantLock關鍵核心在於對隊列同步器AbstractQueuedSynchronizer(簡稱同步器)的理解。

在同步組件的實現中,AQS是核心部分,同步組件的實現者經過使用AQS提供的模板方法實現同步組件語義,AQS則實現了對同步狀態的管理,以及對阻塞線程進行排隊,等待通知等等一些底層的實現處理。AQS的核心也包括了這些方面:同步隊列,獨佔式鎖的獲取和釋放,共享鎖的獲取和釋放以及可中斷鎖,超時等待鎖獲取這些特性的實現,而這些實際上則是AQS提供出來的模板方法,概括整理以下:
獨佔式鎖:

void acquire(int arg):
獨佔式獲取同步狀態,若是獲取失敗則插入同步隊列進行等待;
void acquireInterruptibly(int arg):
與acquire方法相同,但在同步隊列中進行等待的時候能夠檢測中斷;
boolean tryAcquireNanos(int arg, long nanosTimeout):
在acquireInterruptibly基礎上增長了超時等待功能,在超時時間內沒有得到同步狀態返回false;
boolean release(int arg):
釋放同步狀態,該方法會喚醒在同步隊列中的下一個節點

共享式鎖:

void acquireShared(int arg):
共享式獲取同步狀態,與獨佔式的區別在於同一時刻有多個線程獲取同步狀態
void acquireSharedInterruptibly(int arg):
在acquireShared方法基礎上增長了能響應中斷的功能
boolean tryAcquireSharedNanos(int arg, long nanosTimeout):
在acquireSharedInterruptibly基礎上增長了超時等待的功能
boolean releaseShared(int arg):共享式釋放同步狀態
ReentrantLock

ReentrantLock重入鎖,是實現Lock接口的一個類,也是在實際編程中使用頻率很高的一個鎖,支持重入性,表示可以對共享資源可以重複加鎖,即當前線程獲取該鎖再次獲取不會被阻塞。在java關鍵字synchronized隱式支持重入性,synchronized經過獲取自增,釋放自減的方式實現重入。與此同時,ReentrantLock還支持公平鎖和非公平鎖兩種方式。那麼,要想完徹底全的弄懂ReentrantLock的話,主要也就是ReentrantLock同步語義的學習:1. 重入性的實現原理;2. 公平鎖和非公平鎖。

重入性的實現原理

要想支持重入性,就要解決兩個問題:1. 在線程獲取鎖的時候,若是已經獲取鎖的線程是當前線程的話則直接再次獲取成功;2. 因爲鎖會被獲取n次,那麼只有鎖在被釋放一樣的n次以後,該鎖纔算是徹底釋放成功。經過這篇文章,咱們知道,同步組件主要是經過重寫AQS的幾個protected方法來表達本身的同步語義。針對第一個問題,咱們來看看ReentrantLock是怎樣實現的,以非公平鎖爲例,判斷當前線程可否得到鎖爲例,核心方法爲nonfairTryAcquire:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //1. 若是該鎖未被任何線程佔有,該鎖能被當前線程獲取
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //2.若被佔有,檢查佔有線程是不是當前線程
    else if (current == getExclusiveOwnerThread()) {
        // 3. 再次獲取,計數加一
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

這段代碼的邏輯也很簡單,具體請看註釋。爲了支持重入性,在第二步增長了處理邏輯,若是該鎖已經被線程所佔有了,會繼續檢查佔有線程是否爲當前線程,若是是的話,同步狀態加1返回true,表示能夠再次獲取成功。每次從新獲取都會對同步狀態進行加一的操做,那麼釋放的時候處理思路是怎樣的了?(依然仍是以非公平鎖爲例)核心方法爲tryRelease:

protected final boolean tryRelease(int releases) {
    //1. 同步狀態減1
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        //2. 只有當同步狀態爲0時,鎖成功被釋放,返回true
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 3. 鎖未被徹底釋放,返回false
    setState(c);
    return free;
}

代碼的邏輯請看註釋,須要注意的是,重入鎖的釋放必須得等到同步狀態爲0時鎖纔算成功釋放,不然鎖仍未釋放。若是鎖被獲取n次,釋放了n-1次,該鎖未徹底釋放返回false,只有被釋放n次纔算成功釋放,返回true。到如今咱們能夠理清ReentrantLock重入性的實現了,也就是理解了同步語義的第一條.

公平鎖與非公平鎖

ReentrantLock支持兩種鎖:公平鎖和非公平鎖。何謂公平性,是針對獲取鎖而言的,若是一個鎖是公平的,那麼鎖的獲取順序就應該符合請求上的絕對時間順序,知足FIFO。ReentrantLock的構造方法無參時是構造非公平鎖,源碼爲:

public ReentrantLock() {
    sync = new NonfairSync();
}

另外還提供了另一種方式,可傳入一個boolean值,true時爲公平鎖,false時爲非公平鎖,源碼爲:

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

在上面非公平鎖獲取時(nonfairTryAcquire方法)只是簡單的獲取了一下當前狀態作了一些邏輯處理,並無考慮到當前同步隊列中線程等待的狀況。咱們來看看公平鎖的處理邏輯是怎樣的,核心方法爲:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
  }
}

這段代碼的邏輯與nonfairTryAcquire基本上一直,惟一的不一樣在於增長了hasQueuedPredecessors的邏輯判斷,方法名就可知道該方法用來判斷當前節點在同步隊列中是否有前驅節點的判斷,若是有前驅節點說明有線程比當前線程更早的請求資源,根據公平性,當前線程請求資源失敗。若是當前節點沒有前驅節點的話,再纔有作後面的邏輯判斷的必要性。公平鎖每次都是從同步隊列中的第一個節點獲取到鎖,而非公平性鎖則不必定,有可能剛釋放鎖的線程能再次獲取到鎖。

公平鎖 VS 非公平鎖

公平鎖每次獲取到鎖爲同步隊列中的第一個節點,保證請求資源時間上的絕對順序,而非公平鎖有可能剛釋放鎖的線程下次繼續獲取該鎖,則有可能致使其餘線程永遠沒法獲取到鎖,形成「飢餓」現象。

公平鎖爲了保證時間上的絕對順序,須要頻繁的上下文切換,而非公平鎖會下降必定的上下文切換,下降性能開銷。所以,ReentrantLock默認選擇的是非公平鎖,則是爲了減小一部分上下文切換,保證了系統更大的吞吐量。

ReentrantReadWriteLock

在併發場景中用於解決線程安全的問題,咱們幾乎會高頻率的使用到獨佔式鎖,一般使用java提供的關鍵字synchronized或者concurrents包中實現了Lock接口的ReentrantLock。它們都是獨佔式獲取鎖,也就是在同一時刻只有一個線程可以獲取鎖。而在一些業務場景中,大部分只是讀數據,寫數據不多,若是僅僅是讀數據的話並不會影響數據正確性(出現髒讀),而若是在這種業務場景下,依然使用獨佔鎖的話,很顯然這將是出現性能瓶頸的地方。針對這種讀多寫少的狀況,java還提供了另一個實現Lock接口的ReentrantReadWriteLock(讀寫鎖)。讀寫所容許同一時刻被多個讀線程訪問,可是在寫線程訪問時,全部的讀線程和其餘的寫線程都會被阻塞。在分析WirteLock和ReadLock的互斥性時能夠按照WriteLock與WriteLock之間,WriteLock與ReadLock之間以及ReadLock與ReadLock之間進行分析。這裏作一個概括總結:

公平性選擇:支持非公平性(默認)和公平的鎖獲取方式,吞吐量仍是非公平優於公平;
重入性:支持重入,讀鎖獲取後能再次獲取,寫鎖獲取以後可以再次獲取寫鎖,同時也可以獲取讀鎖;
鎖降級:遵循獲取寫鎖,獲取讀鎖再釋放寫鎖的次序,寫鎖可以降級成爲讀鎖

要想可以完全的理解讀寫鎖必須可以理解這樣幾個問題:1. 讀寫鎖是怎樣實現分別記錄讀寫狀態的?2. 寫鎖是怎樣獲取和釋放的?3.讀鎖是怎樣獲取和釋放的?咱們帶着這樣的三個問題,再去了解下讀寫鎖。

寫鎖詳解

寫鎖的獲取

同步組件的實現聚合了同步器(AQS),並經過重寫重寫同步器(AQS)中的方法實現同步組件的同步語義。所以,寫鎖的實現依然也是採用這種方式。在同一時刻寫鎖是不能被多個線程所獲取,很顯然寫鎖是獨佔式鎖,而實現寫鎖的同步語義是經過重寫AQS中的tryAcquire方法實現的。源碼爲:

protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. If read count nonzero or write count nonzero
     *    and owner is a different thread, fail.
     * 2. If count would saturate, fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
    Thread current = Thread.currentThread();
    // 1. 獲取寫鎖當前的同步狀態
    int c = getState();
    // 2. 獲取寫鎖獲取的次數
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        // 3.1 當讀鎖已被讀線程獲取或者當前線程不是已經獲取寫鎖的線程的話
        // 當前線程獲取寫鎖失敗
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        // 3.2 當前線程獲取寫鎖,支持可重複加鎖
        setState(c + acquires);
        return true;
    }
    // 3.3 寫鎖未被任何線程獲取,當前線程可獲取寫鎖
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

這段代碼的邏輯請看註釋,這裏有一個地方須要重點關注,exclusiveCount(c)方法,該方法源碼爲:

static int exclusiveCount(int c) { 
       return c & EXCLUSIVE_MASK; 
 }

其中EXCLUSIVE_MASK爲: static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
EXCLUSIVE_MASK爲1左移16位而後減1,即爲0x0000FFFF。而exclusiveCount方法是將同步狀態(state爲int類型)與0x0000FFFF相與,即取同步狀態的低16位。那麼低16位表明什麼呢?根據exclusiveCount方法的註釋爲獨佔式獲取的次數即寫鎖被獲取的次數,如今就能夠得出來一個結論同步狀態的低16位用來表示寫鎖的獲取次數。同時還有一個方法值得咱們注意:

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

該方法是獲取讀鎖被獲取的次數,是將同步狀態(int c)右移16次,即取同步狀態的高16位,如今咱們能夠得出另一個結論同步狀態的高16位用來表示讀鎖被獲取的次數。如今還記得咱們開篇說的須要弄懂的第一個問題嗎?讀寫鎖是怎樣實現分別記錄讀鎖和寫鎖的狀態的,如今這個問題的答案就已經被咱們弄清楚了。
如今咱們回過頭來看寫鎖獲取方法tryAcquire,其主要邏輯爲:當讀鎖已經被讀線程獲取或者寫鎖已經被其餘寫線程獲取,則寫鎖獲取失敗;不然,獲取成功並支持重入,增長寫狀態。

寫鎖的釋放
寫鎖釋放經過重寫AQS的tryRelease方法,源碼爲:

protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //1. 同步狀態減去寫狀態
    int nextc = getState() - releases;
    //2. 當前寫狀態是否爲0,爲0則釋放寫鎖
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    //3. 不爲0則更新同步狀態
    setState(nextc);
    return free;
}

源碼的實現邏輯請看註釋,不難理解與ReentrantLock基本一致,這裏須要注意的是,減小寫狀態int nextc = getState() - releases;只須要用當前同步狀態直接減去寫狀態的緣由正是咱們剛纔所說的寫狀態是由同步狀態的低16位表示的。

讀鎖詳解

讀鎖的獲取
看完了寫鎖,如今來看看讀鎖,讀鎖不是獨佔式鎖,即同一時刻該鎖能夠被多個讀線程獲取也就是一種共享式鎖。按照以前對AQS介紹,實現共享式同步組件的同步語義須要經過重寫AQS的tryAcquireShared方法和tryReleaseShared方法。讀鎖的獲取實現方法爲:

protected final int tryAcquireShared(int unused) {
    /*
     * Walkthrough:
     * 1. If write lock held by another thread, fail.
     * 2. Otherwise, this thread is eligible for
     *    lock wrt state, so ask if it should block
     *    because of queue policy. If not, try
     *    to grant by CASing state and updating count.
     *    Note that step does not check for reentrant
     *    acquires, which is postponed to full version
     *    to avoid having to check hold count in
     *    the more typical non-reentrant case.
     * 3. If step 2 fails either because thread
     *    apparently not eligible or CAS fails or count
     *    saturated, chain to version with full retry loop.
     */
    Thread current = Thread.currentThread();
    int c = getState();
    //1. 若是寫鎖已經被獲取而且獲取寫鎖的線程不是當前線程的話,當前
    // 線程獲取讀鎖失敗返回-1
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        //2. 當前線程獲取讀鎖
        compareAndSetState(c, c + SHARED_UNIT)) {
        //3. 下面的代碼主要是新增的一些功能,好比getReadHoldCount()方法
        //返回當前獲取讀鎖的次數
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    //4. 處理在第二步中CAS操做失敗的自旋已經實現重入性
    return fullTryAcquireShared(current);
}

代碼的邏輯請看註釋,須要注意的是 當寫鎖被其餘線程獲取後,讀鎖獲取失敗,不然獲取成功利用CAS更新同步狀態。另外,當前同步狀態須要加上SHARED_UNIT((1 << SHARED_SHIFT)即0x00010000)的緣由這是咱們在上面所說的同步狀態的高16位用來表示讀鎖被獲取的次數。若是CAS失敗或者已經獲取讀鎖的線程再次獲取讀鎖時,是靠fullTryAcquireShared方法實現的,有興趣能夠看看。

讀鎖的釋放
讀鎖釋放的實現主要經過方法tryReleaseShared,源碼以下,主要邏輯請看註釋:

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    // 前面仍是爲了實現getReadHoldCount等新功能
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    for (;;) {
        int c = getState();
        // 讀鎖釋放 將同步狀態減去讀狀態便可
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            return nextc == 0;
    }
}
鎖降級

讀寫鎖支持鎖降級,遵循按照獲取寫鎖,獲取讀鎖再釋放寫鎖的次序,寫鎖可以降級成爲讀鎖,不支持鎖升級,關於鎖降級下面的示例代碼摘自ReentrantWriteReadLock源碼中:

void processCachedData() {
        rwl.readLock().lock();
        if (!cacheValid) {
            // Must release read lock before acquiring write lock
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try {
                // Recheck state because another thread might have
                // acquired write lock and changed state before we did.
                if (!cacheValid) {
                    data = ...
            cacheValid = true;
          }
          // Downgrade by acquiring read lock before releasing write lock
          rwl.readLock().lock();
        } finally {
          rwl.writeLock().unlock(); // Unlock write, still hold read
        }
      }
 
      try {
        use(data);
      } finally {
        rwl.readLock().unlock();
      }
    }
}

參考文章和書籍:

《Java併發編程的藝術》
《實戰Java高併發程序設計》
https://blog.csdn.net/qq_3433...
https://www.jianshu.com/p/a5f...
https://www.jianshu.com/p/506...

請戳GitHub原文: https://github.com/wangzhiwubigdata/God-Of-BigData

                   關注公衆號,內推,面試,資源下載,關注更多大數據技術~
                   大數據成神之路~預計更新500+篇文章,已經更新50+篇~
相關文章
相關標籤/搜索