一生產者多消費者 — 操作棧問題
本文是想通過棧的方式來進行線程間通訊。
1. 異常情況
看如下代碼:
package entity;
import java.util.ArrayList;
import java.util.List;
public class MyStack {
private List list = new ArrayList();
synchronized public void push() {
try {
if (list.size() == 1) {
this.wait();
}
list.add("anyString=" + Math.random());
this.notify();
System.out.println("push=" + list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public String pop() {
String returnValue = "";
try {
if (list.size() == 0) {
this.wait();
}
returnValue = "" + list.get(0);
System.out.println("pop操作中的:"
+ Thread.currentThread().getName() + " 線程呈wait狀態");
list.remove(0);
this.notify();
System.out.println("pop=" + list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
return returnValue;
}
}
package extthread;
import entity.MyStack;
public class Consumer_Thread extends Thread {
private MyStack myStack;
public Consumer_Thread(MyStack r) {
this.myStack = r;
}
@Override
public void run() {
while (true) {
myStack.pop();
}
}
}
package extthread;
import entity.MyStack;
public class Produce_Thread extends Thread {
private MyStack myStack;
public Produce_Thread(MyStack p) {
this.myStack = p;
}
@Override
public void run() {
while (true) {
myStack.push();
}
}
}
主函數如下:
package test.run;
import entity.MyStack;
import extthread.Consumer_Thread;
import extthread.Produce_Thread;
public class Run {
public static void main(String[] args) throws Exception{
MyStack myStack = new MyStack();
Produce_Thread pThread = new Produce_Thread(myStack);
pThread.start();
Consumer_Thread cThread1 = new Consumer_Thread(myStack);
Consumer_Thread cThread2 = new Consumer_Thread(myStack);
cThread1.start();
cThread2.start();
}
}
運行如上代碼,會出現如下異常:
push=1
pop操作中的:Thread-2 線程呈wait狀態
pop=0
push=1
pop操作中的:Thread-2 線程呈wait狀態
pop=0
Exception in thread "Thread-1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.get(ArrayList.java:429)
at entity.MyStack.pop(MyStack.java:28)
at extthread.Consumer_Thread.run(Consumer_Thread.java:16)
究其原因,是由於在Mystack類中,對戰的棧的操作時用了if來判斷:
synchronized public String pop() {
String returnValue = "";
try {
if (list.size() == 0) {
this.wait();
}
returnValue = "" + list.get(0);
System.out.println("pop操作中的:"
+ Thread.currentThread().getName() + " 線程呈wait狀態");
list.remove(0);
this.notify();
System.out.println("pop=" + list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
return returnValue;
}
我們知道this.norify()
操作會隨機喚醒一個在當前鎖對象上正在等待的線程,假如當前list.size==0爲true,這時候有一個consumer線程consumer1調用了this.wait()
,此時consumer1線程釋放鎖,當前線程的執行流程暫停在了this.wait()
這裏。 produce線程做了一次push操作,然後喚醒了consumer2線程,consumer2執行了remove操作之後,調用了this.norify()
,此時把consumer1線程喚醒,consumer1接着上次的位置繼續執行,執行list.remove()
操作,由於此時list裏面爲空,所以拋IndexOutOfBoundsException異常。
怎麼解決這個問題呢?想要解決這個問題,只需要把if判斷,改成while循環即可。
package entity;
import java.util.ArrayList;
import java.util.List;
public class MyStack {
private List list = new ArrayList();
synchronized public void push() {
try {
/**修改這裏爲while循環*/
while (list.size() == 1) {
this.wait();
}
list.add("anyString=" + Math.random());
this.notify();
System.out.println("push=" + list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public String pop() {
String returnValue = "";
try {
/**修改這裏爲while循環*/
while (list.size() == 0) {
this.wait();
}
returnValue = "" + list.get(0);
System.out.println("pop操作中的:"
+ Thread.currentThread().getName() + " 線程呈wait狀態");
list.remove(0);
this.notify();
System.out.println("pop=" + list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
return returnValue;
}
}
但是這種情況下。異常問題解決了,又會迎來假死的情況,下面來探討假死情況
2.假死情況
假死產生的原因就是所以的線程都執行了this.wait()
,這種情況也很好理解。例如,程序運行,consumer1線程剛調用了this.wait()
方法,produce線程往棧裏添加了一個元素,這時候produce執行notify操作,喚醒了consumer2,consumer2執行了remove之後,調用notify喚醒的是consumer1,而consumer1接着this.wait()
的代碼繼續執行,又進入了while循環,再次調用了this.wait()
,而consumer2也由於外層while循環,重新調用pop操作,也進入了wait狀態。此時所有的線程都在wait,整個程序進入了假死。
解決這個問題的方法就是把this.notify()
改成this.nofify()
。 程序運行便不會在出現假死,並且可以正常運行下去:
package entity;
import java.util.ArrayList;
import java.util.List;
public class MyStack {
private List list = new ArrayList();
synchronized public void push() {
try {
/**修改這裏爲while循環*/
while (list.size() == 1) {
this.wait();
}
list.add("anyString=" + Math.random());
this.notify();
System.out.println("push=" + list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public String pop() {
String returnValue = "";
try {
/**修改這裏爲while循環*/
while (list.size() == 0) {
this.wait();
}
returnValue = "" + list.get(0);
System.out.println("pop操作中的:"
+ Thread.currentThread().getName() + " 線程呈wait狀態");
list.remove(0);
this.notifyAll();
System.out.println("pop=" + list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
return returnValue;
}
}