1. 程式人生 > >多執行緒之生產者/消費者模式(值操作)

多執行緒之生產者/消費者模式(值操作)

1、一個生產者,一個消費者(值操作)
本小節是典型的單個生產者,單個消費者,生產物件為值的Demo
如下,我們假設ValueObject .value為生產的值,當value的值為 “”(空串)時,我們認為生產出來的物件已被消費,當value的值不為空串時,我們認為生產出來的物件未被消費。

public class ValueObject {
    public static String value = "";
}

生產者

public class P {
    private String lock;
    public P(String lock) {
        super();
        this
.lock = lock; } public void setValue() { try { synchronized(lock) { while(!ValueObject.value.equals("")) { //當value的值不為 ""(空串)時,可以認為生產出來的東西未被消費,所以生產者進入等待狀態,直到消費者將其消費再繼續生產。 lock.wait(); } String value
= System.currentTimeMillis() + "" + System.nanoTime(); System.out.println("set的值是" + value); ValueObject.value = value; lock.notify(); } } catch (Exception e) { e.printStackTrace(); } } }

消費者

public class
C { private String lock; public C(String lock) { super(); this.lock = lock; } public void getValue() { try { synchronized (lock) { while(ValueObject.value.equals("")) { //當value的值、為 ""(空串)時,可以認為生產出來的東西已經被消費,所以消費者進入等待狀態,直到生產者生產完成後再進行消費。 lock.wait(); } System.out.println("get的值是" + ValueObject.value); ValueObject.value = ""; lock.notify(); } } catch (Exception e) { e.printStackTrace(); } } }

下面分別附上負責生產和消費兩條執行緒的程式碼

public class ThreadP extends Thread {
    private P p;
    public ThreadP(P p) {
        super();
        this.p = p;
    }
    public void run() {
        while(true) {
            p.setValue();
        }
    }
}
public class ThreadC extends Thread {
    private C c;
    public ThreadC(C c) {
        super();
        this.c = c;
    }
    public void run() {
        while(true) {
            c.getValue();
        }
    }
}

下面是主函式

public static void main(String[] args) {
    String lock = new String("");
    P p = new P(lock);
    C c = new C(lock);
    ThreadP threadP = new ThreadP(p);
    ThreadC threadC = new ThreadC(c);
    threadP.start();
    threadC.start();
}

執行結果如下:
這裡寫圖片描述

從執行結果我們看到,set和get交替出現,對應了一生產者和一消費者進行資料互動,程式正常執行。

但是,如果在上述模式的基礎上擴充套件成多生產多消費的模式,執行的過程中就極有可能出現假死的情況。

2、多個生產者,多個消費者(值操作)
我們將消費者P,生成者C的程式碼改成如下:

public class P {
    private String lock;
    public P(String lock) {
        super();
        this.lock = lock;
    }

    public void setValue() {
        try {
            synchronized(lock) {
                while(!ValueObject.value.equals("")) {
                    System.out.println(Thread.currentThread().getName()+ "進入等待");
                    lock.wait();
                }               
                String value = System.currentTimeMillis() + "" + System.nanoTime();
                System.out.println(Thread.currentThread().getName()+ "開啟生產");
                ValueObject.value = value;
                lock.notify();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class C {
    private String lock;
    public C(String lock) {
        super();
        this.lock = lock;
    }

    public void getValue() {
        try {
            synchronized (lock) {
                while(ValueObject.value.equals("")) {
                    System.out.println(Thread.currentThread().getName()+ "進入等待");
                    lock.wait();
                }
                System.out.println(Thread.currentThread().getName()+ "開啟消費");
                ValueObject.value = "";
                lock.notify();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

主函式下如下:

public static void main(String[] args) {
String lock = new String("");
    P p = new P(lock);
    C c = new C(lock);
    ThreadP[] threadP = new ThreadP[2];
    ThreadC[] threadC = new ThreadC[2];
    for(int i = 0; i < 2; i++) {
        threadP[i] = new ThreadP(p);
        threadP[i].setName("生產者" + (i + 1));
        threadC[i] = new ThreadC(c);
        threadC[i].setName("消費者" + (i + 1));
        threadP[i].start();
        threadC[i].start();
        }
    }

執行結果如下:
這裡寫圖片描述

如圖,執行後,程式進入了假死狀態,無法正常繼續運作。

我們來分析一下出現這種結果的原因:
將執行結果貼上到下面

1 生產者1開啟生產
2 生產者1進入等待
3 消費者2開啟消費
4 消費者2進入等待
5 消費者1進入等待
6 生產者2開啟生產
7 生產者2進入等待
8 消費者2開啟消費
9 消費者2進入等待
10 消費者1進入等待
11 生產者1開啟生產
12 生產者1進入等待
13 生產者2進入等待

1)程式開始,生產者1進行生產,生產完成後發出通知(但屬於通知過早,並無產生效果)。
2)生產者1生產完成後,再次搶到了lock鎖,檢測到產品未被消費,進入等待狀態。
3)消費者2被start()啟動,將產品進行消費併發出通知,喚醒第8行的消費者2。
4)消費後,消費者2再次搶到鎖,但發現產品未被生產,所以進入了等待。
5)消費者1被start()啟動,發現產品被消費,進入等待狀態。
6)生產者2被start()啟動,發生產品已被消費,所以進入生產狀態,生產完成後喚醒了第10行的消費者1。
7)同2),生產者2生產完成後,再次搶到了lock鎖,檢測到產品未被消費,進入等待狀態。
8)消費者2進入下一次while()迴圈,消費產品後喚醒第11行的生產者1。
9)消費者2再次搶到鎖,發現產品未被生產,進入等待。
10)消費者1進入下一次while()迴圈,發現產品被消費,進入等待狀態。
11)生產者1發現產品已被消耗,進入生產,生產完成後,喚醒第13行的生產者2
12)生產者1搶到鎖,發現產品未消費,進入等待狀態。
13)生產者2被喚醒,發現產品未消費,進入等待狀態。

由上述分析可得出結論,程式出現假死的原因是:生產者1生產完成後發出通知喚醒的是生產者2,而不是消費者。動作完成後通知的是同類,當連續喚醒同類時,就非常容易出現假死的情況。

解決方法:將notify()方法換成notifyAll()方法,將同類異類一同喚醒,這樣就不至於出現假死的情況了。





宣告:本文的例子全部來自於《Java多執行緒程式設計核心技術》