Semaphore 原理簡介和使用

Semaphore共享鎖

簡介

在多線程環境下用於協調各個線程, 以保證它們能夠正確、合理的使用公共資源
信號量維護了一個許可集,我們在初始化Semaphore時需要爲這個許可集傳入一個數量值,
該數量值代表同一時間能訪問共享資源的線程數量。
線程可以通過acquire()方法獲取到一個許可,然後對共享資源進行操作,
如果許可集已分配完了,那麼線程將進入等待狀態,
直到其他線程釋放許可纔有機會再獲取許可,線程釋放一個許可通過release()方法完成

DEMO瞭解其用法

上述示例說明:

在創建Semaphore時初始化5個許可,這也就意味着同一個時間點允許5個線程進行共享資源訪問,
使用acquire()方法爲每個線程獲取許可,並進行休眠1秒,如果5個許可已被分配完,
新到來的線程將進入等待狀態。如果線程順利完成操作將通過release()方法釋放許可,
我們執行代碼,可以發現每隔1秒幾乎同一時間出現5條線程訪問

Semaphore實現互斥鎖

簡介

在初始化信號量時傳入1,使得它在使用時最多隻有一個可用的許可,從而可用作一個相互排斥的鎖。
這通常也稱爲二進制信號量,因爲它只能有兩種狀態:一個可用的許可或零個可用的許可。
按此方式使用時,二進制信號量具有某種屬性(與很多 Lock 實現不同),即可以由線程釋放“鎖”,而不是由所有者(因爲信號量沒有所有權的概念)

DEMO瞭解其用法

創建一個數量爲1的互斥信號量Semaphore,
然後併發執行10個線程,在線程中利用Semaphore控制線程的併發執行,
因爲信號量數值只有1,因此每次只能一條線程執行,其他線程進入等待狀態

Semaphore提供的方法

  • Semaphore(int permits) 創建具有給定的許可數和非公平的公平設置的Semaphore

  • Semaphore(int permits, boolean fair) 創建具有給定的許可數和給定的公平設置的Semaphore,true即爲公平鎖

  • void acquireUninterruptibly() 從此信號量中獲取許可,不可中斷

  • int availablePermits() 返回此信號量中當前可用的許可數

  • int drainPermits() 獲取並返回立即可用的所有許可

  • protected Collection<Thread> getQueuedThreads() 返回一個 collection,包含可能等待獲取的線程

  • int getQueueLength() 返回正在等待獲取的線程的估計數目

  • boolean hasQueuedThreads() 查詢是否有線程正在等待獲取

  • boolean isFair() 如果此信號量的公平設置爲 true,則返回 true

  • boolean tryAcquire() 僅在調用時此信號量存在一個可用許可,才從信號量獲取許可

  • boolean tryAcquire(long timeout, TimeUnit unit) 如果在給定的等待時間內,此信號量有可用的許可並且當前線程未被中斷,則從此信號量獲取一個許可

內部原理分析

類圖

AQS是基礎組件,只負責核心併發操作,如加入或維護同步隊列,控制同步狀態,等,
而具體的加鎖和解鎖操作交由子類完成,
因此子類Semaphore共享鎖的獲取與釋放需要自己實現,
這兩個方法分別是獲取鎖的tryAcquireShared(int arg)方法和釋放鎖的tryReleaseShared(int arg)方法,這點從Semaphore的內部結構完全可以看出來

Semaphore的內部類公平鎖(FairSync)和非公平鎖(NoFairSync)各自實現不同的獲取鎖方法即tryAcquireShared(int arg),
畢竟公平鎖和非公平鎖的獲取稍後不同,
而釋放鎖tryReleaseShared(int arg)的操作交由Sync實現,因爲釋放操作都是相同的,因此放在父類Sync中實現當然是最好的

源碼分析

默認非公平鎖

初始化信號量的時候 傳入的 permits 參數 最終會賦值到aqs的state變量 
並對state進行cas操作

調用Semaphore的acquire()方法後,
執行過程是這樣的,
當一個線程請求到來時,如果state值代表的許可數足夠使用,
那麼請求線程將會獲得同步狀態即對共享資源的訪問權,並更新state的值(一般是對state值減1),
但如果state值代表的許可數已爲0,則請求線程將無法獲取同步狀態,
線程將被加入到同步隊列並阻塞,
直到其他線程釋放同步狀態(一般是對state值加1)纔可能獲取對共享資源的訪問權

先獲取state的值,並執行減法操作,得到remaining值,
如果remaining大於等於0,那麼線程獲取同步狀態成功,可訪問共享資源,並更新state的值,
如果remaining小於0,那麼線程獲取同步狀態失敗,將被加入同步隊列(通過doAcquireSharedInterruptibly(arg))

採用無鎖(CAS)併發的操作保證對state值修改的安全

1、在方法中,由於當前線程沒有獲取同步狀態,因此創建一個共享模式(Node.SHARED)的結點並通過addWaiter(Node.SHARED)加入同步隊列,

2、加入完成後,當前線程進入自旋狀態,首先判斷前驅結點是否爲head,

a、如果是,那麼嘗試獲取同步狀態並返回r值,如果r大於0,則說明獲取同步狀態成功,將當前線程設置爲head並傳播,傳播指的是,同步狀態剩餘的許可數值不爲0,通知後續結點繼續獲取同步狀態,到此方法將會return結束,獲取到同步狀態的線程將會執行原定的任務。

b、如果前驅結點不爲head或前驅結點爲head並嘗試獲取同步狀態失敗,那麼調用shouldParkAfterFailedAcquire(p, node)方法判斷前驅結點的waitStatus值是否爲SIGNAL並調整同步隊列中的node結點狀態,如果返回true,那麼執行parkAndCheckInterrupt()方法,將當前線程掛起並返回是否中斷線程的flag


在AQS中存在一個變量state,當我們創建Semaphore對象傳入許可數值時,
最終會賦值給state,state的數值代表同一個時刻可同時操作共享數據的線程數量,
每當一個線程請求(如調用Semaphored的acquire()方法)獲取同步狀態成功,
state的值將會減少1,直到state爲0時,表示已沒有可用的許可數,
也就是對共享數據進行操作的線程數已達到最大值,其他後來線程將被阻塞,
此時AQS內部會將線程封裝成共享模式的Node結點,加入同步隊列中等待並開啓自旋操作。
只有當持有對共享數據訪問權限的線程執行完成任務並釋放同步狀態後,
同步隊列中的對於的結點線程纔有可能獲取同步狀態並被喚醒執行同步操作,注
意在同步隊列中獲取到同步狀態的結點將被設置成head並清空相關線程數據(畢竟線程已在執行也就沒有必要保存信息了),
AQS通過這種方式便實現共享鎖

本文使用 mdnice 排版

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