理發(fā)師問題加強版-多個理發(fā)師問題

寫在前面:

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


理發(fā)師問題描述:

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

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

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


加強版的問題描述:

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

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

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


問題流程分析:

讓我們先來看看一個理發(fā)師的場景再現(xiàn):
理發(fā)師問題流程圖.png
  1. 陽光明媚的早上,商店開門。店里面空空如也,理發(fā)師伸了個懶腰,睡回籠覺去了。
  2. 一位顧客來了,發(fā)現(xiàn)理發(fā)師都在睡覺,走到理發(fā)師面前,拍醒了理發(fā)師。
  3. 理發(fā)師醒了之后,十分抱歉,趕快給顧客理發(fā)。
  4. 理發(fā)完成,理發(fā)師告訴顧客:發(fā)理好了。
  5. 客戶答到:好的!轉身離開理發(fā)店。
  6. 理發(fā)師呼叫一下一個顧客
    • 若發(fā)現(xiàn)理發(fā)店恢復了空空如也的狀態(tài),就繼續(xù)去睡覺了
    • 若在還有顧客在椅子上等待,理發(fā)師就去喚醒椅子上睡覺的顧客。
      + 顧客隨理發(fā)師坐到理發(fā)椅上,等待理發(fā)師理發(fā)完成
      + 重復步驟4
      ....
當有多個理發(fā)師的時候會怎么樣呢。言語有點難以描述了,但可以看作多個單理發(fā)師的理發(fā)師店共享等待椅子隊列。每個理發(fā)師,訪問同一個的等待椅子隊列,但是,理發(fā)的時候互不影響。

技術需求

在Java中對于多線程同步的支持有很多方案。除了簡單的鎖對象(Class Lock),和條件對象(Class Condition)搭配使用之外,還有Synchronization關鍵字用來保護一個代碼片段,避免多個線程同時修改臨界區(qū)內容,也可以使用阻塞隊列等。我感覺鎖和條件對象比較適合這一題的解答。
鎖和條件對象的的使用:

 private Lock lock=new ReentrantLock();
  lock.lick();//獲取這個鎖,如果這個鎖被另外一個線程擁有則阻塞
  lock.unlock();//釋放鎖

  private Condition condition = lock.newCondition();
  condition.await();//阻塞當前線程
  condition.signalAll();//釋放擁有因為condition.await()的線程,將其放到等待隊列。該線程釋放鎖的時候執(zhí)行。
  condition.siginal();//在阻塞隊列中隨機釋放一個線程,將其放到等待隊列。該線程釋放鎖的時候執(zhí)行。

那么問題來了,我們需要哪些鎖呢?我們再看一個理發(fā)師的情況:

  • 理發(fā)師在沒有顧客的時候,調用自己的Condition.await()。
  • 用戶來的時候調用Barber.Condition.singalAll();并調用 自己的Condition.await()即可;
  • 理發(fā)師線程釋放之后進一步向前推進,直達下一次和客戶溝通的時候,掛起自己,喚醒客戶線程。
  • 重復上述就可完成理發(fā)師線程和用戶線程的溝通了。

一個理發(fā)師鎖,一個用戶鎖,一個互斥鎖就行了。

那么多個理發(fā)師的時候,每個理發(fā)師都有自己的用戶,理發(fā)師和用戶之間的信息交換是1對1的,那么也就是說每個理發(fā)師都有自己的鎖和條件對象,以供顧客調用。與此同時,每個顧客應該也有自己的鎖和對象讓理發(fā)師調用。畢竟理發(fā)師們只不過是共享了用戶隊列。


討論題:

1.理發(fā)師數(shù)量為 1 下,離開用戶和椅子數(shù)量關系

理發(fā)師數(shù)量為1的時候.png

理論分析:

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

實驗數(shù)據(jù)證明:

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


2.椅子為零,離開用戶和理發(fā)師數(shù)量關系

椅子數(shù)量為0時.png

理論分析:

假設理發(fā)師理發(fā)速度為 V,則 N 位理發(fā)師的理論上的理發(fā)速度為 NV。設 N 的 1 時候,滯留離開的用戶為
M;那么 N 大于 1 時候,被滯留的用戶大致為 M/N。但是,用戶達到時間間隔隨機(0~keepTime),好比,給了
理發(fā)師休息的機會,所以被滯留的用戶數(shù)量應該少于 M/N。變化速率近似于 f(x)=-lgx 函數(shù)。

數(shù)據(jù)證明:

結合圖形的擬合曲線以及各店的數(shù)據(jù)分析可知,該理論分析成立。


下面就上代碼了,一大波代碼正在靠近,請耐心。(get和Set方法等方法略,完整代碼可參考demo)

Demo地址

