寫在前面:
這是睡眠理發師問題加強版的Java解決方案參考,是一次操作系統實驗的分析報告。實驗問題完整描述可參考實驗完整描述以及要求文檔。實驗的完整代碼可參考Demo。
理發師問題描述:
一個理發店由一個有n個椅子的等候室和一個有一個理發椅的理發室組成。
- 如果有沒有顧客來服務,理發師就去睡覺了。
- 如果顧客走進理發店和所有的椅子被占用了,然后顧客離開了商店。
- 如果理發師很忙,但是椅子是可用的,那么顧客坐在一張免費的椅子上。
- 如果理發師睡著了,顧客就會叫醒理發師。
這是課本上的理發師問題,對于這個問題的解答網上有很多解法,可參考:CSDN 進程(線程)間同步互斥問題
(三) 熟睡的理發師問題
加強版的問題描述:
一個理發店由一個有n個椅子的等候室和一個有m理發椅的理發室組成。
- 如果有沒有顧客可以服務,所有的理發師都去睡覺。
- 如果顧客走進理發店椅子被占用了,然后顧客離開了商店。
- 如果所有的理發師都很忙,但是椅子是可用的,然后顧客坐在一張免費的椅子上。
- 如果理發師睡著了,顧客就會醒過來的理發師。
實驗完整問題描述以及要求文檔鏈接
問題流程分析:
讓我們先來看看一個理發師的場景再現:
- 陽光明媚的早上,商店開門。店里面空空如也,理發師伸了個懶腰,睡回籠覺去了。
- 一位顧客來了,發現理發師都在睡覺,走到理發師面前,拍醒了理發師。
- 理發師醒了之后,十分抱歉,趕快給顧客理發。
- 理發完成,理發師告訴顧客:發理好了。
- 客戶答到:好的!轉身離開理發店。
- 理發師呼叫一下一個顧客
- 若發現理發店恢復了空空如也的狀態,就繼續去睡覺了
- 若在還有顧客在椅子上等待,理發師就去喚醒椅子上睡覺的顧客。
+ 顧客隨理發師坐到理發椅上,等待理發師理發完成
+ 重復步驟4
....
當有多個理發師的時候會怎么樣呢。言語有點難以描述了,但可以看作多個單理發師的理發師店共享等待椅子隊列。每個理發師,訪問同一個的等待椅子隊列,但是,理發的時候互不影響。
技術需求
在Java中對于多線程同步的支持有很多方案。除了簡單的鎖對象(Class Lock),和條件對象(Class Condition)搭配使用之外,還有Synchronization關鍵字用來保護一個代碼片段,避免多個線程同時修改臨界區內容,也可以使用阻塞隊列等。我感覺鎖和條件對象比較適合這一題的解答。
鎖和條件對象的的使用:
private Lock lock=new ReentrantLock();
lock.lick();//獲取這個鎖,如果這個鎖被另外一個線程擁有則阻塞
lock.unlock();//釋放鎖
private Condition condition = lock.newCondition();
condition.await();//阻塞當前線程
condition.signalAll();//釋放擁有因為condition.await()的線程,將其放到等待隊列。該線程釋放鎖的時候執行。
condition.siginal();//在阻塞隊列中隨機釋放一個線程,將其放到等待隊列。該線程釋放鎖的時候執行。
那么問題來了,我們需要哪些鎖呢?我們再看一個理發師的情況:
- 理發師在沒有顧客的時候,調用自己的Condition.await()。
- 用戶來的時候調用Barber.Condition.singalAll();并調用 自己的Condition.await()即可;
- 理發師線程釋放之后進一步向前推進,直達下一次和客戶溝通的時候,掛起自己,喚醒客戶線程。
- 重復上述就可完成理發師線程和用戶線程的溝通了。
一個理發師鎖,一個用戶鎖,一個互斥鎖就行了。
那么多個理發師的時候,每個理發師都有自己的用戶,理發師和用戶之間的信息交換是1對1的,那么也就是說每個理發師都有自己的鎖和條件對象,以供顧客調用。與此同時,每個顧客應該也有自己的鎖和對象讓理發師調用。畢竟理發師們只不過是共享了用戶隊列。
討論題:
1.理發師數量為 1 下,離開用戶和椅子數量關系
理論分析:
理發師的數量為 1 的時候,每增加 n 把椅子,用戶等待數量緩沖區增加 n,即滯留用戶離開數量減少 n。
實驗數據證明:
結合圖標可知,該擬合曲線為的斜率近似于-1 的直線,即每增加 n 把椅子,被滯留而離開用戶的數量減
少 n,理論分析成立。
2.椅子為零,離開用戶和理發師數量關系
理論分析:
假設理發師理發速度為 V,則 N 位理發師的理論上的理發速度為 NV。設 N 的 1 時候,滯留離開的用戶為
M;那么 N 大于 1 時候,被滯留的用戶大致為 M/N。但是,用戶達到時間間隔隨機(0~keepTime),好比,給了
理發師休息的機會,所以被滯留的用戶數量應該少于 M/N。變化速率近似于 f(x)=-lgx 函數。
數據證明:
結合圖形的擬合曲線以及各店的數據分析可知,該理論分析成立。
下面就上代碼了,一大波代碼正在靠近,請耐心。(get和Set方法等方法略,完整代碼可參考demo)
Demo地址
public class Barber {
private int id;//理發師Id
private Customer myCus;//理發師當前的顧客
private Lock lock;//理發師的鎖
private Condition condition;//理發師的條件變量
private boolean busy;//理發師忙碌狀態
public class Customer {
private int id;//用戶id
private int myBarber;//用戶的理發師
private Lock lock;//用戶鎖
private Condition condition;//用戶條件變量
}
public class Driver {
private static Shop shop;
private static int serviceTime;//服務時間
private static int nBarbers;//理發師數量
private static int nChairs;//椅子數量
private static int nCustomers;//用戶數量
public static void main(String[] args) throws InterruptedException {
//略輸入函數:接受用戶輸入:理發師數量,椅子數量,用戶數量,服務時間
shop=new Shop(nBarbers, nChairs);
//創建理發師線程
for(int i=0;i<nBarbers;i++) {
BarThread barThread=driver.new BarThread(i);
barThread.start();
}
//創建客戶線程
Vector<Thread> threads = new Vector<>();
for(int i=0;i<nCustomers;i++) {
CusThread cusThread=driver.new CusThread(i);
Random random=new Random();
Thread.sleep(random.nextInt(10));
threads.add(cusThread);
cusThread.start();
}
// 保證 shop.getDropsoff()在所有線程結束的時候調用
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("沒有理發離開的用戶數量為:"+shop.getDropsoff());
}
//理發師線程
private class BarThread extends Thread{
private int id;
public BarThread(int id) {
this.id=id;
}
public void run() {
while(true) {
try {
shop.helloCustomer(id);
sleep(serviceTime);//理發時間
shop.byeCustomer(id);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}
//客戶線程
private class CusThread extends Thread{
private int id;
private int barber=-1;
public CusThread(int id) {
this.id=id;
}
@Override
public void run() {
try {
if((barber=shop.visitShop(id))!=-1)
shop.leaveShop(id, barber);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Shop {
private static int nDropsoff;//未接受服務退出的人數
private int nBarbers;//理發師數量
private int nChairs;//椅子數量
private ArrayList<Barber> barList;//理發師隊列
private ArrayList<Customer> cusList;//客戶等待隊列
private Lock lock=new ReentrantLock();//互斥鎖
//用戶調用
public int visitShop(int id) throws InterruptedException {
lock.lock();//進入臨界區
int barId;
Barber barber;
Customer customer=new Customer(id);
//沒有空余椅子了,用戶離開了
if(cusList.size()>nChairs) {
System.out.println("顧客\t"+id+"\t離開了理發店因為沒有空位置了");
nDropsoff++;
lock.unlock();
return -1;
}
//沒有空閑理發師的時候
if(getSleepBarber()==-1) {
cusList.add(customer);//坐到椅子上
System.out.println("客戶\t"+id+"\t就座,"+"\t就坐的位置是 "+cusList.size());
lock.unlock();//離開臨界區
customer.getLock().lock();
customer.getCondition().await();//阻塞當前線程,用戶睡覺
customer.getLock().unlock();
//被理發師激活
lock.lock();//再次進入臨界區
barId=customer.getBar();//查詢自己的理發師
barber=barList.get(barId);
System.out.println("顧客 \t"+id+"\t走到理發師\t\t"+barId);
}else {
//有空閑的理發師
barId=getSleepBarber();//找到正在睡覺的理發師
customer.setBarber(barId);
barber=barList.get(barId);
barber.setCustomer(customer);//告訴理發師自己ID
barber.setBusy(true);//設置理發師為忙碌
System.out.println("顧客 \t"+id+"\t叫醒理發師\t\t"+barId);
}
lock.unlock();
barber.getLock().lock();
barber.getCondition().signalAll();//讓理發師開始理發理發師
barber.getLock().unlock();
return barId;
}
//用戶調用
public void leaveShop(int cusId,int barId) throws InterruptedException {
lock.lock();
Barber barber=barList.get(barId);
Customer customer=barber.getCustomer();
System.out.println("顧客\t"+cusId+"\t等待理發師\t\t"+barId+"\t完成理發");
//等待理發師理通知發結束
lock.unlock();
customer.getLock().lock();
customer.getCondition().await();
customer.getLock().unlock();
//顧客得知理發完成
lock.lock();
System.out.println("客戶\t"+cusId+"\t回答“好的”然后離開");
barber.getLock().lock();
barber.getCondition().signalAll();//離開
barber.getLock().unlock();
lock.unlock();
}
public void helloCustomer(int id) throws InterruptedException {
lock.lock();
Barber barber=barList.get(id);
Customer customer;
barber.getLock().lock();
//店里面沒有顧客
if(cusList.size()==0) {
System.out.println("理發師\t"+id+"\t去睡覺了因為沒有客戶");
barber.setBusy(false);
//等待顧客叫醒自己
lock.unlock();
barber.getLock().lock();
barber.getCondition().await();
barber.getLock().unlock();
//顧客叫醒自己
lock.lock();
customer=barber.getCustomer();//查詢顧客ID
}else {
//理發師叫醒顧客
customer=cusList.get(0);
cusList.remove(0);
customer.setBarber(id);//告訴用戶自己的位置
barber.setCustomer(customer);
//叫醒顧客
lock.unlock();//釋放鎖
customer.getLock().lock();;
customer.getCondition().signalAll();//激活椅子上的客戶
customer.getLock().unlock();
//等待顧客走過來
barber.getLock().lock();
barber.getCondition().await();
barber.getLock().unlock();
//顧客就座,開始理發
lock.lock();
}
System.out.println("理發師\t"+id+"\t正在服務客戶 \t"+customer.getId());
lock.unlock();
}
public void byeCustomer(int id) throws InterruptedException {
lock.lock();
Barber barber=barList.get(id);
Customer customer=barber.getCustomer();
System.out.println("理發師\t"+id+"\t告訴用戶 \t\t"+customer.getId()+"\t發理好了");
//通知顧客理發完成
lock.unlock();
customer.getLock().lock();
customer.getCondition().signalAll();//通知客戶理發完了
customer.getLock().unlock();
//等待顧客離開
barber.getLock().lock();
barber.getCondition().await();
barber.getLock().unlock();
lock.lock();
//顧客離開呼叫下一個顧客
System.out.println("理發師\t"+id+"\t理發完成,呼叫下一個用戶");
lock.unlock();
}
public void addDropsoff() {
nDropsoff++;
}
public int getDropsoff() {
return nDropsoff;
}
//查詢睡覺的理發師
public int getSleepBarber() {
lock.lock();
for(Barber b:barList) {
if(b.getBusy()==false) {
lock.unlock();
return b.getId();
}
}
lock.unlock();
return -1;
}
public Shop(int b,int c) {
nBarbers=b;
nChairs=c;
barList=new ArrayList<>();
for(int i=0;i<nBarbers;i++) {
barList.add(new Barber(i));
}
cusList=new ArrayList<>();
}
}