1. 程式人生 > >Java中Semaphore(訊號量)的使用

Java中Semaphore(訊號量)的使用

Semaphore的作用:

在java中,使用了synchronized關鍵字和Lock鎖實現了資源的併發訪問控制,在同一時間只允許唯一了執行緒進入臨界區訪問資源(讀鎖除外),這樣子控制的主要目的是為了解決多個執行緒併發同一資源造成的資料不一致的問題。在另外一種場景下,一個資源有多個副本可供同時使用,比如印表機房有多個印表機、廁所有多個坑可供同時使用,這種情況下,Java提供了另外的併發訪問控制--資源的多副本的併發訪問控制,今天學習的訊號量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){ 
		semaphore.acquire(); 
        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; 
		lock.lock();
        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鎖的併發訪問控制功能。