public class Barber {
    private int id;//理發(fā)師Id
    private Customer myCus;//理發(fā)師當前的顧客
    private Lock lock;//理發(fā)師的鎖
    private Condition condition;//理發(fā)師的條件變量
    private boolean busy;//理發(fā)師忙碌狀態(tài)
public class Customer {
    private int id;//用戶id
    private int myBarber;//用戶的理發(fā)師
    private Lock lock;//用戶鎖
    private Condition condition;//用戶條件變量
}
public class Driver {
    private static Shop shop;
    private static int serviceTime;//服務時間
    private static int nBarbers;//理發(fā)師數(shù)量
    private static int nChairs;//椅子數(shù)量
    private static int nCustomers;//用戶數(shù)量
    
    public static void main(String[] args) throws InterruptedException {
        //略輸入函數(shù):接受用戶輸入:理發(fā)師數(shù)量,椅子數(shù)量,用戶數(shù)量,服務時間
        shop=new Shop(nBarbers, nChairs);
        //創(chuàng)建理發(fā)師線程
        for(int i=0;i<nBarbers;i++) {
            BarThread barThread=driver.new BarThread(i);
            barThread.start();
        }
        //創(chuàng)建客戶線程
        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("沒有理發(fā)離開的用戶數(shù)量為:"+shop.getDropsoff());
    }
    //理發(fā)師線程
    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);//理發(fā)時間
                    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;//未接受服務退出的人數(shù)
    private int nBarbers;//理發(fā)師數(shù)量
    private int nChairs;//椅子數(shù)量
    private ArrayList<Barber> barList;//理發(fā)師隊列
    private ArrayList<Customer> cusList;//客戶等待隊列
    
    private Lock lock=new ReentrantLock();//互斥鎖
    
    //用戶調用
    public int visitShop(int  id) throws InterruptedException {
        lock.lock();//進入臨界區(qū)
        int barId;
        Barber barber;
        Customer customer=new Customer(id);
        //沒有空余椅子了,用戶離開了
        if(cusList.size()>nChairs) {
            System.out.println("顧客\t"+id+"\t離開了理發(fā)店因為沒有空位置了");
            nDropsoff++;
            lock.unlock();
            return -1;
        }
        //沒有空閑理發(fā)師的時候
        if(getSleepBarber()==-1) {
            cusList.add(customer);//坐到椅子上
            System.out.println("客戶\t"+id+"\t就座,"+"\t就坐的位置是 "+cusList.size());
            
            lock.unlock();//離開臨界區(qū)
            customer.getLock().lock();
            customer.getCondition().await();//阻塞當前線程,用戶睡覺
            customer.getLock().unlock();
            
            //被理發(fā)師激活
            lock.lock();//再次進入臨界區(qū)
            barId=customer.getBar();//查詢自己的理發(fā)師
            barber=barList.get(barId);
            System.out.println("顧客 \t"+id+"\t走到理發(fā)師\t\t"+barId);
        }else {
            //有空閑的理發(fā)師
            barId=getSleepBarber();//找到正在睡覺的理發(fā)師
            customer.setBarber(barId);
            barber=barList.get(barId);
            barber.setCustomer(customer);//告訴理發(fā)師自己ID
            barber.setBusy(true);//設置理發(fā)師為忙碌
            System.out.println("顧客 \t"+id+"\t叫醒理發(fā)師\t\t"+barId);
        }
        
        lock.unlock();
        barber.getLock().lock();
        barber.getCondition().signalAll();//讓理發(fā)師開始理發(fā)理發(fā)師
        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等待理發(fā)師\t\t"+barId+"\t完成理發(fā)");
        
        //等待理發(fā)師理通知發(fā)結束
        lock.unlock();
        customer.getLock().lock();
        customer.getCondition().await();
        customer.getLock().unlock();
        
        //顧客得知理發(fā)完成
        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("理發(fā)師\t"+id+"\t去睡覺了因為沒有客戶");
            barber.setBusy(false);
            //等待顧客叫醒自己
            lock.unlock();
            barber.getLock().lock();
            barber.getCondition().await();
            barber.getLock().unlock();
            
            //顧客叫醒自己
            lock.lock();
            customer=barber.getCustomer();//查詢顧客ID
        }else {
            //理發(fā)師叫醒顧客
            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();
             //顧客就座,開始理發(fā)
            lock.lock();
        }
        System.out.println("理發(fā)師\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("理發(fā)師\t"+id+"\t告訴用戶 \t\t"+customer.getId()+"\t發(fā)理好了");
        //通知顧客理發(fā)完成
        lock.unlock();
        customer.getLock().lock();
        customer.getCondition().signalAll();//通知客戶理發(fā)完了
        customer.getLock().unlock();
        //等待顧客離開
        barber.getLock().lock();
        barber.getCondition().await();
        barber.getLock().unlock();
        lock.lock();
        //顧客離開呼叫下一個顧客
        System.out.println("理發(fā)師\t"+id+"\t理發(fā)完成,呼叫下一個用戶");
        lock.unlock();
    }
    public void addDropsoff() {
        nDropsoff++;
    }
    public int getDropsoff() {
        return nDropsoff; 
    }
    //查詢睡覺的理發(fā)師
    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<>();
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

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

友情鏈接更多精彩內容