1. 程式人生 > >java多線程wait()方法必須放在while循環裏面的原因探析

java多線程wait()方法必須放在while循環裏面的原因探析

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 implements
Runnable { 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循環裏面的原因探析