面試官一聲冷笑:用過semaphore吧,說說信號量模型?
信號量模型可簡單概括為:一個計數器,一個等待佇列,三個方法,在信號量模型里,計數器和等待佇列對外是透明的,所以只能通過信號量模型提供的三個方法來訪問它們,這三個方法分別是:init()、down()和up(),你可以結合下圖來形象化地理解,
- 信號量模型

詳細解釋下里面提到的這些方法?
- init()
設定計數器初始值, - down()
計數器-1;若此時計數器<0,則當前執行緒被阻塞,否則當前執行緒可繼續執行 - up()
計數器+1;若此時計數器≤0,則喚醒等待佇列中的一個執行緒,并將其從等待佇列中移除,有的人可能認為這里的判斷條件應該≥0,估計你是理解生產者-消費者模式中的生產者,可這樣思考,>0意味著沒有阻塞的執行緒,所以只有 ≤0 時才需要喚醒一個等待的執行緒,
down()和up()應該成對出現,并且先呼叫down()獲取鎖,處理完成后再呼叫up()釋放鎖,若信號量init值為1,應該不會出現>0情況,除非故意調先用up(),這也失去了信號量本身的意義了,
這些方法都是原子性的,并且這個原子性是由信號量模型的實作方保證的,JDK里的信號量模型是由java.util.concurrent.Semaphore實作,Semaphore這個類能夠保證這三個方法都是原子操作,
talk is cheap,show me code?
- 代碼

信號量模型中,down()、up()最早被稱為P操作和V操作,信號量模型也稱PV原語,還有的人會用semWait()和semSignal()表達它們,叫法不同,語意都相同,JUC的acquire()和release()就對應down()和up(),
如何使用信號量?
就像紅綠信號燈,車必須先檢查是否為綠燈,綠燈才能通過,
比如累加器,count+=1操作是個臨界區,只允許一個執行緒執行,也就是說要保證互斥,

分析如下:假設執行緒t1、t2同時訪問add(),當同時呼叫acquire()時,由于acquire()是一個原子操作,只可能有一個執行緒(假設t1)把信號量里的計數器減為0,t2則是將計數器減為-1:
- 對t1,信號量里面的計數器的值是0,≥0,所以t1繼續執行
- 對t2,信號量里面的計數器的值是-1,<0,所以t2被阻塞
所以此時只有t1會進入臨界區執行count+=1,
當t1執行release(),信號量里計數器的值是-1,加1之后的值是0,小于等于0,根據up()操作,此時等待佇列中的t2會被喚醒,于是t2在t1執行完臨界區代碼后,才獲得進入臨界區執行的機會,這就保證了互斥性,
既然有JDK提供了Lock,為啥還要提供一個Semaphore ?實作互斥鎖,僅是 Semaphore部分功能,Semaphore還可以允許多個執行緒訪問一個臨界區,
最常見的就是各種池化資源:連接池、物件池、執行緒池等,比如資料庫連接池,同一時刻,一定是允許多個執行緒同時使用連接池的,每個連接在被釋放前,是不允許其他執行緒使用的,
物件池要求一次性創建出N個物件,之后所有的執行緒重復利用這N個物件,當然物件在被釋放前,也是不允許其他執行緒使用的,所以核心就是限流器的設計,這里限流指不允許多于N個執行緒同時進入臨界區,
如何快速實作一個這樣的限流器呢?那就是信號量,
如果我們把計數器的值設定成物件池里物件的個數N,就能完美解決物件池的限流問題了,
代碼如下:

注意這里使用的是 Vector,進入臨界區的N個執行緒不安全,add/remove都是不安全的,比如 ArrayList remove() :
public E remove(int index) {
rangeCheck(index);
modCount++;
// 假設倆執行緒 t1,t2都執行到這一步,t1 讓出cpu,t2執行
E oldValue = elementData(index);
// 到這步,t1繼續執行,這時t1,t2拿到的oldValue是一樣的,兩個執行緒能拿到同一個物件,執行緒不安全!
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
好的,請回家等通知吧!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/279240.html
標籤:java
下一篇:GUI鍵盤監聽事件(java)
