java多線程wait()方法必須放在while循環裏面的原因探析
阿新 • • 發佈:2019-03-03
override out nbsp 一個 spa implement exc 3.1 包子
1、寫一個包子生產消費案例:一次生產或消費一個包子,有包子就消費,沒有就生產。(部分代碼參考傳智播客劉意2015Java基礎視頻講義)
1.1 寫一個Baozi.class,包含main()方法,用來測試
package com.oy.demo3; /* * 包子生產消費案例:一次生產或消費一個包子,有包子就消費,沒有就生產。 */ public class Baozi { // 默認是flag,表示沒有包子,需要生產線程來生產包子;如果是true,說明有包子,需要消費端來消費包子。 public boolean flag; // 計數,當前在生產或消費第n個包子public int count = 0; public static void main(String[] args) { // 創建共享對象 Baozi s = new Baozi(); // 在外界把共享對象創建出來,通過構造方法傳遞給其他的類。這樣st、gt1、gt2就共享s對象。 SetThread st = new SetThread(s); GetThread gt1 = new GetThread(s); GetThread gt2 = new GetThread(s);// 線程類 Thread t1 = new Thread(st); Thread t2 = new Thread(gt1); Thread t3 = new Thread(gt2); // 啟動線程 t1.start(); t2.start(); t3.start(); } }
1.2 生產包子的線程類 SetThread.class
package com.oy.demo3; public class SetThread implementsRunnable { private Baozi s; public SetThread(Baozi s) { this.s = s; } @Override public void run() { while (true) { synchronized (s) { // 判斷有沒有 if (s.flag) { // 生產端,有就等待 try { System.out.println("生產端:等待。。。"); s.wait(); // 等待,並且立即釋放鎖。將來醒過來的時候,是從這裏醒過來的 System.out.println("生產端:醒過來了。。。"); } catch (InterruptedException e) { e.printStackTrace(); } } // 開始生產 s.count++; System.out.println("生產第" + s.count + "包子。。。"); // 生產完後,修改標記為true s.flag = true; // 喚醒線程 s.notifyAll(); System.out.println("==========開始搶CPU的執行權=========="); } } } }
1.3 消費包子的線程類 GetThread.class
package com.oy.demo3; public class GetThread implements Runnable { private Baozi s; public GetThread(Baozi s) { this.s = s; } @Override public void run() { while (true) { synchronized (s) { while (!s.flag) { // 消費端,沒有就等待 try { System.out.println(Thread.currentThread().getName() + "消費端:等待。。。"); s.wait(); // 等待,並且立即釋放鎖。將來醒過來的時候,是從這裏醒過來的 System.out.println(Thread.currentThread().getName() + "消費端:醒過來了。。。"); } catch (InterruptedException e) { e.printStackTrace(); } } // 開始消費 System.out.println(Thread.currentThread().getName() + "消費第" + s.count + "個包子"); // 消費完了,修改標記為false s.flag = false; // 喚醒線程 s.notifyAll(); System.out.println("==========開始搶CPU的執行權=========="); } } } }
2、測試結果(只選擇了控制臺打印的部分結果):
==========開始搶CPU的執行權==========
生產端:等待。。。
Thread-2消費端:醒過來了。。。
Thread-2消費第6806個包子
==========開始搶CPU的執行權==========
Thread-2消費端:等待。。。
Thread-1消費端:醒過來了。。。
Thread-1消費端:等待。。。
生產端:醒過來了。。。
生產第6807包子。。。
==========開始搶CPU的執行權==========
3、對測試結果的分析:
3.1 首先明確,生產端開啟了一個線程,消費端開啟了兩個線程。
3.2 Thread-2消費第6806個包子,然後喚醒等待的線程;然後3個線程開始搶CPU的執行權,Thread-2消費端線程搶到了,但是此時沒有包子了,所以等待;
3.3 然後,Thread-1消費端線程搶到了執行權,在原來wait()方法的地方醒過來,執行wait()方法後面的代碼System.out.println(Thread.currentThread().getName() + "消費端:醒過來了。。。");然後繼續while循環判斷,由於此時沒有包子,所以等待。如果把while改成if,就不會判斷是否有包子,之間執行後面的代碼消費包子,此時並沒有包子了,這就產生了錯誤(同一個包子被消費了兩次)。
3.4 綜上所述:wait()方法套在while循環中,線程下次醒過來後會繼續進行循環,判斷條件是否滿足,滿足就重新等待。
3.5 由於生產端只開啟了一個線程,所以將wait()方法套在if代碼塊中也是可以的,當然使用while也可以。
java多線程wait()方法必須放在while循環裏面的原因探析