對於java多線程的wait()方法,我們在jdk1.6的說明文檔里可以看到這樣一段話

從上面的截圖,我們可以看出,在使用wait方法時,需要使用while循環來判斷條件十分滿足,而不是if,那么我們思考以下,如果使用if會怎么樣?
為方便講解,我們來看一個被廣泛使用的生產消費的例子。代碼部分參考 郝斌java視頻教程 部分改編。
/*
生產和消費
*/
package multiThread;
class SynStack
{
private char[] data = new char[6];
private int cnt = 0; //表示數組有效元素的個數
public synchronized void push(char ch)
{
if (cnt >= data.length)
{
try
{
System.out.println("生產線程"+Thread.currentThread().getName()+"准備休眠");
this.wait();
System.out.println("生產線程"+Thread.currentThread().getName()+"休眠結束了");
}
catch (Exception e)
{
e.printStackTrace();
}
}
this.notify();
data[cnt] = ch;
++cnt;
System.out.printf("生產線程"+Thread.currentThread().getName()+"正在生產第%d個產品,該產品是: %c\n", cnt, ch);
}
public synchronized char pop()
{
char ch;
if (cnt <= 0)
{
try
{
System.out.println("消費線程"+Thread.currentThread().getName()+"准備休眠");
this.wait();
System.out.println("消費線程"+Thread.currentThread().getName()+"休眠結束了");
}
catch (Exception e)
{
e.printStackTrace();
}
}
this.notify();
ch = data[cnt-1];
System.out.printf("消費線程"+Thread.currentThread().getName()+"正在消費第%d個產品,該產品是: %c\n", cnt, ch);
--cnt;
return ch;
}
}
class Producer implements Runnable
{
private SynStack ss = null;
public Producer(SynStack ss)
{
this.ss = ss;
}
public void run()
{
char ch;
for (int i=0; i<10; ++i)
{
// try{
// Thread.sleep(100);
// }
// catch (Exception e){
// }
ch = (char)('a'+i);
ss.push(ch);
}
}
}
class Consumer implements Runnable
{
private SynStack ss = null;
public Consumer(SynStack ss)
{
this.ss = ss;
}
public void run()
{
for (int i=0; i<10; ++i)
{
/*try{
Thread.sleep(100);
}
catch (Exception e){
}*/
//System.out.printf("%c\n", ss.pop());
ss.pop();
}
}
}
public class TestPC2
{
public static void main(String[] args)
{
SynStack ss = new SynStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
Thread t1 = new Thread(p);
t1.setName("1號");
t1.start();
/*Thread t2 = new Thread(p);
t2.setName("2號");
t2.start();*/
Thread t6 = new Thread(c);
t6.setName("6號");
t6.start();
/*Thread t7 = new Thread(c);
t7.setName("7號");
t7.start();*/
}
}
上面的代碼只有一個消費者線程和一個生產者線程,程序運行完美,沒有任何錯誤,那為為什么jdk里面強調要用while呢?
這個問題,我之前也向了很久,同事提到了一點,這個程序如果用到多個生產者和消費者的情況,就會出錯,我試了一下,確實會出錯。但是我不能明白為什么就會出錯。
不是有synchronized關鍵字加鎖了嗎?
其實,這里也錯在我對wait方法原理的實際運行效果不是很了解,當我在wait方法的前后都加上輸出提示語句后,我明白了。
一個線程執行了wait方法以后,它不會再繼續執行了,直到被notify喚醒。
那么喚醒以后從何處開始執行?
這是解決這里出錯原因的關鍵。
我們嘗試修改代碼,實現一個生產線程,兩個消費線程。
/* 生產和消費 */ package multiThread; class SynStack { private char[] data = new char[6]; private int cnt = 0; //表示數組有效元素的個數 public synchronized void push(char ch) { if (cnt >= data.length) { try { System.out.println("生產線程"+Thread.currentThread().getName()+"准備休眠"); this.wait(); System.out.println("生產線程"+Thread.currentThread().getName()+"休眠結束了"); } catch (Exception e) { e.printStackTrace(); } } this.notify(); data[cnt] = ch; ++cnt; System.out.printf("生產線程"+Thread.currentThread().getName()+"正在生產第%d個產品,該產品是: %c\n", cnt, ch); } public synchronized char pop() { char ch; if (cnt <= 0) { try { System.out.println("消費線程"+Thread.currentThread().getName()+"准備休眠"); this.wait(); System.out.println("消費線程"+Thread.currentThread().getName()+"休眠結束了"); } catch (Exception e) { e.printStackTrace(); } } this.notify(); ch = data[cnt-1]; System.out.printf("消費線程"+Thread.currentThread().getName()+"正在消費第%d個產品,該產品是: %c\n", cnt, ch); --cnt; return ch; } } class Producer implements Runnable { private SynStack ss = null; public Producer(SynStack ss) { this.ss = ss; } public void run() { char ch; for (int i=0; i<10; ++i) { // try{ // Thread.sleep(100); // } // catch (Exception e){ // } ch = (char)('a'+i); ss.push(ch); } } } class Consumer implements Runnable { private SynStack ss = null; public Consumer(SynStack ss) { this.ss = ss; } public void run() { for (int i=0; i<10; ++i) { /*try{ Thread.sleep(100); } catch (Exception e){ }*/ //System.out.printf("%c\n", ss.pop()); ss.pop(); } } } public class TestPC2 { public static void main(String[] args) { SynStack ss = new SynStack(); Producer p = new Producer(ss); Consumer c = new Consumer(ss); Thread t1 = new Thread(p); t1.setName("1號"); t1.start(); /*Thread t2 = new Thread(p); t2.setName("2號"); t2.start();*/ Thread t6 = new Thread(c); t6.setName("6號"); t6.start(); Thread t7 = new Thread(c); t7.setName("7號"); t7.start(); } }
上面代碼就是在main函數里增加了一個消費線程。
然后錯誤出現了。

數組越界,為什么會這樣?
問題的關鍵就在於7號消費線程喚醒了6號消費線程,而6號消費線程被喚醒以后,它從哪里開始執行是關鍵!!!!
它會執行
System.out.println("消費線程"+Thread.currentThread().getName()+"休眠結束了");
這行代碼。
不是從pop()方法的開始處執行。
那么這跟使用if方法有什么關系?
因為,7號線程喚醒了6號線程,並執行了以下4行代碼。
ch = data[cnt-1];
System.out.printf("消費線程"+Thread.currentThread().getName()+"正在消費第%d個產品,該產品是: %c\n", cnt, ch);
--cnt;
return ch;
7號線程執行完上面的代碼后,cnt就=0了
又因為6號線程被喚醒時已經處在if方法體內,它不會再去執行if條件判斷,所以就順序往下執行,這個時候執行
ch = data[cnt-1];
就會出現越界異常。
假如使用while就不會,因為當喚醒了6號線程以后,它依然會去執行循環條件檢測。所以不可能執行下去,保證了程序的安全。
