【死磕Java併發】—– J.U.C之併發工具類:Semaphore
此篇部落格所有原始碼均來自JDK 1.8
訊號量Semaphore是一個控制訪問多個共享資源的計數器,和CountDownLatch一樣,其本質上是一個“共享鎖”。
友情提示:歡迎關注公眾號【芋道原始碼】。?關注後,拉你進【原始碼圈】微信群討論技術和原始碼。
友情提示:歡迎關注公眾號【芋道原始碼】。?關注後,拉你進【原始碼圈】微信群討論技術和原始碼。
友情提示:歡迎關注公眾號【芋道原始碼】。?關注後,拉你進【原始碼圈】微信群討論技術和原始碼。
Semaphore,在API是這麼介紹的:
一個計數訊號量。從概念上講,訊號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然後再獲取該許可。每個 release() 新增一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可物件,Semaphore 只對可用許可的號碼進行計數,並採取相應的行動。
Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的執行緒數目。
下面我們就一個停車場的簡單例子來闡述Semaphore:
為了簡單起見我們假設停車場僅有5個停車位,一開始停車場沒有車輛所有車位全部空著,然後先後到來三輛車,停車場車位夠,安排進去停車,然後又來三輛,這個時候由於只有兩個停車位,所有隻能停兩輛,其餘一輛必須在外面候著,直到停車場有空車位,當然以後每來一輛都需要在外面候著。當停車場有車開出去,裡面有空位了,則安排一輛車進去(至於是哪輛 要看選擇的機制是公平還是非公平)。
從程式角度看,停車場就相當於訊號量Semaphore,其中許可數為5,車輛就相對執行緒。當來一輛車時,許可數就會減 1 ,當停車場沒有車位了(許可書 == 0 ),其他來的車輛需要在外面等候著。如果有一輛車開出停車場,許可數 + 1,然後放進來一輛車。
號量Semaphore是一個非負整數(>=1)。當一個執行緒想要訪問某個共享資源時,它必須要先獲取Semaphore,當Semaphore >0時,獲取該資源並使Semaphore – 1。如果Semaphore值 = 0,則表示全部的共享資源已經被其他執行緒全部佔用,執行緒必須要等待其他執行緒釋放資源。當執行緒釋放資源時,Semaphore則+1
實現分析
Semaphore結構如下:
從上圖可以看出Semaphore內部包含公平鎖(FairSync)和非公平鎖(NonfairSync),繼承內部類Sync,其中Sync繼承AQS(再一次闡述AQS的重要性)。
Semaphore提供了兩個建構函式:
Semaphore(int permits) :建立具有給定的許可數和非公平的公平設定的 Semaphore。
Semaphore(int permits, boolean fair) :建立具有給定的許可數和給定的公平設定的 Semaphore。
實現如下:
publicSemaphore(int permits){
sync =newNonfairSync(permits);}publicSemaphore(int permits,boolean fair){
sync = fair ?newFairSync(permits):newNonfairSync(permits);}
Semaphore預設選擇非公平鎖。
當訊號量Semaphore = 1 時,它可以當作互斥鎖使用。其中0、1就相當於它的狀態,當=1時表示其他執行緒可以獲取,當=0時,排他,即其他執行緒必須要等待。
訊號量獲取
Semaphore提供了acquire()方法來獲取一個許可。
publicvoid acquire()throwsInterruptedException{
sync.acquireSharedInterruptibly(1);}
內部呼叫AQS的acquireSharedInterruptibly(int arg),該方法以共享模式獲取同步狀態:
publicfinalvoid acquireSharedInterruptibly(int arg)throwsInterruptedException{if(Thread.interrupted())thrownewInterruptedException();if(tryAcquireShared(arg)<0)
doAcquireSharedInterruptibly(arg);}
在acquireSharedInterruptibly(int arg)中,tryAcquireShared(int arg)由子類來實現,對於Semaphore而言,如果我們選擇非公平模式,則呼叫NonfairSync的tryAcquireShared(int arg)方法,否則呼叫FairSync的tryAcquireShared(int arg)方法。
公平
protectedint tryAcquireShared(int acquires){for(;;){//判斷該執行緒是否位於CLH佇列的列頭if(hasQueuedPredecessors())return-1;//獲取當前的訊號量許可int available = getState();//設定“獲得acquires個訊號量許可之後,剩餘的訊號量許可數”int remaining = available - acquires;//CAS設定訊號量if(remaining <0||
compareAndSetState(available, remaining))return remaining;}}
非公平
對於非公平而言,因為它不需要判斷當前執行緒是否位於CLH同步佇列列頭,所以相對而言會簡單些。
protectedint tryAcquireShared(int acquires){return nonfairTryAcquireShared(acquires);}finalint nonfairTryAcquireShared(int acquires){for(;;){int available = getState();int remaining = available - acquires;if(remaining <0||
compareAndSetState(available, remaining))return remaining;}}
訊號量釋放
獲取了許可,當用完之後就需要釋放,Semaphore提供release()來釋放許可。
publicvoid release(){
sync.releaseShared(1);}
內部呼叫AQS的releaseShared(int arg):
publicfinalboolean releaseShared(int arg){if(tryReleaseShared(arg)){
doReleaseShared();returntrue;}returnfalse;}
releaseShared(int arg)呼叫Semaphore內部類Sync的tryReleaseShared(int arg):
protectedfinalboolean tryReleaseShared(int releases){for(;;){int current = getState();//訊號量的許可數 = 當前訊號許可數 + 待釋放的訊號許可數intnext= current + releases;if(next< current)// overflowthrownewError("Maximum permit count exceeded");//設定可獲取的訊號許可數為nextif(compareAndSetState(current,next))returntrue;}}
對於訊號量的獲取釋放詳細過程,請參考如下部落格:
【死磕Java併發】—–J.U.C之AQS:CLH同步佇列
【死磕Java併發】—–J.U.C之AQS:同步狀態的獲取與釋放
【死磕Java併發】—–J.U.C之AQS:阻塞和喚醒執行緒
【死磕Java併發】—–J.U.C之重入鎖:ReentrantLock
應用示例
我們已停車為示例:
publicclassSemaphoreTest{staticclassParking{//訊號量privateSemaphore semaphore;Parking(int count){
semaphore =newSemaphore(count);}publicvoid park(){try{//獲取訊號量
semaphore.acquire();long time =(long)(Math.random()*10);System.out.println(Thread.currentThread().getName()+"進入停車場,停車"+ time +"秒...");Thread.sleep(time);System.out.println(Thread.currentThread().getName()+"開出停車場...");}catch(InterruptedException e){
e.printStackTrace();}finally{
semaphore.release();}}}staticclassCarextendsThread{Parking parking ;Car(Parking parking){this.parking = parking;}@Overridepublicvoid run(){
parking.park();//進入停車場}}publicstaticvoid main(String[] args){Parking parking =newParking(3);for(int i =0; i <5; i++){newCar(parking).start();}}}
執行結果如下:
相關推薦
【死磕Java併發】—– J.U.C之併發工具類:Semaphore
此篇部落格所有原始碼均來自JDK 1.8訊號量Semaphore是一個控制訪問多個共享資源的計數
【死磕Java併發】-----J.U.C之併發工具類:Exchanger
此篇部落格所有原始碼均來自JDK 1.8 前面三篇部落格分別介紹了CyclicBarrier、CountDownLatch、Semaphore,現在介紹併發工具類中的最後一個Exchange。Exchange是最簡單的也是最複雜的,簡單在於API非常簡
【死磕Java併發】-----J.U.C之重入鎖:ReentrantLock
此篇部落格所有原始碼均來自JDK 1.8 ReentrantLock,可重入鎖,是一種遞迴無阻塞的同步機制。它可以等同於synchronized的使用,但是ReentrantLock提供了比synchronized更強大、靈活的鎖機制,可以減少死鎖發生
【死磕Java併發】-----J.U.C之併發工具類:CyclicBarrier
此篇部落格所有原始碼均來自JDK 1.8 CyclicBarrier,一個同步輔助類,在API中是這麼介紹的: 它允許一組執行緒互相等待,直到到達某個公共屏障點 (common barrier point)。在涉及一組固定大小的執行緒的程式中,這些執
【死磕Java併發】-----J.U.C之AQS:阻塞和喚醒執行緒
此篇部落格所有原始碼均來自JDK 1.8 線上程獲取同步狀態時如果獲取失敗,則加入CLH同步佇列,通過通過自旋的方式不斷獲取同步狀態,但是在自旋的過程中則需要判斷當前執行緒是否需要阻塞,其主要方法在acquireQueued(): if (sho
【死磕Java併發】-----J.U.C之阻塞佇列:ArrayBlockingQueue
ArrayBlockingQueue,一個由陣列實現的有界阻塞佇列。該佇列採用FIFO的原則對元素進行排序新增的。 ArrayBlockingQueue為有界且固定,其大小在構造時由建構函式來決定,確認之後就不能再改變了。ArrayBlockingQueu
【死磕Java併發】—– J.U.C之AQS:同步狀態的獲取與釋放
此篇部落格所有原始碼均來自JDK 1.8在前面提到過,AQS是構建Java同步元件的基礎,我們期
【死磕Java併發】-----J.U.C之Condition
此篇部落格所有原始碼均來自JDK 1.8 在沒有Lock之前,我們使用synchronized來控制同步,配合Object的wait()、notify()系列方法可以實現等待/通知模式。在Java SE5後,Java提供了Lock介面,相對於Synch
【死磕Java併發】-----J.U.C之阻塞佇列:DelayQueue
DelayQueue是一個支援延時獲取元素的無界阻塞佇列。裡面的元素全部都是“可延期”的元素,列頭的元素是最先“到期”的元素,如果佇列裡面沒有元素到期,是不能從列頭獲取元素的,哪怕有元素也不行。也就是說只有在延遲期到時才能夠從佇列中取元素。 DelayQu
【死磕Java併發】—–J.U.C之AQS(一篇就夠了)
作者:大明哥 原文地址:http://cmsblogs.com 越是核心的東西越是要反覆看,本文篇幅較長,希望各位細細品讀,來回多讀幾遍理解下。 AQS簡介 java的內建鎖一直都是備受爭議的,在JDK 1.6之前,synchronized這個重量級鎖其效能一直都
【死磕Java併發】-----J.U.C之AQS:AQS簡介
Java的內建鎖一直都是備受爭議的,在JDK 1.6之前,synchronized這個重量級鎖其效能一直都是較為低下,雖然在1.6後,進行大量的鎖優化策略(【死磕Java併發】—–深入分析synchronized的實現原理),但是與Lock相比synchroni
【死磕Java併發】-----J.U.C之AQS:CLH同步佇列
此篇部落格所有原始碼均來自JDK 1.8 CLH同步佇列是一個FIFO雙向佇列,AQS依賴它來完成同步狀態的管理,當前執行緒如果獲取同步狀態失敗時,AQS則會將當前執行緒已經等待狀態等資訊構造成一個節點(Node)並將其加入到CLH同步佇列,同時會
【死磕Java並發】—–J.U.C之AQS(一篇就夠了)
ini tle 循環 針對 可能 width als 如果 boolean [隱藏目錄]1 獨占式1.1 獨占式同步狀態獲取1.2 獨占式獲取響應中斷1.3 獨占式超時獲取1.4 獨占式同步狀態釋放2 共享式2.1 共享式
死磕Java併發:J.U.C之併發工具類:Semaphore
作者:chenssy來源:Java技術棧公眾號訊號量Semaphore是一個控制訪問多個共享資源
J.U.C 的同步工具類
agg googl rri void 之前 ron code number cat 介紹幾個同步工具類,很簡單、常用。 說些廢話啊,最近wo在學習的過程中,多用google搜索,此外,查類的時候,會多看Java api 8 的英文文檔,覺得收獲很多。 1.CyclicB
J.U.C之重入鎖:ReentrantLock
此篇部落格所有原始碼均來自JDK 1.8 ReentrantLock,可重入鎖,是一種遞迴無阻塞的同步機制。它可以等同於synchronized的使用,但是ReentrantLock提供了比synchronized更強大、靈活的鎖機制,可以減少死鎖發生的概率。 API介
【Java】J.U.C併發包 - AQS機制
簡介 Java併發包(java.util.concurrent)中提供了很多併發工具,這其中,很多我們耳熟能詳的併發工具,譬如ReentrantLock、Semaphore,CountDownLatch,CyclicBarrier,它們的實現都用到了一個共同的基類 - Abstrac
【死磕Java併發】-----Java記憶體模型之happens-before
在上篇部落格(【死磕Java併發】—–深入分析volatile的實現原理)LZ提到過由於存線上程本地記憶體和主記憶體的原因,再加上重排序,會導致多執行緒環境下存在可見性的問題。那麼我們正確使用同步、鎖的情況下,執行緒A修改了變數a何時對執行緒B可見? 我們無法就所有場景來規
【死磕Java併發】- 深入分析volatile的實現原理
通過前面一章我們瞭解了synchronized是一個重量級的鎖,雖然JVM對它做了很多優化,而下面介紹的volatile則是輕量級的synchronized。如果一個變數使用volatile,則它比使用synchronized的成本更加低,因為它不會引起執行緒上下文的切換和排程。Java語言
【死磕Java併發】-----Java記憶體模型之分析volatile
volatile可見性;對一個volatile的讀,總可以看到對這個變數最終的寫; volatile原子性;volatile對單個讀/寫具有原子性(32位Long、Double),但是複合操作除外,例如i++; JVM底層採用“記憶體屏障”來實現volat