對於java多線程的wait()方法,咱們在jdk1.6的說明文檔裏能夠看到這樣一段話java
從上面的截圖,咱們能夠看出,在使用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關鍵字加鎖了嗎?this
其實,這裏也錯在我對wait方法原理的實際運行效果不是很瞭解,當我在wait方法的先後都加上輸出提示語句後,我明白了。spa
一個線程執行了wait方法之後,它不會再繼續執行了,直到被notify喚醒。線程
那麼喚醒之後從何處開始執行?code
這是解決這裏出錯緣由的關鍵。視頻
咱們嘗試修改代碼,實現一個生產線程,兩個消費線程。
/* 生產和消費 */ 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號線程之後,它依然會去執行循環條件檢測。因此不可能執行下去,保證了程序的安全。