Java多線程(2)生產者消費者問題(一)

一、問題描述

在線程世界裏,生產者就是生產數據的線程,消費者就是消費數據的線程。在多線程開發當中,如果生產者處理速度很快,而消費者處理速度很慢,那麼生產者就必須等待消費者處理完,才能繼續生產數據。同樣的道理,如果消費者的處理能力大於生產者,那麼消費者就必須等待生產者。如何用代碼描述此類問題。

二、一個消費者線程、一個生產者線程

有幾點需要強調:
1.main方法中的資源res的線程t1、t2共享的。所以當操作該資源時需要同步
2.同步函數的鎖即爲this,所以res即爲線程t1、t2的鎖
3.因爲只有兩個線程所以能夠準確的喚醒對象線程(即消費者線程喚醒生產者進程、生產者進程喚醒消費者進程),
那麼問題就來了,如果是多消費者進程,多生產進程怎麼辦?

package com.thread.pcprob;

/**
 * 共享數據
 * @author dqf
 *
 */
public class Resource {
       //產品編號
       private Integer proNo = 0;
       //判斷是消費數據,還是生產數據
       private boolean flag = false;

       //生產數據
       public synchronized void produce(){
             if(flag){//如果flag=true,則等待消費;
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("生產-->" + (++proNo));
             flag = true;
             this.notify();//喚醒等待進程
       }

       //消費數據
       public synchronized void consume(){
             if(!flag){//如果flag=false,則等待生產
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("消費-------->" + proNo);
             flag = false;
             this.notify();//喚醒等待進程
       }
}
package com.thread.pcprob;

/**
 * 生產者
 * @author dqf
 *
 */
public class Producer implements Runnable{

       private Resource res;

       public Producer(Resource res) {
             this.res = res;
       }

       @Override
       public void run() {
             while(true){
                    res.produce();
             }
       }
}
package com.thread.pcprob;

/**
 * 消費者
 * @author dqf
 *
 */
public class Consumer implements Runnable{

       private Resource res;

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

       @Override
       public void run() {
             while(true){
                    res.consume();
             }
       }
}
package com.thread.pcprob;

public class App {
       public static void main(String[] args) {
             Resource res = new Resource();

             Producer p = new Producer(res);
             Consumer c = new Consumer(res);

             Thread t1 = new Thread(p);
             Thread t2 = new Thread(c);

             t1.start();
             t2.start();

       }
}

部分輸出:

消費-------->159091
生產-->159092
消費-------->159092
生產-->159093
消費-------->159093
生產-->159094
消費-------->159094
生產-->159095
消費-------->159095
生產-->159096
消費-------->159096
生產-->159097
消費-------->159097
生產-->159098
消費-------->159098
生產-->159099
消費-------->159099

三、多生產者進程、多消費者進程

我們把main方法修改如下:

public class App {
       public static void main(String[] args) {
             Resource res = new Resource();

             Producer p = new Producer(res);
             Consumer c = new Consumer(res);

             Thread t1 = new Thread(p);
             Thread t2 = new Thread(p);

             Thread t3 = new Thread(c);
             Thread t4 = new Thread(c);

             t1.start();
             t2.start();
             t3.start();
             t4.start();

       }
}

部分輸出:

消費-------->123384
消費-------->123384
生產-->123385
消費-------->123385
消費-------->123385
生產-->123386
消費-------->123386
消費-------->123386
生產-->123387
消費-------->123387
消費-------->123387
生產-->123388
消費-------->123388

我們發現會出現兩種錯誤的情況
1.生產一個,消費多次
2.生產多次,消費一次

下面來分析產生兩種問題的原因:
這裏寫圖片描述

假設一種狀態:
flag = false,t1獲取到鎖,t2、t3、t4等待鎖釋放。
因爲flag爲false,t1阻塞自動釋放鎖,t2獲取鎖,同樣因爲flag爲false,t2阻塞自動釋放鎖。
若t3獲取鎖,執行到this.notify(),則會喚醒一個阻塞狀態的線程,如果喚醒了t1線程,t1線程結束後又喚醒了t2。
則,就會出現多次生產,一次消費的情況。

其實造成這種情況的主要原因是:不能指定具體喚醒哪一個線程、或者說不能指定喚醒哪一類線程。導致,生產進程結束後,又喚醒了生產進程;或者消費進程結束後,又喚醒消費進程。


如果將Resource改爲(if改爲while),那麼在線程被喚醒後,都會再次判斷一次flag。

package com.thread.pcprob;

/**
 * 共享數據
 * @author dqf
 *
 */
public class Resource {
       //產品編號
       private Integer proNo = 0;
       //判斷是消費數據,還是生產數據
       private boolean flag = false;

       //生產數據
       public synchronized void produce(){
             while(flag){//如果flag=true,則等待消費;
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("生產-->" + (++proNo));
             flag = true;
             this.notify();//喚醒等待進程
       }

       //消費數據
       public synchronized void consume(){
             while(!flag){//如果flag=false,則等待生產
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("消費-------->" + proNo);
             flag = false;
             this.notify();//喚醒等待進程
       }
}

但是這種情況又會導致,t1、t2、t3、t4全部處於阻塞狀態。

最終寫法(notify改爲notifyAll):

package com.thread.pcprob;

/**
 * 共享數據
 * @author dqf
 *
 */
public class Resource {
       //產品編號
       private Integer proNo = 0;
       //判斷是消費數據,還是生產數據
       private boolean flag = false;

       //生產數據
       public synchronized void produce(){
             while(flag){//如果flag=true,則等待消費;
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("生產-->" + (++proNo));
             flag = true;
             this.notifyAll();//喚醒等待進程
       }

       //消費數據
       public synchronized void consume(){
             while(!flag){//如果flag=false,則等待生產
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("消費-------->" + proNo);
             flag = false;
             this.notifyAll();//喚醒等待進程
       }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章