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


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個包子               // Thread-2消費完了第6806個包子,然后喚醒等待的線程;
==========開始搶CPU的執行權==========  // 然后3個線程開始搶CPU的執行權
Thread-2消費端:等待。。。        // 消費端線程Thread-2搶到了,但是此時沒有包子了,所以等待
Thread-1消費端:醒過來了。。。      // 然后,消費端線程Thread-1搶到了執行權,在原來wait()方法的地方醒過來,
                      // 執行wait()方法后面的代碼System.out.println(Thread.currentThread().getName() + "消費端:醒過來了。。。")
Thread-1消費端:等待。。。        // 然后繼續while循環判斷,由於此時沒有包子,所以等待;
                      // 如果把while改成if,就不會判斷是否有包子,直接執行后面的代碼消費包子
                      // 此時並沒有包子了,這就產生了錯誤(同一個包子被消費了兩次)。
生產端:醒過來了。。。 生產第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也可以。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM