理發師問題加強版-多個理發師問題

寫在前面:

這是睡眠理發師問題加強版的Java解決方案參考,是一次操作系統實驗的分析報告。實驗問題完整描述可參考實驗完整描述以及要求文檔。實驗的完整代碼可參考Demo


理發師問題描述:

一個理發店由一個有n個椅子的等候室和一個有一個理發椅的理發室組成。

  1. 如果有沒有顧客來服務,理發師就去睡覺了。
  2. 如果顧客走進理發店和所有的椅子被占用了,然后顧客離開了商店。
  3. 如果理發師很忙,但是椅子是可用的,那么顧客坐在一張免費的椅子上。
  4. 如果理發師睡著了,顧客就會叫醒理發師。

這是課本上的理發師問題,對于這個問題的解答網上有很多解法,可參考:CSDN 進程(線程)間同步互斥問題
(三) 熟睡的理發師問題


加強版的問題描述:

一個理發店由一個有n個椅子的等候室和一個有m理發椅的理發室組成。

  1. 如果有沒有顧客可以服務,所有的理發師都去睡覺。
  2. 如果顧客走進理發店椅子被占用了,然后顧客離開了商店。
  3. 如果所有的理發師都很忙,但是椅子是可用的,然后顧客坐在一張免費的椅子上。
  4. 如果理發師睡著了,顧客就會醒過來的理發師。

實驗完整問題描述以及要求文檔鏈接


問題流程分析:

讓我們先來看看一個理發師的場景再現:
理發師問題流程圖.png
  1. 陽光明媚的早上,商店開門。店里面空空如也,理發師伸了個懶腰,睡回籠覺去了。
  2. 一位顧客來了,發現理發師都在睡覺,走到理發師面前,拍醒了理發師。
  3. 理發師醒了之后,十分抱歉,趕快給顧客理發。
  4. 理發完成,理發師告訴顧客:發理好了。
  5. 客戶答到:好的!轉身離開理發店。
  6. 理發師呼叫一下一個顧客
    • 若發現理發店恢復了空空如也的狀態,就繼續去睡覺了
    • 若在還有顧客在椅子上等待,理發師就去喚醒椅子上睡覺的顧客。
      + 顧客隨理發師坐到理發椅上,等待理發師理發完成
      + 重復步驟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的時候.png

理論分析:

理發師的數量為 1 的時候,每增加 n 把椅子,用戶等待數量緩沖區增加 n,即滯留用戶離開數量減少 n。

實驗數據證明:

結合圖標可知,該擬合曲線為的斜率近似于-1 的直線,即每增加 n 把椅子,被滯留而離開用戶的數量減
少 n,理論分析成立。


2.椅子為零,離開用戶和理發師數量關系

椅子數量為0時.png

理論分析:

假設理發師理發速度為 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<>();
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 在沒遇到對的人之前 本想之前的日子一定要好好來過 努力把自己變成一個好的模樣 成長這條路 會很疼 會孤獨 會失去也...
    子不言吾不語閱讀 431評論 3 14
  • 愚鈍與敏感, 散漫與克制。肥大自我與無欲無求,沉默失語與過分喧囂, 愛自由與規劃人生。然而哪一方面都沒做好,所以我...
    澤木榮志閱讀 204評論 4 0
  • 上次寫了一個控件約束報錯,因為上個例子只有一個控件,不具有代表性,所以,這次就找了個控件挺多的例子,這個例子是用第...
    5a3830ede979閱讀 276評論 0 0
  • 當考初級會計證遇上參加模擬企業沙盤的省賽是該如何選擇,時間正好沖突。誰有經驗,幫幫回答,急
    LPiggy閱讀 129評論 0 0