Java併發——訊號量Semaphore
1. 訊號量Semaphore
訊號量維護了一組許可證,以約束訪問被限制資源的執行緒數。
類java.util.concurrent.Semaphore實現了訊號量。
這段文字轉自:https://blog.csdn.net/zbc1090549839/article/details/53389602
在java中,使用了synchronized關鍵字和Lock鎖實現了資源的併發訪問控制,在同一時間只允許唯一了執行緒進入臨界區訪問資源(讀鎖除外),這樣子控制的主要目的是為了解決多個執行緒併發同一資源造成的資料不一致的問題。在另外一種場景下,一個資源有多個副本可供同時使用,比如印表機房有多個印表機、廁所有多個坑可供同時使用,這種情況下,Java提供了另外的併發訪問控制--資源的多副本的併發訪問控制,今天學習的訊號量Semaphore即是其中的一種
。Semaphore是用來保護一個或者多個共享資源的訪問,Semaphore內部維護了一個計數器,其值為可以訪問的共享資源的個數。一個執行緒要訪問共享資源,先獲得訊號量,如果訊號量的計數器值大於1,意味著有共享資源可以訪問,則使其計數器值減去1,再訪問共享資源。
如果計數器值為0,執行緒進入休眠。當某個執行緒使用完共享資源後,釋放訊號量,並將訊號量內部的計數器加1,之前進入休眠的執行緒將被喚醒並再次試圖獲得訊號量。
就好比一個廁所管理員,站在門口,只有廁所有空位,就開門允許與空側數量等量的人進入廁所。多個人進入廁所後,相當於N個人來分配使用N個空位。為避免多個人來同時競爭同一個側衛,在內部仍然使用鎖來控制資源的同步訪問。
2.訊號量的使用,常用方法
(1)Semaphore(int permits)建構函式
初始化一個訊號量,其中permits指定了可用許可證的數量。
(2)Semaphore(int permits, boolean fair)建構函式
初始化一個訊號量,其中permits指定了可用許可證的數量,fair設定公平策略。
當公平策略設定為false,訊號量不會保證執行緒獲取許可證的順序,也就是說,搶佔是允許的。
當公平策略設定為true,訊號量就能保住呼叫acquire()方法的任意執行緒能按照方法被呼叫處理的順序獲取許可證(先進先出,FIFO)。
(3)void acquire()
從這個訊號量中獲取一個許可證,否則阻塞直到有一個許可證可用或者呼叫執行緒被中斷。
(4)void acquire(int permits)
從這個訊號量中獲取permits數量的許可證,否則阻塞直到有一個許可證可用或者呼叫執行緒被中斷。
(5)int availablePermits()
返回當前可用許可證的數目。
(6)int drainPermits()
獲取並返回立即可用的許可證的數量。
(7)void release()
釋放一個許可證,將其放回給訊號量。可用許可證的數目增加1.
(8)void release(int permits)
釋放permits數量的許可證,將其放回給訊號量。可用許可證的數目增加permits個.
3. 示例
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args)
{
Semaphore semaphore = new Semaphore(3, true);
Random random = new Random();
Runnable r = new Runnable() {
@Override
public void run()
{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+ "開始工作!");
work();
System.out.println(Thread.currentThread().getName()+ "工作結束!");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
semaphore.release(); //在使用完之後要釋放許可證
}
}
void work()
{
int worktime = random.nextInt(1000);
System.out.println(Thread.currentThread().getName()+"工作時間:"+ worktime);
try {
Thread.sleep(worktime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i = 0; i < 6; i++)
{
executorService.execute(r);
}
executorService.shutdown();
}
}
執行結果:
pool-1-thread-1開始工作!
pool-1-thread-1工作時間:319
pool-1-thread-4開始工作!
pool-1-thread-4工作時間:503
pool-1-thread-2開始工作!
pool-1-thread-2工作時間:954
pool-1-thread-1工作結束!
pool-1-thread-3開始工作!
pool-1-thread-3工作時間:996
pool-1-thread-4工作結束!
pool-1-thread-5開始工作!
pool-1-thread-5工作時間:827
pool-1-thread-2工作結束!
pool-1-thread-6開始工作!
pool-1-thread-6工作時間:857
pool-1-thread-3工作結束!
pool-1-thread-5工作結束!
pool-1-thread-6工作結束!
剛開始,只有執行緒1,4,2在開始工作。只有當執行緒1結束,釋放一個許可證之後,執行緒3才開始工作。執行緒4結束工作釋放許可證之後,執行緒5就可以開始工作了。也就是說同一時間段只能有3個許可證可用。