Java控制並發線程數的Semaphore
Semaphore(訊號量)是用來控制同時訪問特定資源的線程數量,它通過協調各個線程,以保證合理的使用公用資源。以前我都覺得從字面上很難理解Semaphore所表達的含義,只能把它比作是控制流程量的紅綠燈,比如XX馬路要限制流量,只允許同時有一百輛車在這條路上行使,其他的都必須在路口等待,所以前一百輛車會看到綠燈,可以開進這條馬路,後面的車會看到紅燈,不能駛入XX馬路,但是如果前一百輛中有五輛車已經離開了XX馬路,那麼後面就允許有5輛車駛入馬路,這個例子裡說的車就是線程,駛入馬路就表示線程在執行,離開馬路就表示線程執行完成,看見紅燈就表示線程被阻塞,不能執行。
應用情境
Semaphore可以用於做流量控制,特別公用資源有限的應用情境,比如資料庫連接。假如有一個需求,要讀取幾萬個檔案的資料,因為都是IO密集型任務,我們可以啟動幾十個線程並發的讀取,但是如果讀到記憶體後,還需要儲存到資料庫中,而資料庫的串連數只有10個,這時我們必須控制只有十個線程同時擷取資料庫連接儲存資料,否則會報錯無法擷取資料庫連接。這個時候,我們就可以使用Semaphore來做流控,代碼如下:
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorService threadPool = Executors
.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
s.acquire();
System.out.println("save data");
s.release();
} catch (InterruptedException e) {
}
}
});
}
threadPool.shutdown();
}
}
在代碼中,雖然有30個線程在執行,但是只允許10個並發的執行。Semaphore的構造方法Semaphore(int permits) 接受一個整型的數字,表示可用的許可證數量。Semaphore(10)表示允許10個線程擷取許可證,也就是最大並發數是10。Semaphore的用法也很簡單,首先線程使用Semaphore的acquire()擷取一個許可證,使用完之後調用release()歸還許可證。還可以用tryAcquire()方法嘗試擷取許可證。
其他方法
Semaphore還提供一些其他方法:
int availablePermits() :返回此訊號量中當前可用的許可證數。
int getQueueLength():返回正在等待擷取許可證的線程數。
boolean hasQueuedThreads() :是否有線程正在等待擷取許可證。
void reducePermits(int reduction) :減少reduction個許可證。是個protected方法。
Collection getQueuedThreads() :返回所有等待擷取許可證的線程集合。是個protected方法。
面試題思考
在很多情況下,可能有多個線程需要訪問數目很少的資源。假想在伺服器上運行著若干個回答用戶端請求的線程。這些線程需要串連到同一資料庫,但任一時刻只能獲得一定數目的資料庫連接。你要怎樣才能夠有效地將這些固定數目的資料庫連接分配給大量的線程?
答:
1.給方法加同步鎖,保證同一時刻只能有一個人去調用此方法,其他所有線程排隊等待,但是此種情況下即使你的資料庫連結有10個,也始終只有一個處於使用狀態。這樣將會大大的浪費系統資源,而且系統的運行效率非常的低下。
2.另外一種方法當然是使用訊號量,通過訊號量許可與資料庫可用串連數相同的數目,將大大的提高效率和效能。