AQS原理簡介

AQS簡介

  • 原名 AbstractQueuedSynchronizer 即隊列同步器 是構建鎖和其他同步組件的基礎框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch)

  • AQS解決了子類實現同步器時涉及當的大量細節問題,例如獲取同步狀態、FIFO同步隊列 自定義同步器在實現時只需要實現共享資源state的獲取與釋放方式即可,至於具體線程等待隊列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在頂層實現好了,所以使用AQS不僅能夠極大地減少實現工作,而且也不必處理在多個位置上發生的競爭問題

  • 只能在一個時刻發生阻塞,從而降低上下文切換的開銷,提高了吞吐量

  • AQS通過內置的FIFO同步隊列來完成資源獲取線程的排隊工作,如果當前線程獲取同步狀態失敗(鎖)時,AQS則會將當前線程以及等待狀態等信息構造成一個節點(Node)並將其加入同步隊列,同時會阻塞當前線程,當同步狀態釋放時,則會把節點中的線程喚醒,使其再次嘗試獲取同步狀態

深入源碼 看看有哪些方法

  • getState()

返回同步狀態的當前值
  • setState(int newState)

設置當前同步狀態
  • compareAndSetState(int expect, int update)

使用CAS設置當前狀態,該方法能夠保證狀態設置的原子性

自定義同步器主要實現以下幾個方法

  • tryAcquire(int arg)

獨佔式獲取同步狀態,獲取同步狀態成功後,其他線程需要等待該線程釋放同步狀態才能獲取同步狀態
  • tryRelease(int arg)

獨佔式釋放同步狀態
  • tryAcquireShared(int arg)

共享式獲取同步狀態,返回值大於等於0則表示獲取成功,否則獲取失敗
  • tryReleaseShared(int arg)

共享式釋放同步狀態
  • tryReleaseShared(int arg)

當前同步器是否在獨佔式模式下被線程佔用,一般該方法表示是否被當前線程所獨佔
  • acquire(int arg)

獨佔式獲取同步狀態,如果當前線程獲取同步狀態成功,則由該方法返回,否則,將會進入同步隊列等待,該方法將會調用可重寫的tryAcquire(int arg)方法
  • acquireInterruptibly(int arg)

與acquire(int arg)相同,但是該方法響應中斷,當前線程爲獲取到同步狀態而進入到同步隊列中,如果當前線程被中斷,則該方法會拋出InterruptedException異常並返回
  • tryAcquireNanos(int arg,long nanos)

超時獲取同步狀態,如果當前線程在nanos時間內沒有獲取到同步狀態,那麼將會返回false,已經獲取則返回true
  • acquireShared(int arg)

共享式獲取同步狀態,如果當前線程未獲取到同步狀態,將會進入同步隊列等待,與獨佔式的主要區別是在同一時刻可以有多個線程獲取到同步狀態
  • acquireSharedInterruptibly(int arg)

共享式獲取同步狀態,響應中斷
  • tryAcquireSharedNanos(int arg, long nanosTimeout)

共享式獲取同步狀態,增加超時限制
  • release(int arg)

獨佔式釋放同步狀態,該方法會在釋放同步狀態之後,將同步隊列中第一個節點包含的線程喚醒
  • releaseShared(int arg)

共享式釋放同步狀態

CLH

CLH同步隊列是一個FIFO雙向隊列,AQS依賴它來完成同步狀態的管理,
當前線程如果獲取同步狀態失敗時,AQS則會將當前線程已經等待狀態等信息構造成一個節點(Node)並將其加入到CLH同步隊列,同時會阻塞當前線程,
當同步狀態釋放時,會把首節點喚醒(公平鎖),使其再次嘗試獲取同步狀態。

在CLH同步隊列中,一個節點表示一個線程,它保存着
線程的引用(thread)、狀態(waitStatus)、前驅節點(prev)、後繼節點(next),其數據結構如下

同步隊列的數據結構

 

入列

CHL這種鏈表式結構入列,無非就是tail指向新節點、新節點的前驅節點指向當前最後的節點,當前最後一個節點的next指向當前節點
  • addWaiter(Node node)

將當前線程加入到等待隊列的隊尾,並返回當前線程所在的結點

addWaiter(Node node)先通過快速嘗試設置尾節點,如果失敗,則調用enq(Node node)方法設置尾節點

此方法用於將node加入隊尾,該方法核心就是通過CAS自旋的方式來設置尾節點,知道獲得預期的結果即添加節點成功,當前線程纔會返回

出列

CLH同步隊列遵循FIFO(先進先出),首節點的線程釋放同步狀態後,
將會喚醒它的後繼節點(next),而後繼節點將會在獲取同步狀態成功時將自己設置爲首節點,這個過程非常簡單,
head執行該節點並斷開原首節點的next和當前節點的prev即可,
注意在這個過程是不需要使用CAS來保證的,因爲只有一個線程能夠成功獲取到同步狀態。

同步狀態的獲取與釋放

獨佔式同步狀態獲取

如果獲取到資源,線程直接返回,否則進入等待隊列,直到獲取到資源爲止,且整個過程忽略中斷的影響
  • tryAcquire:去嘗試獲取鎖,獲取成功則設置鎖狀態並返回true,否則返回false。該方法由自定義同步組件自己實現(通過state的get/set/CAS),該方法必須要保證線程安全的獲取同步狀態。

  • addWaiter:如果tryAcquire返回FALSE(獲取同步狀態失敗),則調用該方法將當前線程加入到CLH同步隊列尾部,並標記爲獨佔模式。

  • acquireQueued:當前線程會根據公平性原則來進行阻塞等待(自旋),直到獲取鎖爲止;如果在整個等待過程中被中斷過,則返回true,否則返回false。

  • selfInterrupt:如果線程在等待過程中被中斷過,它是不響應的。只是獲取資源後纔再進行自我中斷selfInterrupt(),將中斷補上。

當前線程會一直嘗試獲取同步狀態,當然前提是隻有其前驅節點爲頭結點才能夠嘗試獲取同步狀態

1、保持FIFO同步隊列原則。

2、頭節點釋放同步狀態後,將會喚醒其後繼節點,後繼節點被喚醒後需要檢查自己是否爲頭節點。

這段代碼主要檢查當前線程是否需要被阻塞,具體規則如下:

[1]如果當前線程的前驅節點狀態爲SINNAL,則表明當前線程需要被阻塞,
調用unpark()方法喚醒,直接返回true,當前線程阻塞

[2]如果當前線程的前驅節點狀態爲CANCELLED(ws > 0),
則表明該線程的前驅節點已經等待超時或者被中斷了,
則需要從CLH隊列中將該前驅節點刪除掉,直到回溯到前驅節點狀態 <= 0 ,返回false

[3]如果前驅節點非SINNAL,非CANCELLED,
則通過CAS的方式將其前驅節點設置爲SINNAL,返回false

整個流程中,如果前驅結點的狀態不是SIGNAL,那麼自己就不能被阻塞,
需要去找個安心的休息點(前驅節點狀態 <= 0 ),同時可以再嘗試下看有沒有機會去獲取資源。

如果 shouldParkAfterFailedAcquire(Node pred, Node node) 方法返回true,則調用parkAndCheckInterrupt()方法阻塞當前線程

本文使用 mdnice 排版

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