Semaphore(信號量)
信號量可以簡單的概括為:一個計數(shù)器,一個等待隊列,三個方法。在信號量模型里,計數(shù)器和等待隊列對外是透明的,所以只能通過信號量模型提供的三個方法來訪問它們,這三個方法分別是:init()、down()和up()。

這三個方法詳細(xì)的語義具體如下所示:
- init():設(shè)置計數(shù)器的初始值。
- down():計數(shù)器的值減1;如果此時計數(shù)器的值小于0,則當(dāng)前線程將被阻塞,否則當(dāng)前線程可以繼續(xù)執(zhí)行。
- up():計數(shù)器的值加1;如果此時計數(shù)器的值小于或者等于0,則喚醒等待隊列中的一個線程,并將其從等待隊列中移除。
在Java SDK里面,信號量模型是由java.util.concurrent.Semaphore實(shí)現(xiàn)的,Semaphore這個類能夠保證這三個方法都是原子操作。
class Semaphore{
// 計數(shù)器
int count;
// 等待隊列
Queue queue;
// 初始化操作
Semaphore(int c){
this.count=c;
}
//
void down(){
this.count--;
if(this.count<0){
//將當(dāng)前線程插入等待隊列
//阻塞當(dāng)前線程
}
}
void up(){
this.count++;
if(this.count<=0) {
//移除等待隊列中的某個線程T
//喚醒線程T
}
}
}
down()、up()這兩個操作歷史上最早稱為P操作和V操作,所以信號量模型也被稱為PV原語。在Java SDK并發(fā)包里,down()和up()對應(yīng)的則是acquire()和release()。
信號量是如何保證互斥的?
- 假設(shè)兩個線程T1和T2同時訪問addOne()方法,當(dāng)它們同時調(diào)用acquire()的時候,由于acquire()是一個原子操作,所以只能有一個線程(假設(shè)T1)把信號量里的計數(shù)器減為0,另外一個線程(T2)則是將計數(shù)器減為-1。對于線程T1,信號量里面的計數(shù)器的值是0,大于等于0,所以線程T1會繼續(xù)執(zhí)行;對于線程T2,信號量里面的計數(shù)器的值是-1,小于0,按照信號量模型里對down()操作的描述,線程T2將被阻塞。所以此時只有線程T1會進(jìn)入臨界區(qū)執(zhí)行count+=1;。
- 當(dāng)線程T1執(zhí)行release()操作,也就是up()操作的時候,信號量里計數(shù)器的值是-1,加1之后的值是0,小于等于0,按照信號量模型里對up()操作的描述,此時等待隊列中的T2將會被喚醒。于是T2在T1執(zhí)行完臨界區(qū)代碼之后才獲得了進(jìn)入臨界區(qū)執(zhí)行的機(jī)會,從而保證了互斥性。
快速實(shí)現(xiàn)一個限流器
Semaphore除了能像lock一樣實(shí)現(xiàn)互斥鎖之外,還可以允許多個線程訪問同一個臨界區(qū)。(這個功能是lock不容易實(shí)現(xiàn)的)
eg:如連接池、對象池、線程池等等。數(shù)據(jù)庫連接池,在同一時刻,一定是允許多個線程同時使用連接池的,當(dāng)然,每個連接在被釋放前,是不允許其他線程使用的。
下面是一個常量池的一個實(shí)現(xiàn)代碼:
class ObjPool<T, R> {
final List<T> pool;
// 用信號量實(shí)現(xiàn)限流器
final Semaphore sem;
// 構(gòu)造函數(shù)
ObjPool(int size, T t){
pool = new Vector<T>(){};
for(int i=0; i<size; i++){
pool.add(t);
}
sem = new Semaphore(size);
}
// 利用對象池的對象,調(diào)用func
R exec(Function<T,R> func) {
T t = null;
sem.acquire();
try {
t = pool.remove(0);
return func.apply(t);
} finally {
pool.add(t);
sem.release();
}
}
}
// 創(chuàng)建對象池
ObjPool<Long, String> pool =
new ObjPool<Long, String>(10, 2);
// 通過對象池獲取t,之后執(zhí)行
pool.exec(t -> {
System.out.println(t);
return t.toString();
});
總結(jié)一下:Semaphore就像旋轉(zhuǎn)壽司店,共有10個座位,當(dāng)座位有空余時,等待的人就可以坐上去。如果有只有2個空位,來的是一家3口,那就只有等待。如果來的是一對情侶,就可以直接坐上去吃。當(dāng)然如果同時空出5個空位,那一家3口和一對情侶可以同時上去吃。CountDownLatch就像大型商場里面的臨時游樂場,每一場游樂的時間過后等待的人同時進(jìn)場玩,而一場中間會有不愛玩了的人隨時出來,但不能進(jìn)入,一旦所有進(jìn)入的人都出來了,新一批人就可以同時進(jìn)場
他們與非共享最大的區(qū)別就在于是否能多個線程同時獲取鎖。
這里有semaphore和countDownlatch的一篇文章。