深入理解Semaphore

使用

Semaphore是計數信號量。Semaphore管理一系列許可證。每個acquire方法阻塞,直到有一個許可證可以獲得然后拿走一個許可證;每個release方法增加一個許可證,這可能會釋放一個阻塞的acquire方法。然而,其實并沒有實際的許可證這個對象,Semaphore只是維持了一個可獲得許可證的數量。?
Semaphore經常用于限制獲取某種資源的線程數量。下面舉個例子,比如說操場上有5個跑道,一個跑道一次只能有一個學生在上面跑步,一旦所有跑道在使用,那么后面的學生就需要等待,直到有一個學生不跑了,下面是這個例子:

/*** 操場,有5個跑道* Created by Xingfeng on 2016-12-09.*/
public class Playground {/*** 跑道類*/static class Track {private int num;public Track(int num) {this.num = num;}@Overridepublic String toString() {return "Track{" +"num=" + num +'}';}}private Track[] tracks = {new Track(1), new Track(2), new Track(3), new Track(4), new Track(5)};private volatile boolean[] used = new boolean[5];private Semaphore semaphore = new Semaphore(5, true);/*** 獲取一個跑道*/public Track getTrack() throws InterruptedException {semaphore.acquire(1);return getNextAvailableTrack();}/*** 返回一個跑道** @param track*/public void releaseTrack(Track track) {if (makeAsUsed(track))semaphore.release(1);}/*** 遍歷,找到一個沒人用的跑道** @return*/private Track getNextAvailableTrack() {for (int i = 0; i < used.length; i++) {if (!used[i]) {used[i] = true;return tracks[i];}}return null;}/*** 返回一個跑道** @param track*/private boolean makeAsUsed(Track track) {for (int i = 0; i < used.length; i++) {if (tracks[i] == track) {if (used[i]) {used[i] = false;return true;} else {return false;}}}return false;}}
  • 從上面可以看到,創建了5個跑道對象,并使用一個boolean類型的數組記錄每個跑道是否被使用了,初始化了5個許可證的Semaphore,在獲取跑道時首先調用acquire(1)獲取一個許可證,在歸還一個跑道是調用release(1)釋放一個許可證。接下來再看啟動程序,如下:
public class SemaphoreDemo {static class Student implements Runnable {private int num;private Playground playground;public Student(int num, Playground playground) {this.num = num;this.playground = playground;}@Overridepublic void run() {try {//獲取跑道Playground.Track track = playground.getTrack();if (track != null) {System.out.println("學生" + num + "在" + track.toString() + "上跑步");TimeUnit.SECONDS.sleep(2);System.out.println("學生" + num + "釋放" + track.toString());//釋放跑道playground.releaseTrack(track);}} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {Executor executor = Executors.newCachedThreadPool();Playground playground = new Playground();for (int i = 0; i < 100; i++) {executor.execute(new Student(i+1,playground));}}}
  • 上面的代碼中,Student類代表學生,首先獲取跑道,一旦獲取到就打印一句話,然后睡眠2s,然后再打印釋放,最后歸還跑道。

源碼解析

Semaphore有兩種模式,公平模式和非公平模式。公平模式就是調用acquire的順序就是獲取許可證的順序,遵循FIFO;而非公平模式是搶占式的,也就是有可能一個新的獲取線程恰好在一個許可證釋放時得到了這個許可證,而前面還有等待的線程。

構造方法

Semaphore有兩個構造方法,如下:

       public Semaphore(int permits) {sync = new NonfairSync(permits);}public Semaphore(int permits, boolean fair) {sync = fair ? new FairSync(permits) : new NonfairSync(permits);}
  • 從上面可以看到兩個構造方法,都必須提供許可的數量,第二個構造方法可以指定是公平模式還是非公平模式,默認非公平模式。?

Semaphore內部基于AQS的共享模式,所以實現都委托給了Sync類。?
這里就看一下NonfairSync的構造方法:

