天天看點

Java——AQS詳解1. 概述

1. 概述

AQS:

  • 抽象的隊列式同步器Abstract Quened Synchronizer,是除了java自帶的synchronized關鍵字之外的鎖機制。
  • 簡單點說:AQS就是基于CLH隊列,用volatile修飾共享變量state,線程通過CAS去改變狀态符,成功則擷取鎖成功,失敗則進入等待隊列,等待被喚醒。

原理:

  • 如果被請求的共享資源空閑,則将目前請求資源的線程設定為有效的工作線程,并将共享資源設定為鎖定狀态
  • 如果被請求的共享資源被占用,那麼就需要一套線程阻塞等待以及被喚醒時鎖配置設定的機制,這個機制AQS是用CLH隊列鎖實作的,即将暫時擷取不到鎖的線程加入到隊列中。

CLH隊列:

  • 一個虛拟的雙向隊列,虛拟的雙向隊列即不存在隊列執行個體,僅存在節點之間的關聯關系。
  • AQS是将每一條請求共享資源的線程封裝成一個CLH鎖隊列的一個結點(Node),來實作鎖的配置設定。
Java——AQS詳解1. 概述

維護了一個volatile int state(代表共享資源)和一個FIFO線程等待隊列(多線程争用資源被阻塞時會進入此隊列)

AQS定義兩種資源共享方式:

  • Exclusive(獨占,隻有一個線程能執行,如ReentrantLock)
  • Share(共享,多個線程可同時執行,如Semaphore/CountDownLatch)

不同的自定義同步器争用共享資源的方式也不同。自定義同步器在實作時隻需要實作共享資源state的擷取與釋放方式即可,至于具體線程等待隊列的維護(如擷取資源失敗入隊/喚醒出隊等),AQS已經在頂層實作好了。自定義同步器實作時主要實作以下幾種方法:

  • isHeldExclusively():該線程是否正在獨占資源。隻有用到condition才需要去實作它。
  • tryAcquire(int):獨占方式。嘗試擷取資源,成功則傳回true,失敗則傳回false。
  • tryRelease(int):獨占方式。嘗試釋放資源,成功則傳回true,失敗則傳回false。
  • tryAcquireShared(int):共享方式。嘗試擷取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。
  • tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放後允許喚醒後續等待結點傳回true,否則傳回false。

以ReentrantLock為例,state初始化為0,表示未鎖定狀态。A線程lock()時,會調用tryAcquire()獨占該鎖并将state+1。此後,其他線程再tryAcquire()時就會失敗,直到A線程unlock()到state=0(即釋放鎖)為止,其它線程才有機會擷取該鎖。當然,釋放鎖之前,A線程自己是可以重複擷取此鎖的(state會累加),這就是可重入的概念。但要注意,擷取多少次就要釋放多麼次,這樣才能保證state是能回到零态的。

 

再以CountDownLatch以例,任務分為N個子線程去執行,state也初始化為N(注意N要與線程個數一緻)。這N個子線程是并行執行的,每個子線程執行完後countDown()一次,state會CAS減1。等到所有子線程都執行完後(即state=0),會unpark()主調用線程,然後主調用線程就會從await()函數傳回,繼續後餘動作。

一般來說,自定義同步器要麼是獨占方法,要麼是共享方式,他們也隻需實作tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一種即可。但AQS也支援自定義同步器同時實作獨占和共享兩種方式,如ReentrantReadWriteLock。