【併發】5、6、抽象隊列同步器AQS應用ReentrantLock

在這裏插入圖片描述


Lock的特性:

  • 可重入
    這個特性就是加了幾次鎖也要釋放幾次鎖
    synchronized也有可重入性

  • 公平性與非公平性

實現鎖的核心:

  • CAS
    保證加鎖永遠只有一個線程能夠成功
  • LockSupport
    對線程阻塞和喚醒
  • 自旋
    cas加鎖失敗,則這些線程就要自旋,阻塞住 就不佔用cpu資源
  • queue
    放阻塞的那些線程,用隊列是因爲隊列的FIFO可以保證公平性

CAS

CAS Compare and swap
能夠保證不管併發有多高,都能保證這個執行的原子性

通過cas算法去加鎖,這樣保證加鎖永遠只有一個線程能夠成功

CAS 工作原理

主內存中有一個expect=0,如果兩個線程都要去修改expect,則CAS就會讓這兩個線程都複製一份到自己線程中,然後再用另一個變量比如是refresh存修改後的值, 線程A和線程B就都去和主內存比較 如果expect的值相等,則將refresh中的值修改主內存的值,如果expect值不等,則不改主內存的值。
在這裏插入圖片描述

CAS的使用

Unsafe類中提供了三個關於CAS的方法:
在這裏插入圖片描述

線程阻塞就不會佔用cpu的資源
通過java的LockSupport.part() 就可以阻塞線程

等到線程Aunlock()的時候 喚醒線程B 這樣線程B就可以接着去執行 去加鎖

LockSupport.park和unpark的使用

在這裏插入圖片描述

ReentrantLock

ReentrantLock定義在java.util.concurrent包下

java.util.concurrent包中很多功能就是基於AQS框架的

公平鎖和非公平鎖實現的特性就是通過 在ReentrantLock內部定義了一個Sync的內部類,該類繼承AbstractQueuedSynchronized,對 該抽象類的部分方法做了實現;
並且還定義了兩個子類:
這兩個類都繼承自Sync,也就是間接繼承了AbstractQueuedSynchronized,所以這一個 ReentrantLock同時具備公平與非公平特性。

  • FairSync 公平鎖的實現
  • NonfairSync 非公平鎖的實現

AQS源碼分析

超類中有一個變量:記錄當前獲取鎖的線程是誰
在這裏插入圖片描述

AQS類下的變量state狀態器,表明當前同步器的狀態
state爲0表明是無鎖狀態,沒有被任何一個線程持有
在這裏插入圖片描述
隊列的創建:
AQS會基於Node構建一條隊列,Node是AQS的內部類,
Node的重要屬性:




  • Node的prev和next屬性用來形成雙向鏈表。
  • Node的thread屬性用來保持對線程的引用
  • Node的SHARED屬性表示鎖是共享鎖,Semaphore鎖是共享的
  • Node的EXCLUSIVE屬性表示鎖是互斥的,ReentrantLock需要鎖是互斥的
  • Node的waitestate屬性表示當前結點的生命狀態(信號量)
    • SIGNAL=-1 可被喚醒
    • CANCELLED=1 代表出現異常,中斷引起的,需要廢棄結束
    • CONDITION=-2 條件等待
    • PROPAGATE=-3 傳播
    • 0 是初始狀態Init狀態

在這裏插入圖片描述

在這裏插入圖片描述
AQS的head屬性會指向Node的頭部,tail屬性會指向Node的尾部

形成的雙向隊列:
在這裏插入圖片描述

公平鎖:
FairSync的lock()方法調用acquire()方法
在這裏插入圖片描述
acquire()方法:


  • tryAcquire 嘗試去獲取鎖:
    在這裏插入圖片描述

    • 通過Thread.currentThread()獲取當前線程的引用
    • getState() 獲取同步器的狀態
      • c==0 無鎖狀態
        • 對於公平鎖,首先要判斷是否有線程在排隊
          通過判斷隊列的隊頭隊尾是否一樣在這裏插入圖片描述

        • 沒有線程排隊,才用CAS去加鎖,加鎖其實就是把改同步器的狀態爲1

        • 把當前線程的引用賦給exclusiveOwnerThread

      • c!=0
        • 第一種情況,這個鎖是被當前線程持有的,則再對state++
          Lock的可重入性就是通過這部分邏輯做到的
        • 第二種情況,這個鎖是被其他線程持有的,則返回false表示加鎖失敗
  • addWaiter 線程入隊
    返回隊尾的node
    在這裏插入圖片描述

    • 創建Node結點
      入參是當前線程引用和mode是EXCLUSIVE互斥鎖,這時默認waiteState即爲0

    • enq(node)
      在這裏插入圖片描述

      • t==null 則給隊列初始化
        構建隊列要先給隊列做初始化,即創建一個空結點,thread爲null,隊頭head隊尾tail同時指向這個創建好的空結點
        在這裏插入圖片描述

      • t!=null 進行入隊操作
        入隊也存在競爭,爲了保證所有阻塞線程對象能夠被喚醒即都能入隊,所以要用CAS保證入隊的原子性

        • 把prev指向t即隊尾指向的node
        • 用CAS的方式移動尾部指針
        • 原來尾部即t的node的next指向當前入隊的node
          在這裏插入圖片描述
  • acquireQueued 阻塞
    **在這裏插入圖片描述**

    • 如果當前node是隊列的第一個 則再通過tryAcquire嘗試獲取鎖,儘可能避免線程被阻塞。

      • 獲取到鎖了 節點就出隊。 並且把head往後挪一個節點
        通過setHead 把head指向當前node,並把當前node的thread、pred置位null,也就是變成了一個空結點在這裏插入圖片描述
      • 如果沒有搶到鎖 就阻塞
        第一輪循環,通過shouldParkAfterFailedAcquire修改head的狀態爲-1即SIGNAL
        第二輪循環,阻塞線程。
        • shouldParkAfterFailedAcquire
          在這裏插入圖片描述

          取出前驅結點的狀態waitStatus,當前結點能否被喚醒取決於前驅結點的狀態

          • 如果前驅結點的狀態是signal ws==Node.SIGNAL 直接返回true代表是當前結點可喚醒的
          • ws>0 代表前驅節點 出現異常要被cancelled
          • ws是0或propagate,我們就通過CAS方式將前驅節點設置爲可喚醒狀態SIGNAL,即將ws設置爲-1.
            head的waitState設置爲-1的原因:因爲持有鎖的線程T0在釋放鎖的時候,會去喚醒隊列中排隊的第一個線程T1,要判斷head的waitState是否!=0。成立的話會把waitState改爲0,然後把把T1被喚醒;T1接着走循環去搶鎖,可能會再失敗(在非公平鎖場景下),就會再次被阻塞,head的節點就又經歷兩輪循環 waitState從0又變成-1.
        • parkAndCheckInterrupt 阻塞線程,並且需要判斷線程是否是由中斷信號喚醒的
          調用LockSupport.park進行阻塞
          在這裏插入圖片描述




持有鎖的這邊邏輯
執行unlock()
release()

這裏把-1又改成0的原因是,如果在非公平鎖的情況下,當前線程被喚醒後有可能還是會搶不到鎖,那這樣就要保持是0的狀態繼續去執行阻塞的邏輯

在這裏插入圖片描述

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