Semaphore及其用法
1、Semaphore 是什么
Semaphore 通常我們叫它信號量, 可以用來控制同時訪問特定資源的線程數量,通過協調各個線程,以保證合理的使用資源。
比如:停車場入口立著的那個顯示屏,每有一輛車進入停車場顯示屏就會顯示剩余車位減1,每有一輛車從停車場出去,顯示屏上顯示的剩余車輛就會加1,當顯示屏上的剩余車位為0時,停車場入口的欄桿就不會再打開,車輛就無法進入停車場了,直到有一輛車從停車場出去為止。
比如:在學生時代都去餐廳打過飯,假如有3個窗口可以打飯,同一時刻也只能有3名同學打飯。第四個人來了之后就必須在外面等著,只要有打飯的同學好了,就可以去相應的窗口了 。
?
2、使用場景
通常用于那些資源有明確訪問數量限制的場景,常用于限流 。
比如:數據庫連接池,同時進行連接的線程有數量限制,連接不能超過一定的數量,當連接達到了限制數量后,后面的線程只能排隊等前面的線程釋放了數據庫連接才能獲得數據庫連接。
比如:停車場場景,車位數量有限,同時只能容納多少臺車,車位滿了之后只有等里面的車離開停車場外面的車才可以進入。
3、Semaphore常用方法說明
acquire() 獲取一個令牌,在獲取到令牌、或者被其他線程調用中斷之前線程一直處于阻塞狀態。 ? acquire(int permits) 獲取一個令牌,在獲取到令牌、或者被其他線程調用中斷、或超時之前線程一直處于阻塞狀態。acquireUninterruptibly() 獲取一個令牌,在獲取到令牌之前線程一直處于阻塞狀態(忽略中斷)。tryAcquire() 嘗試獲得令牌,返回獲取令牌成功或失敗,不阻塞線程。 ? tryAcquire(long timeout, TimeUnit unit) 嘗試獲得令牌,在超時時間內循環嘗試獲取,直到嘗試獲取成功或超時返回,不阻塞線程。 ? release() 釋放一個令牌,喚醒一個獲取令牌不成功的阻塞線程。 ? hasQueuedThreads() 等待隊列里是否還存在等待線程。 ? getQueueLength() 獲取等待隊列里阻塞的線程數。 ? drainPermits() 清空令牌把可用令牌數置為0,返回清空令牌的數量。 ? availablePermits() 返回可用的令牌數量。4、用semaphore 實現停車場提示牌功能。
每個停車場入口都有一個提示牌,上面顯示著停車場的剩余車位還有多少,當剩余車位為0時,不允許車輛進入停車場,直到停車場里面有車離開停車場,這時提示牌上會顯示新的剩余車位數。
業務場景 :
1、停車場容納總停車量10。
2、當一輛車進入停車場后,顯示牌的剩余車位數響應的減1.
3、每有一輛車駛出停車場后,顯示牌的剩余車位數響應的加1。
4、停車場剩余車位不足時,車輛只能在外面等待。
代碼:
public class TestCar { ?//停車場同時容納的車輛10private static Semaphore semaphore=new Semaphore(10); ?public static void main(String[] args) { ?//模擬100輛車進入停車場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();//獲取令牌嘗試進入停車場System.out.println(Thread.currentThread().getName()+"成功進入停車場");Thread.sleep(new Random().nextInt(10000));//模擬車輛在停車場停留的時間System.out.println(Thread.currentThread().getName()+"駛出停車場");semaphore.release();//釋放令牌,騰出停車場車位} catch (InterruptedException e) {e.printStackTrace();}}},i+"號車"); ?thread.start(); ?} ?} } ?5、Semaphore實現原理
(1)、Semaphore初始化。
Semaphore semaphore=new Semaphore(2);1、當調用new Semaphore(2) 方法時,默認會創建一個非公平的鎖的同步阻塞隊列。
2、把初始令牌數量賦值給同步隊列的state狀態,state的值就代表當前所剩余的令牌數量。
初始化完成后同步隊列信息如下圖:
(2)獲取令牌
semaphore.acquire();1、當前線程會嘗試去同步隊列獲取一個令牌,獲取令牌的過程也就是使用原子的操作去修改同步隊列的state ,獲取一個令牌則修改為state=state-1。
2、 當計算出來的state<0,則代表令牌數量不足,此時會創建一個Node節點加入阻塞隊列,掛起當前線程。
3、當計算出來的state>=0,則代表獲取令牌成功。
源碼:
/*** 獲取1個令牌*/public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);} /*** 共享模式下獲取令牌,獲取成功則返回,失敗則加入阻塞隊列,掛起線程* @param arg* @throws InterruptedException*/public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();//嘗試獲取令牌,arg為獲取令牌個數,當可用令牌數減當前令牌數結果小于0,則創建一個節點加入阻塞隊列,掛起當前線程。if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);} /*** 1、創建節點,加入阻塞隊列,* 2、重雙向鏈表的head,tail節點關系,清空無效節點* 3、掛起當前節點線程* @param arg* @throws InterruptedException*/private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {//創建節點加入阻塞隊列final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {//獲得當前節點pre節點final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);//返回鎖的stateif (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}//重組雙向鏈表,清空無效節點,掛起當前線程if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}線程1、線程2、線程3、分別調用semaphore.acquire(),整個過程隊列信息變化如下圖:
(3)、釋放令牌
semaphore.release();當調用semaphore.release() 方法時
1、線程會嘗試釋放一個令牌,釋放令牌的過程也就是把同步隊列的state修改為state=state+1的過程
2、釋放令牌成功之后,同時會喚醒同步隊列中的一個線程。
3、被喚醒的節點會重新嘗試去修改state=state-1 的操作,如果state>=0則獲取令牌成功,否則重新進入阻塞隊列,掛起線程。
源碼:
/*** 釋放令牌*/public void release() {sync.releaseShared(1);} /***釋放共享鎖,同時會喚醒同步隊列中的一個線程。* @param arg* @return*/public final boolean releaseShared(int arg) {//釋放共享鎖if (tryReleaseShared(arg)) {//喚醒所有共享節點線程doReleaseShared();return true;}return false;} /*** 喚醒同步隊列中的一個線程*/private void doReleaseShared() {for (;;) {Node h = head;if (h != null && h != tail) {int ws = h.waitStatus;if (ws == Node.SIGNAL) {//是否需要喚醒后繼節點if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改狀態為初始0continue;unparkSuccessor(h);//喚醒h.nex節點線程}else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE));}if (h == head) // loop if head changedbreak;}}繼上面的圖,當我們線程1調用semaphore.release(); 時候整個流程如下圖:
編輯于 05-31 10:14
總結
以上是生活随笔為你收集整理的Semaphore及其用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 轩辕传奇服务器合并信息,轩辕传奇9月1日
- 下一篇: ORACLE 多版本读一致性