Semaphore的作用:
在java中,使用了synchronized關鍵字和Lock鎖實現了資源的並發訪問控制,在同一時間只允許唯一了線程進入臨界區訪問資源(讀鎖除外),這樣子控制的主要目的是為了解決多個線程並發同一資源造成的數據不一致的問題。在另外一種場景下,一個資源有多個副本可供同時使用,比如打印機房有多個打印機、廁所有多個坑可供同時使用,這種情況下,Java提供了另外的並發訪問控制--資源的多副本的並發訪問控制,今天學習的信號量Semaphore即是其中的一種。
Semaphore實現原理初探:
Semaphore是用來保護一個或者多個共享資源的訪問,Semaphore內部維護了一個計數器,其值為可以訪問的共享資源的個數。一個線程要訪問共享資源,先獲得信號量,如果信號量的計數器值大於1,意味着有共享資源可以訪問,則使其計數器值減去1,再訪問共享資源。
如果計數器值為0,線程進入休眠。當某個線程使用完共享資源后,釋放信號量,並將信號量內部的計數器加1,之前進入休眠的線程將被喚醒並再次試圖獲得信號量。
就好比一個廁所管理員,站在門口,只有廁所有空位,就開門允許與空側數量等量的人進入廁所。多個人進入廁所后,相當於N個人來分配使用N個空位。為避免多個人來同時競爭同一個側衛,在內部仍然使用鎖來控制資源的同步訪問。
Semaphore的使用:
Semaphore使用時需要先構建一個參數來指定共享資源的數量,Semaphore構造完成后即是獲取Semaphore、共享資源使用完畢后釋放Semaphore。
Semaphore semaphore = new Semaphore(10,true); semaphore.acquire(); //do something here semaphore.release();
下面的代碼就是模擬控制商場廁所的並發使用:
public class ResourceManage {
private final Semaphore semaphore ;
private boolean resourceArray[];
private final ReentrantLock lock;
public ResourceManage() {
this.resourceArray = new boolean[10];//存放廁所狀態
this.semaphore = new Semaphore(10,true);//控制10個共享資源的使用,使用先進先出的公平模式進行共享;公平模式的信號量,先來的先獲得信號量
this.lock = new ReentrantLock(true);//公平模式的鎖,先來的先選
for(int i=0 ;i<10; i++){
resourceArray[i] = true;//初始化為資源可用的情況
}
}
public void useResource(int userId){
try{
semaphore.acquire();
int id = getResourceId();//占到一個坑
System.out.print("userId:"+userId+"正在使用資源,資源id:"+id+"\n");
Thread.sleep(100);//do something,相當於於使用資源
resourceArray[id] = true;//退出這個坑
}catch (InterruptedException e){
e.printStackTrace();
}finally {
semaphore.release();//釋放信號量,計數器加1
}
}
private int getResourceId(){
int id = -1;
try {
lock.lock();//雖然使用了鎖控制同步,但由於只是簡單的一個數組遍歷,效率還是很高的,所以基本不影響性能。
for(int i=0; i<10; i++){
if(resourceArray[i]){
resourceArray[i] = false;
id = i;
break;
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
return id;
}
}
public class ResourceUser implements Runnable{
private ResourceManage resourceManage;
private int userId;
public ResourceUser(ResourceManage resourceManage, int userId) {
this.resourceManage = resourceManage;
this.userId = userId;
}
public void run(){
System.out.print("userId:"+userId+"准備使用資源...\n");
resourceManage.useResource(userId);
System.out.print("userId:"+userId+"使用資源完畢...\n");
}
public static void main(String[] args){
ResourceManage resourceManage = new ResourceManage();
Thread[] threads = new Thread[100];
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(new ResourceUser(resourceManage,i));//創建多個資源使用者
threads[i] = thread;
}
for(int i = 0; i < 100; i++){
Thread thread = threads[i];
try {
thread.start();//啟動線程
}catch (Exception e){
e.printStackTrace();
}
}
}
}
最后,Semaphore除了控制資源的多個副本的並發訪問控制,也可以使用二進制信號量來實現類似synchronized關鍵字和Lock鎖的並發訪問控制功能。