生產者/消費者模式(阻塞隊列)

生產消費者模式 

貌似也是阻塞的問題 

花了一些時間終於弄明白這個東東,以前還以爲是不復雜的一個東西的,以前一直以爲和觀察者模式差不多(其實也是差不多的,呵呵),生產消費者模式應該是可以通過觀察者模式來實現的,對於在什麼環境下使用現在想的還不是特別清楚,主要是在實際中還沒使用過這個。 

需要使用到同步,以及線程,屬於多併發行列,和觀察者模式的差異也就在於此吧,所以實現起來也主要在這裏的差異。

在實際的軟件開發過程中,經常會碰到如下場景:某個模塊負責產生數據,這些數據由另一個模塊來負責處理(此處的模塊是廣義的,可以是類、函數、線程、進程等)。產生數據的模塊,就形象地稱爲生產者;而處理數據的模塊,就稱爲消費者。 
  
單單抽象出生產者和消費者,還夠不上是生產者/消費者模式。該模式還需要有一個緩衝區處於生產者和消費者之間,作爲一箇中介。生產者把數據放入緩衝區,而消費者從緩衝區取出數據 


◇解耦 
  假設生產者和消費者分別是兩個類。如果讓生產者直接調用消費者的某個方法,那麼生產者對於消費者就會產生依賴(也就是耦合)。將來如果消費者的代碼發生變化,可能會影響到生產者。而如果兩者都依賴於某個緩衝區,兩者之間不直接依賴,耦合也就相應降低了。 
   
◇支持併發(concurrency) 
  生產者直接調用消費者的某個方法,還有另一個弊端。由於函數調用是同步的(或者叫阻塞的),在消費者的方法沒有返回之前,生產者只好一直等在那邊。萬一消費者處理數據很慢,生產者就會白白糟蹋大好時光。 
  使用了生產者/消費者模式之後,生產者和消費者可以是兩個獨立的併發主體(常見併發類型有進程和線程兩種,後面的帖子會講兩種併發類型下的應用)。生產者把製造出來的數據往緩衝區一丟,就可以再去生產下一個數據。基本上不用依賴消費者的處理速度。其實當初這個模式,主要就是用來處理併發問題的。 
   
◇支持忙閒不均 
  緩衝區還有另一個好處。如果製造數據的速度時快時慢,緩衝區的好處就體現出來了。當數據製造快的時候,消費者來不及處理,未處理的數據可以暫時存在緩衝區中。等生產者的製造速度慢下來,消費者再慢慢處理掉。 
   

用了兩種方式實現了一下這個模式,主要參考了網上的一些例子才弄明白,這裏對隊列的實現有很多種方法,需要和具體的應用相結合吧,隊列緩衝區很簡單,現在已有大量的實現,缺點是在性能上面(內存分配的開銷和同步/互斥的開銷),下面的實現都是這種方式;環形緩衝區(減少了內存分配的開銷),雙緩衝區(減少了同步/互斥的開銷)。 
第一個例子是使用的信號量的東東,沒有執行具體的東西,只是實現了這個例子,要做複雜的業務邏輯的話需要自己在某些方法內去具體實現 
代碼如下: 

