Java中的信號量(Semaphore)

初識Semaphore

  • “信號量”,也可以稱其爲“信號燈”,它的存在就如同生活中的紅綠燈一般,用來控制車輛的通行。在程序員眼中,線程就好比行駛的車輛,程序員就可以通過信號量去指定線程是否可以執行,並且可以指定訪問臨界區的線程數量

信號量模型

  • 信號量的模型很簡單,有:一個計數器,一個等待隊列,三個方法(init,down,up)。在該模型中,計數器與等待隊列對外是透明的,只能去通過三個方法區訪問。

三個方法詳解:

  1. init():設置計數器的初始值(信號量是支持多個線程訪問一個臨界區的,所以可以通過設置計數器的初始值來控制線程訪問臨界資源的數量)
  2. down():計數器減一操作;如果此時計數器的值小於0,則當前線程被阻塞,否則當前線程可以繼續執行。
  3. up():計數器加一操作;如果此時計數器的值小於或者等於 0,則喚醒等待隊列中的一個線程,並將其從等待隊列中移除。

注意:這裏的三個方法均是原子操作。在Java SDK裏,信號量是由java.util.concurrent.Semaphore實現的,Semaphore可以保證方其都是原子操作。並且在Java SDK併發包中,down()和up()對應的是acquire()和release()方法。

參考下面代碼感受一下信號量模型:

class Semaphore{
  // 計數器
  int count;
  // 等待隊列
  Queue queue;
  // 初始化操作
  Semaphore(int c){
    this.count=c;
  }
  // 
  void down(){
    this.count--;
    if(this.count<0){
      // 將當前線程插入等待隊列
      // 阻塞當前線程
    }
  }
  void up(){
    this.count++;
    if(this.count<=0) {
      // 移除等待隊列中的某個線程 T
      // 喚醒線程 T
    }
  }
}

 

關於信號量的使用

1. 如何互斥操作

正如開篇介紹信號量可以控制線程的執行,相當於互斥鎖一樣,控制單一線程對臨界資源的訪問。而限號量正是通過計數器來實現互斥規則的。

如下代碼:

  • 在進入臨界區之前執行down()方法,退出前執行up()方法皆就可以了。在實例代碼中acquire就相當於down方法,release相當於up方法;
static int count;
// 初始化信號量
static final Semaphore s 
    = new Semaphore(1);
// 用信號量保證互斥    
static void addOne() {
  s.acquire();
  try {
    count+=1;
  } finally {
    s.release();
  }
}

信號量具體如何實現互斥?

  • 比如現有兩個線程 t1與t2,而我們規定在初始化計數器時將計數器設置爲1,表明臨界區只允許一個線程去訪問。當兩個線程同時訪問addOne()方法時,兩個線程同時執行acquire()方法,由於acquire()是一個原子操作,當t1將計數器值減爲0,t2將計數器減爲-1。對於t1而言,信號量的計數器值爲0,滿足大於等於0條件,所以t1會繼續執行;對於t2而言,信號量裏邊的計數器爲-1,小於0,按照down()操作的描述,t2會被阻塞。因此只有t1線程進入臨界區執行count+=1操作。
  • 當t1執行release()操作時,信號量裏邊的計數器會+1,此時爲0,小於等於0,因此會將處於等待隊列中的t2線程喚醒。於是t2在t1執行完臨界代碼之後進入到了臨界區。從而保證了互斥性。

 

1. Semaphore如何實現多個線程訪問一個臨界區

Semaphore有一個獨特的功能,就是可以允許多個線程訪問一個臨界區。這裏就通過“快速實現一個限流器”來說明;

  • 對象池:指的是一次性創建出 N 個對象,之後所有的線程重複利用這 N 個對象,當然對象在被釋放前,也是不允許其他線程使用的。
  • 這裏的限流就是指不允許多餘N個線程同時進入臨界區

解決方法:在上一個例子中我們將計數器初始化爲1,表示只允許一個線程進入臨界區,現在我們將計數器設置爲對象池中的對象個數N,表明在同一個時刻允許N個線程可以同時進入到臨界區,就可以解決限流問題了;

 

思考:信號量在在執行release()方法後如果滿足喚醒其他線程條件就會去喚醒一個線程繼續執行,這裏爲什麼不能去喚醒所有線程呢?

  • 由於信號量沒有Condition概念,當阻塞線程被喚醒會直接運行,不會去檢查此時的臨界條件是否滿足,因此信號量只允許喚醒一個阻塞線程,否則就會出現缺少臨界條件檢查而帶來的線程安全問題。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章