Java 生產--消費者模式初探

生產者消費模式 : 顧名思義有生產者、消費者、資源 三個對象。生產者生產資源,消費者消費資源。

類似於工廠流水線,多條生產線(生產者),多條包裝線(消費線)。

前面有介紹線程的 等待喚醒機制 我們可以回顧下,一條輸入線程(生產者),一條輸出線程(消費者),不同線程對同一資源進行操作,只不過操作的工作不一致。

回顧下等待喚醒機制的代碼(2條線程)

//資源文件
class Resources
{
   private String name;
   private int count = 1;          
   private boolean flag = false;  //標誌位

   //生產函數
   public void  synchronized setName(String name)
   {
     if(flag)
      { try{this.wait();} try(Exception e){} }
      this.name = name + count++;
      System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
      falg = true;
      this.notify();   
   }

   //消費函數
   public void synchronized out()
   {
     if(!flag)
     { try{this.wait();} try(Exception e){} }
     System.out.println(Thread.currentThread().getName()+"  <----消費者--->  "+this.name); 
      flag = false;
      this.notify();
   }

}


// 生產者文件
class Producter implements Runnable
{
   private Resources  res;

   public Producter(Resources res)
   {
      this.res = res;
   }

  //實現run方法
  public void run()
  {
    res.setName("蘋果");
  }  
}


//消費者文件
class Consumer implements Runnable
{
   private Resources  res;

   public Consumer(Resources  res)
   {
     this.res = res;
   }

   //實現run方法
   public void run()
   {
      res.out();
   }
}

//main方法
public static void main(String [] args)
{
   //資源
   Resources  res =  new Resources();

  //線程對象
  Thread t1 = new Thread(new Producter(res));
  Thread t2 = new Thread(new Consumer(res));

   //開啓線程
   t1.start();
   t2.start();  
}

以上代碼運行是們問題的,正常的生產線程生產一個資源,消費線程消費一個資源,有序間隔輸出。但是當我們增加線程數繼續運行的時候,就發現打印的結果不對了。
在main方法修改如下

    public static void main(String[] args) 
    {

        //創建資源
        Resource res = new Resource();

        //生產者線程
        Thread t1 = new Thread(new Producter(res));
        Thread t2 = new Thread(new Consumer(res));


        //消費者線程
        Thread t3 = new Thread(new Producter(con));
        Thread t4 = new Thread(new Producter(con));


        //開啓線程
        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }

.
截取部分輸出結果不匹配截圖如下
.

這裏寫圖片描述

.
生產2個,只消費了其中1個。或者消費兩個,只生產一個。……爲什麼會出現如上問題呢?我們可以分析下問題
.

假設代碼執行流程如下(解釋下生產2次,消費一次)

生產線程 : t1、t2
消費線程 :t3、t4

(1) 假設生產者率先獲取cpu的執行權,生產者有2個。假設t1先獲取執行權,t1判斷標誌位flag = false,先生產一個(打印一次),後將flag = true,並notify()後。此時t1還持有執行權,繼續回來判斷flag = true, 符合條件,t1就直接wait了,放棄資格。此時wait的線程就直接進線程池(數據結構爲鏈表結構,先進先出)
(2) t2、t3、t4,假設生產者t2繼續獲取cpu執行權,判斷標誌位flag = true,符合條件,t2也wait了,放棄資格。(此時線程池裏有t1、t2)
(3) 只剩下t3、t4。 t3先獲取執行資格,判斷標誌位!falg = false,不符合條件,繼續往下執行,消費一個。並把標誌位設置成falg = false,並notofy()下。後,t3繼續享有執行權,回來繼續判斷標誌位 !flag = true 。符合條件,t3就直接wait了。失去執行資格,進入線程池。(此時線程池有t1、t2、t3)。
(4) 上一步驟 t3 執行完後,notify(),此時優先喚醒了生產線程t1。t1 線程在剛wait地方繼續往下執行(注意:t1不繼續判斷標誌位了),生產一個。並繼續notify(),此時喚醒的是t2線程,t2也不繼續判斷,繼續往下執行,又生產一個。t2喚醒的是消費者t3。t3 就消費一個。此時,就會產生生產兩個,只消費一個的現象。

其他情況自己類比解釋。

那問題來了? 爲什麼會造成如上問題呢 ?
根據上述執行步驟分析,我們可以知道,如果t3執行後,喚醒了t1時,t1執行完成後,t1又喚醒t2,如果在喚醒前我們讓線程 重新去判斷標誌位,那就不存在連續生產兩個的情況。 也就是說,把 if() ,修改成 while() 。

//資源類文件
class Resource
{
    private String name;

    private int count = 1;

    private boolean flag = false;

    // 生產函數
    public synchronized void setName(String name)
    {
        while(flag) {  //每次執行都判斷標記

            try {wait();}catch(Exception e) {}

        }

        this.name = name+"..."+count++;

        System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);

        flag = true;

        this.notify();


    }

    // 消費函數
    public synchronized void out()
    {
        while(!flag)   //每次執行都判斷標記
        {
            try {wait();}catch(Exception e) {}
        }

        System.out.println(Thread.currentThread().getName()+"  <----消費者--->  "+this.name);

        flag = false;

        this.notify();;
    }

}

.
.

運行下看下打印結果,你會發現,全部線程都wait了。
這裏寫圖片描述

怎麼解決呢?Java在Object類裏面還有個方法notifyAll()方法。喚醒全部。全喚醒後,他們都會循環去判斷標記,只要循環判斷標記,就知道什麼時候該執行,什麼時候該wait。

//資源類文件
class Resource
{
    private String name;

    private int count = 1;

    private boolean flag = false;

    // 生產函數
    public synchronized void setName(String name)
    {
        while(flag) {

            try {wait();}catch(Exception e) {}

        }

        this.name = name+"..."+count++;

        System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);

        flag = true;

        this.notifyAll();


    }

    // 消費函數
    public synchronized void out()
    {
        while(!flag)
        {
            try {wait();}catch(Exception e) {}
        }

        System.out.println(Thread.currentThread().getName()+"  <----消費者--->  "+this.name);

        flag = false;

        this.notifyAll();;
    }

}

.
.
運行結果如下(只截取部分截圖)
這裏寫圖片描述

總結 :當出現多個生產者和消費者必須
(1) while 循環判斷標記
(2) notifyAll () 既喚醒本方又喚醒對方

本文demo地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章