消費者:

    public class TestConsumer implements Runnable {  
          
        TestQueue obj;  
          
        public TestConsumer(TestQueue tq){  
            this.obj=tq;  
        }  
      
        public void run() {               
            try {  
                for(int i=0;i<10;i++){  
                    obj.consumer();  
                }             
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
        }  
    }  

生產者:

    public class TestProduct implements Runnable {  
          
        TestQueue obj;  
          
        public TestProduct(TestQueue tq){  
            this.obj=tq;  
        }  
          
        public void run() {  
            for(int i=0;i<10;i++){  
                try {  
                    obj.product("test"+i);  
                } catch (Exception e) {               
                    e.printStackTrace();  
                }  
            }  
        }  
      
    }  

隊列(使用了信號量,採用synchronized進行同步,採用lock進行同步會出錯,或許是還不知道實現的方法):


    public static Object signal=new Object();  
        boolean bFull=false;   
        private List thingsList=new ArrayList();   
        private final ReentrantLock lock = new ReentrantLock(true);  
        BlockingQueue q = new ArrayBlockingQueue(10);  
        /** 
         * 生產 
         * @param thing 
         * @throws Exception 
         */  
        public void product(String thing) throws Exception{   
            synchronized(signal){  
                if(!bFull){  
                    bFull=true;  
                      //產生一些東西,放到 thingsList 共享資源中  
                    System.out.println("product");  
                    System.out.println("倉庫已滿,正等待消費...");   
                    thingsList.add(thing);   
                    signal.notify(); //然後通知消費者  
                }            
                signal.wait(); // 然後自己進入signal待召隊列  
                  
            }  
              
        }  
          
        /** 
         * 消費 
         * @return 
         * @throws Exception 
         */  
        public String consumer()throws Exception{             
            synchronized(signal){  
                if(!bFull)  {    
                         signal.wait(); // 進入signal待召隊列,等待生產者的通知  
                }  
                bFull=false;   
                // 讀取buf 共享資源裏面的東西  
                System.out.println("consume");  
                System.out.println("倉庫已空,正等待生產...");   
                signal.notify(); // 然後通知生產者  
            }  
            String result="";  
            if(thingsList.size()>0){  
                result=thingsList.get(thingsList.size()-1).toString();  
                thingsList.remove(thingsList.size()-1);  
            }  
            return result;  
        }  
測試代碼:

    public class TestMain {  
        public static void main(String[] args) throws Exception{  
            TestQueue tq=new TestQueue();  
            TestProduct tp=new TestProduct(tq);  
            TestConsumer tc=new TestConsumer(tq);  
            Thread t1=new Thread(tp);  
            Thread t2=new Thread(tc);  
            t1.start();  
            t2.start();  
        }  
    }  

運行結果:

  1. product  
  2. 倉庫已滿,正等待消費...  
  3. consume  
  4. 倉庫已空,正等待生產...  
  5. product  
  6. 倉庫已滿,正等待消費...  
  7. consume  
  8. 倉庫已空,正等待生產...  
  9. product  
  10. 倉庫已滿,正等待消費...  
  11. consume  
  12. 倉庫已空,正等待生產...  
  13. product  
  14. 倉庫已滿,正等待消費...  
  15. consume  
  16. 倉庫已空,正等待生產...  
  17. product  
  18. 倉庫已滿,正等待消費...  
  19. consume  
  20. 倉庫已空,正等待生產...  
  21. product  
  22. 倉庫已滿,正等待消費...  
  23. consume  
  24. 倉庫已空,正等待生產...  
  25. product  
  26. 倉庫已滿,正等待消費...  
  27. consume  
  28. 倉庫已空,正等待生產...  
  29. product  
  30. 倉庫已滿,正等待消費...  
  31. consume  
  32. 倉庫已空,正等待生產...  
  33. product  
  34. 倉庫已滿,正等待消費...  
  35. consume  
  36. 倉庫已空,正等待生產...  
  37. product  
  38. 倉庫已滿,正等待消費...  
  39. consume  
  40. 倉庫已空,正等待生產... 


第二種發放使用java.util.concurrent.BlockingQueue類來重寫的隊列那個類,使用這個方法比較簡單,並且性能上也沒有什麼問題。
這是jdk裏面的例子


class Producer implements Runnable {  
 *   private final BlockingQueue queue;  
 *   Producer(BlockingQueue q) { queue = q; }  
 *   public void run() {  
 *     try {  
 *       while(true) { queue.put(produce()); }  
 *     } catch (InterruptedException ex) { ... handle ...}  
 *   }  
 *   Object produce() { ... }  
 * }  
 *  
 * class Consumer implements Runnable {  
 *   private

jdk1.5以上的一個實現,使用了Lock以及條件變量等東西
    class BoundedBuffer {  
      final Lock lock = new ReentrantLock();  
      final Condition notFull  = lock.newCondition();   
      final Condition notEmpty = lock.newCondition();   
      
      final Object[] items = new Object[100];  
      int putptr, takeptr, count;  
      
      public void put(Object x) throws InterruptedException {  
        lock.lock();  
        try {  
          while (count == items.length)   
            notFull.await();  
          items[putptr] = x;   
          if (++putptr == items.length) putptr = 0;  
          ++count;  
          notEmpty.signal();  
        } finally {  
          lock.unlock();  
        }  
      }  
      
      public Object take() throws InterruptedException {  
        lock.lock();  
        try {  
          while (count == 0)   
            notEmpty.await();  
          Object x = items[takeptr];   
          if (++takeptr == items.length) takeptr = 0;  
          --count;  
          notFull.signal();  
          return x;  
        } finally {  
          lock.unlock();  
        }  
      }   
    }  

轉載自http://canofy.iteye.com/blog/411408

發佈了79 篇原創文章 · 獲贊 143 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章