Java的Semaphore信號量使用及原理解析
1、Semaphore 是什么
Semaphore 通常我們叫它信號量, 可以用來控制同時(shí)訪問特定資源的線程數(shù)量,通過協(xié)調(diào)各個(gè)線程,以保證合理的使用資源。
可以把它簡單的理解成我們停車場入口立著的那個(gè)顯示屏,每有一輛車進(jìn)入停車場顯示屏就會顯示剩余車位減1,每有一輛車從停車場出去,顯示屏上顯示的剩余車輛就會加1,當(dāng)顯示屏上的剩余車位為0時(shí),停車場入口的欄桿就不會再打開,車輛就無法進(jìn)入停車場了,直到有一輛車從停車場出去為止。
2、使用場景
朱勇用于那些資源有明確訪問數(shù)量限制的場景,常用于限流 。
比如:數(shù)據(jù)庫連接池,同時(shí)進(jìn)行連接的線程有數(shù)量限制,連接不能超過一定的數(shù)量,當(dāng)連接達(dá)到了限制數(shù)量后,后面的線程只能排隊(duì)等前面的線程釋放了數(shù)據(jù)庫連接才能獲得數(shù)據(jù)庫連接。
比如:停車場場景,車位數(shù)量有限,同時(shí)只能容納多少臺車,車位滿了之后只有等里面的車離開停車場外面的車才可以進(jìn)入。
3、Semaphore常用方法說明
信號量的構(gòu)造函數(shù) 非公平:
public Semaphore(int permits);//permits就是允許同時(shí)運(yùn)行的線程數(shù)目
公平(獲得鎖的順序與線程啟動(dòng)順序有關(guān)):
public Semaphore(int permits,boolean fair);//permits就是允許同時(shí)運(yùn)行的線程數(shù)目
acquire() 獲取一個(gè)令牌,在獲取到令牌、或者被其他線程調(diào)用中斷之前線程一直處于阻塞狀態(tài)。 ? acquire(int permits) 獲取一個(gè)令牌,在獲取到令牌、或者被其他線程調(diào)用中斷、或超時(shí)之前線程一直處于阻塞狀態(tài)。 acquireUninterruptibly() 獲取一個(gè)令牌,在獲取到令牌之前線程一直處于阻塞狀態(tài)(忽略中斷)。 tryAcquire() 嘗試獲得令牌,返回獲取令牌成功或失敗,不阻塞線程。 ? tryAcquire(long timeout, TimeUnit unit) 嘗試獲得令牌,在超時(shí)時(shí)間內(nèi)循環(huán)嘗試獲取,直到嘗試獲取成功或超時(shí)返回,不阻塞線程。 ? release() 釋放一個(gè)令牌,喚醒一個(gè)獲取令牌不成功的阻塞線程。 ? hasQueuedThreads() 等待隊(duì)列里是否還存在等待線程。 ? getQueueLength() 獲取等待隊(duì)列里阻塞的線程數(shù)。 ? drainPermits() 清空令牌把可用令牌數(shù)置為0,返回清空令牌的數(shù)量。 ? availablePermits() 返回可用的令牌數(shù)量。
4、用semaphore 實(shí)現(xiàn)停車場提示牌功能。
每個(gè)停車場入口都有一個(gè)提示牌,上面顯示著停車場的剩余車位還有多少,當(dāng)剩余車位為0時(shí),不允許車輛進(jìn)入停車場,直到停車場里面有車離開停車場,這時(shí)提示牌上會顯示新的剩余車位數(shù)。
業(yè)務(wù)場景 :
1、停車場容納總停車量10。
2、當(dāng)一輛車進(jìn)入停車場后,顯示牌的剩余車位數(shù)響應(yīng)的減1.
3、每有一輛車駛出停車場后,顯示牌的剩余車位數(shù)響應(yīng)的加1。
4、停車場剩余車位不足時(shí),車輛只能在外面等待。
代碼:
public class TestCar { ? //停車場同時(shí)容納的車輛10 private static Semaphore semaphore=new Semaphore(10); ? public static void main(String[] args) { ? //模擬100輛車進(jìn)入停車場 for(int i=0;i<100;i++){ ? Thread thread=new Thread(new Runnable() { public void run() { try { System.out.println("===="+Thread.currentThread().getName()+"來到停車場"); if(semaphore.availablePermits()==0){ System.out.println("車位不足,請耐心等待"); } semaphore.acquire();//獲取令牌嘗試進(jìn)入停車場 System.out.println(Thread.currentThread().getName()+"成功進(jìn)入停車場"); Thread.sleep(new Random().nextInt(10000));//模擬車輛在停車場停留的時(shí)間 System.out.println(Thread.currentThread().getName()+"駛出停車場"); semaphore.release();//釋放令牌,騰出停車場車位 } catch (InterruptedException e) { e.printStackTrace(); } } },i+"號車"); ? thread.start(); ? } ? } } ?
5、Semaphore實(shí)現(xiàn)原理
(1)、Semaphore初始化
Semaphore semaphore=new Semaphore(2);
1、當(dāng)調(diào)用new Semaphore(2) 方法時(shí),默認(rèn)會創(chuàng)建一個(gè)非公平的鎖的同步阻塞隊(duì)列。
2、把初始令牌數(shù)量賦值給同步隊(duì)列的state狀態(tài),state的值就代表當(dāng)前所剩余的令牌數(shù)量。
初始化完成后同步隊(duì)列信息如下圖:
(2)獲取令牌
semaphore.acquire();
1、當(dāng)前線程會嘗試去同步隊(duì)列獲取一個(gè)令牌,獲取令牌的過程也就是使用原子的操作去修改同步隊(duì)列的state ,獲取一個(gè)令牌則修改為state=state-1。
2、 當(dāng)計(jì)算出來的state<0,則代表令牌數(shù)量不足,此時(shí)會創(chuàng)建一個(gè)Node節(jié)點(diǎn)加入阻塞隊(duì)列,掛起當(dāng)前線程。
3、當(dāng)計(jì)算出來的state>=0,則代表獲取令牌成功。
源碼:
/** * 獲取1個(gè)令牌 */ public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
/** * 共享模式下獲取令牌,獲取成功則返回,失敗則加入阻塞隊(duì)列,掛起線程 * @param arg * @throws InterruptedException */ public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); //嘗試獲取令牌,arg為獲取令牌個(gè)數(shù),當(dāng)可用令牌數(shù)減當(dāng)前令牌數(shù)結(jié)果小于0,則創(chuàng)建一個(gè)節(jié)點(diǎn)加入阻塞隊(duì)列,掛起當(dāng)前線程。 if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
/** * 1、創(chuàng)建節(jié)點(diǎn),加入阻塞隊(duì)列, * 2、重雙向鏈表的head,tail節(jié)點(diǎn)關(guān)系,清空無效節(jié)點(diǎn) * 3、掛起當(dāng)前節(jié)點(diǎn)線程 * @param arg * @throws InterruptedException */ private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { //創(chuàng)建節(jié)點(diǎn)加入阻塞隊(duì)列 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { //獲得當(dāng)前節(jié)點(diǎn)pre節(jié)點(diǎn) final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg);//返回鎖的state if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } //重組雙向鏈表,清空無效節(jié)點(diǎn),掛起當(dāng)前線程 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
線程1、線程2、線程3、分別調(diào)用semaphore.acquire(),整個(gè)過程隊(duì)列信息變化如下圖:
(3)、釋放令牌
semaphore.release();
當(dāng)調(diào)用semaphore.release() 方法時(shí)
1、線程會嘗試釋放一個(gè)令牌,釋放令牌的過程也就是把同步隊(duì)列的state修改為state=state+1的過程
2、釋放令牌成功之后,同時(shí)會喚醒同步隊(duì)列的所有阻塞節(jié)共享節(jié)點(diǎn)線程
3、被喚醒的節(jié)點(diǎn)會重新嘗試去修改state=state-1 的操作,如果state>=0則獲取令牌成功,否則重新進(jìn)入阻塞隊(duì)列,掛起線程。
源碼:
/** * 釋放令牌 */ public void release() { sync.releaseShared(1); }
/** *釋放共享鎖,同時(shí)喚醒所有阻塞隊(duì)列共享節(jié)點(diǎn)線程 * @param arg * @return */ public final boolean releaseShared(int arg) { //釋放共享鎖 if (tryReleaseShared(arg)) { //喚醒所有共享節(jié)點(diǎn)線程 doReleaseShared(); return true; } return false; }
/** * 喚醒所有共享節(jié)點(diǎn)線程 */ private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) {//是否需要喚醒后繼節(jié)點(diǎn) if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改狀態(tài)為初始0 continue; unparkSuccessor(h);//喚醒h.nex節(jié)點(diǎn)線程 } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)); } if (h == head) // loop if head changed break; } }
繼上面的圖,當(dāng)我們線程1調(diào)用semaphore.release(); 時(shí)候整個(gè)流程如下圖:
到此這篇關(guān)于Java的Semaphore信號量使用及原理解析的文章就介紹到這了,更多相關(guān)Semaphore信號量解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Maven插件構(gòu)建Docker鏡像的實(shí)現(xiàn)步驟
這篇文章主要介紹了Maven插件構(gòu)建Docker鏡像的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10解決@Cacheable在同一個(gè)類中方法調(diào)用不起作用的問題
這篇文章主要介紹了解決@Cacheable在同一個(gè)類中方法調(diào)用不起作用的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07詳解Spring?Security?捕獲?filter?層面異常返回我們自定義的內(nèi)容
Spring?的異常會轉(zhuǎn)發(fā)到?BasicErrorController?中進(jìn)行異常寫入,然后才會返回客戶端。所以,我們可以在?BasicErrorController?對?filter異常進(jìn)行捕獲并處理,下面通過本文給大家介紹Spring?Security?捕獲?filter?層面異常,返回我們自定義的內(nèi)容,感興趣的朋友一起看看吧2022-05-05SpringBoot整合Ip2region獲取IP地址和定位的詳細(xì)過程
ip2region v2.0 - 是一個(gè)離線IP地址定位庫和IP定位數(shù)據(jù)管理框架,10微秒級別的查詢效率,提供了眾多主流編程語言的 xdb 數(shù)據(jù)生成和查詢客戶端實(shí)現(xiàn) ,這篇文章主要介紹了SpringBoot整合Ip2region獲取IP地址和定位,需要的朋友可以參考下2023-06-06