 NonfairSync(int permits) {super(permits);}
  • 可以看到直接調用了父類的構造方法,Sync的構造方法如下:
Sync(int permits) {setState(permits);}
  • 可以看到調用了setState方法,也就是說AQS中的資源就是許可證的數量。

獲取許可

先從獲取一個許可看起,并且先看非公平模式下的實現。首先看acquire方法,acquire方法有幾個重載,但主要是下面這個方法

public void acquire(int permits) throws InterruptedException {if (permits < 0) throw new IllegalArgumentException();sync.acquireSharedInterruptibly(permits);}
  • 從上面可以看到,調用了Sync的acquireSharedInterruptibly方法,該方法在父類AQS中,如下:
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {//如果線程被中斷了,拋出異常if (Thread.interrupted())throw new InterruptedException();//獲取許可失敗,將線程加入到等待隊列中if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}
  • AQS子類如果要使用共享模式的話,需要實現tryAcquireShared方法,下面看NonfairSync的該方法實現:
 protected int tryAcquireShared(int acquires) {return nonfairTryAcquireShared(acquires);}
  • 該方法調用了父類中的nonfairTyAcquireShared方法,如下:
final int nonfairTryAcquireShared(int acquires) {for (;;) {//獲取剩余許可數量int available = getState();//計算給完這次許可數量后的個數int remaining = available - acquires;//如果許可不夠或者可以將許可數量重置的話,返回if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}
  • 從上面可以看到,只有在許可不夠時返回值才會小于0,其余返回的都是剩余許可數量,這也就解釋了,一旦許可不夠,后面的線程將會阻塞。看完了非公平的獲取,再看下公平的獲取,代碼如下:
 protected int tryAcquireShared(int acquires) {for (;;) {//如果前面有線程再等待,直接返回-1if (hasQueuedPredecessors())return -1;//后面與非公平一樣int available = getState();int remaining = available - acquires;if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}
  • 從上面可以看到,FairSync與NonFairSync的區別就在于會首先判斷當前隊列中有沒有線程在等待,如果有,就老老實實進入到等待隊列;而不像NonfairSync一樣首先試一把,說不定就恰好獲得了一個許可,這樣就可以插隊了。?

看完了獲取許可后,再看一下釋放許可。

釋放許可

釋放許可也有幾個重載方法,但都會調用下面這個帶參數的方法,

public void release(int permits) {if (permits < 0) throw new IllegalArgumentException();sync.releaseShared(permits);}
  • releaseShared方法在AQS中,如下:
public final boolean releaseShared(int arg) {//如果改變許可數量成功if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}
  • AQS子類實現共享模式的類需要實現tryReleaseShared類來判斷是否釋放成功,實現如下:
protected final boolean tryReleaseShared(int releases) {for (;;) {//獲取當前許可數量int current = getState();//計算回收后的數量int next = current + releases;if (next < current) // overflowthrow new Error("Maximum permit count exceeded");//CAS改變許可數量成功,返回trueif (compareAndSetState(current, next))return true;}}
  • 從上面可以看到,一旦CAS改變許可數量成功,那么就會調用doReleaseShared()方法釋放阻塞的線程。

減小許可數量

Semaphore還有減小許可數量的方法,該方法可以用于用于當資源用完不能再用時,這時就可以減小許可證。代碼如下:

protected void reducePermits(int reduction) {if (reduction < 0) throw new IllegalArgumentException();sync.reducePermits(reduction);}
  • 可以看到,委托給了Sync,Sync的reducePermits方法如下:
  final void reducePermits(int reductions) {for (;;) {//得到當前剩余許可數量int current = getState();//得到減完之后的許可數量int next = current - reductions;if (next > current) // underflowthrow new Error("Permit count underflow");//如果CAS改變成功if (compareAndSetState(current, next))return;}}
  • 從上面可以看到,就是CAS改變AQS中的state變量,因為該變量代表許可證的數量。

獲取剩余許可數量  

Semaphore還可以一次將剩余的許可數量全部取走,該方法是drain方法,如下:

public int drainPermits() {return sync.drainPermits();}
  • Sync的實現如下:
 final int drainPermits() {for (;;) {int current = getState();if (current == 0 || compareAndSetState(current, 0))return current;}}
  • 可以看到,就是CAS將許可數量置為0。

總結

Semaphore是信號量,用于管理一組資源。其內部是基于AQS的共享模式,AQS的狀態表示許可證的數量,在許可證數量不夠時,線程將會被掛起;而一旦有一個線程釋放一個資源,那么就有可能重新喚醒等待隊列中的線程繼續執行。

轉自:https://blog.csdn.net/qq_19431333/article/details/70212663

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/443310.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/443310.shtml
英文地址,請注明出處:http://en.pswp.cn/news/443310.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【算法系列之四】柱狀圖儲水

題目&#xff1a; 給定一個數組&#xff0c;每個位置的值代表一個高度&#xff0c;那么整個數組可以看做是一個直方圖&#xff0c; 如果把這個直方圖當作容器的話&#xff0c;求這個容器能裝多少水 例如&#xff1a;3&#xff0c;1&#xff0c;2&#xff0c;4 代表第一個位…

鹽城大數據產業園人才公寓_岳西大數據產業園規劃設計暨建筑設計方案公布,搶先一睹效果圖...

近日&#xff0c;岳西縣大數據產業園規劃設計暨建筑設計方案公布。岳西縣大數據產業園項目總占地面積17014.10㎡(約合25.52畝)&#xff0c;擬建總建筑面積約為61590.84㎡(地上建筑面積39907.49㎡&#xff0c;地下建筑面積21602.35㎡)。以“科技圓環”為主題&#xff0c;組建出一…

【算法系列之五】對稱二叉樹

給定一個二叉樹&#xff0c;檢查它是否是鏡像對稱的。 例如&#xff0c;二叉樹 [1,2,2,3,4,4,3] 是對稱的。 1/ \2 2/ \ / \ 3 4 4 3但是下面這個 [1,2,2,null,3,null,3] 則不是鏡像對稱的: 1/ \2 2\ \3 3 說明: 如果你可以運用遞歸和迭代兩種方法解決這個問題&a…

【算法系列之六】兩整數之和

不使用運算符 和 - &#xff0c;計算兩整數 a 、b 之和。 示例 1: 輸入: a 1, b 2 輸出: 3示例 2: 輸入: a -2, b 3 輸出: 1 方法一&#xff1a;遞歸 public static int getSum1(int a, int b) {if ((a & b) ! 0) { // 判斷是否有進位return getSum1(a ^ b, (a &…

cuda默認函數與c++沖突_好程序員Python教程系列-第8講:函數和模塊

好程序員Python教程系列-第8講&#xff1a;函數和模塊&#xff0c;在講解本章節的內容之前&#xff0c;我們先來研究一道數學題&#xff0c;請說出下面的方程有多少組正整數解。事實上&#xff0c;上面的問題等同于將8個蘋果分成四組每組至少一個蘋果有多少種方案&#xff0c;所…

【算法系列之七】合并兩個有序鏈表

將兩個有序鏈表合并為一個新的有序鏈表并返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。 示例&#xff1a; 輸入&#xff1a;1->2->4, 1->3->4 輸出&#xff1a;1->1->2->3->4->4/*** Definition for singly-linked list.* public cla…

mfc讓圖片與按鈕一起_對許多張圖片進行批量裁剪,看看我是如何快速做到的

概要&#xff1a;當我們需要對很多圖片進行批量裁剪時&#xff0c;以往的辦法是自己一張一張圖片去操作&#xff0c;非常麻煩。有沒有這樣一個工具&#xff0c;能夠幫我們批量進行處理呢&#xff1f;之前小編在網上找了非常多的軟件&#xff0c;一個一個地安裝試用&#xff0c;…

【算法系列之八】刪除鏈表的倒數第N個節點

給定一個鏈表&#xff0c;刪除鏈表的倒數第 n 個節點&#xff0c;并且返回鏈表的頭結點。 示例&#xff1a; 給定一個鏈表: 1->2->3->4->5, 和 n 2.當刪除了倒數第二個節點后&#xff0c;鏈表變為 1->2->3->5.說明&#xff1a; 給定的 n 保證是有效的…

手寫分頁sql_分頁查詢SQL語句

表結構&#xff1a;DROP TABLE IF EXISTS zhoufoxcn.userlist;CREATE TABLE zhoufoxcn.userlist (UserId int(10) unsigned NOT NULL auto_increment,UserName varchar(45) NOT NULL,Age int(10) unsigned NOT NULL default 10,Sex tinyint(3) unsigned NOT NULL default 1,Ta…

【算法系列之九】合并兩個有序數組

給定兩個有序整數數組 nums1 和 nums2&#xff0c;將 nums2 合并到 nums1 中&#xff0c;使得 num1 成為一個有序數組。 說明: 初始化 nums1 和 nums2 的元素數量分別為 m 和 n。你可以假設 nums1 有足夠的空間&#xff08;空間大小大于或等于 m n&#xff09;來保存 nums2 …

把網卡指定給vm虛擬機_為VMWare虛擬網卡指定靜態的MAC地址

當你把虛擬機移到另一臺主機或在同一臺主機但不同的路徑時&#xff0c;虛擬機的MAC地址將會更改。默認情況下VMWare會保證MAC地址的唯一性卻不保存固定性&#xff0c;在每次開啟虛擬機里的系統時都可能重新分配MAC地址來保證唯一性&#xff0c;若你想保證即使虛擬機被移動后&am…

【算法系列之十】三數之和

給定一個包含 n 個整數的數組 nums&#xff0c;判斷 nums 中是否存在三個元素 a&#xff0c;b&#xff0c;c &#xff0c;使得 a b c 0 &#xff1f;找出所有滿足條件且不重復的三元組。 注意&#xff1a;答案中不可以包含重復的三元組。 例如, 給定數組 nums [-1, 0, 1,…

android 動態獲取全縣_省市縣 ------ 三級滾動(android)

預先加載仿滾輪實現的全部數據mCityPickerView.init(this);③ 點擊響應&#xff1a;ss.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View v) {CityConfig cityConfig new CityConfig.Builder().title("選擇城市")//標題.build();m…

發電廠電氣部分第三版pdf_火力發電廠電氣主接線的特點

根據火力發電廠的容量及其在電力系統中的地位&#xff0c;一般可將火力發電廠分為區域性火力發電廠和地方性火力發電廠。這兩類火力發電廠的電氣主接線有各自的特點。一、區域性火力發電廠的電氣主接線1、單機容量及總裝機容量都較大單機容量多為300MW、600MW和少量1000MW,電廠…

【算法系列之十一】k個一組翻轉鏈表

給出一個鏈表&#xff0c;每 k 個節點一組進行翻轉&#xff0c;并返回翻轉后的鏈表。 k 是一個正整數&#xff0c;它的值小于或等于鏈表的長度。如果節點總數不是 k 的整數倍&#xff0c;那么將最后剩余節點保持原有順序。 示例 : 給定這個鏈表&#xff1a;1->2->3-&g…

ghostblog主題_讀Ghost博客源碼與自定義Ghost博客主題

我使用的Ghost博客一直使用者默認的Casper主題。我向來沒怎么打理過自己博客&#xff0c;一方面認為自己不夠專業&#xff0c;很難寫出質量比較高的文字&#xff1b;另一方面認為博客太耗時間&#xff0c;很容易影響正常的工作內容。最近公司即將搬遷&#xff0c;我的開發工作也…

【算法系列之十二】最接近的三數之和

給定一個包括 n 個整數的數組 nums 和 一個目標值 target。找出 nums 中的三個整數&#xff0c;使得它們的和與 target 最接近。返回這三個數的和。假定每組輸入只存在唯一答案。 例如&#xff0c;給定數組 nums [-1&#xff0c;2&#xff0c;1&#xff0c;-4], 和 target 1…

定義一個dto對象_業務代碼的救星——Java 對象轉換框架 MapStruct 妙用

在業務項目的開發中&#xff0c;我們經常需要將 Java 對象進行轉換&#xff0c;比如從將外部微服務得到的對象轉換為本域的業務對象 domainobject&#xff0c;將 domainobject 轉為數據持久層的 dataobject&#xff0c;將 domainobject 轉換為 DTO 以便返回給外部調用方等。在轉…

JVM調優總結 -Xms -Xmx -Xmn -Xss

堆大小設置 JVM 中最大堆大小有三方面限制&#xff1a;相關操作系統的數據模型&#xff08;32-bt還是64-bit&#xff09;限制&#xff1b;系統的可用虛擬內存限制&#xff1b;系統的可用物理內存限制。32位系統 下&#xff0c;一般限制在1.5G~2G&#xff1b;64為操作系統對內存…

discuz設置用戶每天回帖數_[建站教程]Discuz3.4設置QQ互聯登陸教程

雖然現在很多人已經不在使用QQ了&#xff0c;但瘦死的駱駝比馬大&#xff0c;QQ的用戶基數還是很大&#xff0c;而且QQ里有大量的年輕用戶&#xff0c;像我的表妹&#xff0c;表弟剛上初中。他們是忠誠的QQ用戶。為了獲取這批年輕的用戶&#xff0c;我們還是有必要讓網站支持QQ…