多執行緒之生產者/消費者模式(值操作)
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多執行緒程式設計核心技術》