Java生產(chǎn)消費(fèi)問題與虛假喚醒(spurious wakeup)

參考并發(fā)容器-阻塞隊(duì)列 第四部分“阻塞隊(duì)列的實(shí)現(xiàn)原理”。
參考代碼生產(chǎn)者-消費(fèi)者

1.缺少wait會(huì)出現(xiàn)的問題

三個(gè)類:售貨員Clerk,工廠Factory,消費(fèi)者Consumer
Factory和Consumer共享Clerk對(duì)象

class Clerk{
    //商品數(shù)量默認(rèn)是0,volatile關(guān)鍵字保證內(nèi)存可見性
    private volatile int product=0;

    //進(jìn)貨,synchronized關(guān)鍵字保證原子性,互斥性
    public synchronized void get(){
        if(product>10){
            System.out.println("貨滿了");
        }else {
            ++product;
            System.out.println(Thread.currentThread().getName()+"進(jìn)貨"+product);
        }
    }

    //售貨
    public synchronized void sale(){
        if(product<=0){
            System.out.println("沒貨了");
        }else{
            System.out.println(Thread.currentThread().getName()+"賣貨"+product);
            --product;
        }
    }
}
class Factory implements Runnable{

    private Clerk clerk;

    Factory(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i=0;i<20;i++){
        clerk.get();//進(jìn)貨
        }
    }
}
class Consumer implements Runnable{

    private Clerk clerk;

    Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for(int i=0;i<20;i++){
            clerk.sale();//賣貨
        }

    }        
}
public static void main(String[] args) {

        Clerk clerk = new Clerk();
        Factory factory = new Factory(clerk);
        Consumer consumer = new Consumer(clerk);

        Thread tf = new Thread(factory);
        Thread tc = new Thread(consumer);

        tf.start();
        tc.start();

    }

輸出結(jié)果:

Thread-0進(jìn)貨1
Thread-0進(jìn)貨2
Thread-0進(jìn)貨3
Thread-0進(jìn)貨4
Thread-0進(jìn)貨5
Thread-0進(jìn)貨6
Thread-0進(jìn)貨7
Thread-0進(jìn)貨8
Thread-0進(jìn)貨9
Thread-0進(jìn)貨10
Thread-0進(jìn)貨11
貨滿了
貨滿了
貨滿了
貨滿了
貨滿了
貨滿了
貨滿了
貨滿了
貨滿了
Thread-1賣貨11
Thread-1賣貨10
Thread-1賣貨9
Thread-1賣貨8
Thread-1賣貨7
Thread-1賣貨6
Thread-1賣貨5
Thread-1賣貨4
Thread-1賣貨3
Thread-1賣貨2
Thread-1賣貨1
沒貨了
沒貨了
沒貨了
沒貨了
沒貨了
沒貨了
沒貨了
沒貨了
沒貨了
  • 問題:上述的情況是當(dāng)沒貨的時(shí)候還會(huì)繼續(xù)調(diào)用該方法,從而占用資源,二貨滿的情況下也會(huì)重復(fù)調(diào)用進(jìn)貨方法,占用資源,這樣是不合理的。
  • 解決方式:當(dāng)貨滿了,應(yīng)該停止進(jìn)貨,釋放鎖讓消費(fèi)者消費(fèi),當(dāng)沒貨了應(yīng)該停止消費(fèi)釋放鎖,讓進(jìn)貨,這是我們想要的邏輯。
    使用wait()和notifyAll()這兩個(gè)方法來實(shí)現(xiàn)。
class Clerk{
    //商品數(shù)量默認(rèn)是0
    private volatile int product=0;

    //進(jìn)貨
    public synchronized void get(){
        if(product>10){
            System.out.println("貨滿了");
            try {
                this.wait();//等待并釋放clerk的對(duì)象鎖,進(jìn)入線程隊(duì)列等待被喚醒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            ++product;
            System.out.println(Thread.currentThread().getName()+"進(jìn)貨"+product);
            notifyAll();//喚醒等待的線程
        }
    }

    //售貨
    public synchronized void sale(){
        if(product<=0){
            System.out.println("沒貨了");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName()+"賣貨"+product);
            --product;
            notifyAll();
        }
    }
}
  • 結(jié)果
Thread-0進(jìn)貨1
Thread-0進(jìn)貨2
Thread-0進(jìn)貨3
Thread-0進(jìn)貨4
Thread-0進(jìn)貨5
Thread-0進(jìn)貨6
Thread-0進(jìn)貨7
Thread-0進(jìn)貨8
Thread-0進(jìn)貨9
Thread-0進(jìn)貨10
Thread-0進(jìn)貨11
貨滿了
Thread-1賣貨11
Thread-1賣貨10
Thread-1賣貨9
Thread-1賣貨8
Thread-1賣貨7
Thread-1賣貨6
Thread-1賣貨5
Thread-1賣貨4
Thread-1賣貨3
Thread-1賣貨2
Thread-1賣貨1
沒貨了
Thread-0進(jìn)貨1
Thread-0進(jìn)貨2
Thread-0進(jìn)貨3
Thread-0進(jìn)貨4
Thread-0進(jìn)貨5
Thread-0進(jìn)貨6
Thread-0進(jìn)貨7
Thread-0進(jìn)貨8
Thread-1賣貨8
Thread-1賣貨7
Thread-1賣貨6
Thread-1賣貨5
Thread-1賣貨4
Thread-1賣貨3
Thread-1賣貨2
Thread-1賣貨1

2.線程阻塞無法喚醒

  • 當(dāng)產(chǎn)品為1時(shí),生成者線程生產(chǎn)結(jié)束;此時(shí)Consumer還處于wait無法得到喚醒。
    這種情景對(duì)嗎?這種問題什么情況下會(huì)發(fā)生?
  • 解決方式:去掉else,每次都會(huì)喚醒另外一方的線程。
class Clerk{
    //商品數(shù)量默認(rèn)是0
    private volatile int product=0;

    //進(jìn)貨
    public synchronized void get(){
        if(product>10){
            System.out.println("貨滿了");
            try {
                this.wait();//等待并釋放clerk的對(duì)象鎖,進(jìn)入線程隊(duì)列等待被喚醒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        ++product;
        System.out.println(Thread.currentThread().getName()+"進(jìn)貨"+product);
        notifyAll();//喚醒等待的線程
    }

    //售貨
    public synchronized void sale(){
        if(product<=0){
            System.out.println("沒貨了");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
         System.out.println(Thread.currentThread().getName()+"賣貨"+product);
          --product;
         notifyAll();
    }
}

3.虛假喚醒

Clerk clerk = new Clerk();
        Factory factory = new Factory(clerk);
        Consumer consumer = new Consumer(clerk);

        Thread tf = new Thread(factory);
        Thread tc = new Thread(consumer);
        Thread tc2 = new Thread(consumer);
        tf.start();
        tc.start();
        tc2.start();
  • 會(huì)出現(xiàn)負(fù)數(shù)!
沒貨了
沒貨了
Thread-0進(jìn)貨1
Thread-2賣貨1
沒貨了
Thread-1賣貨0
沒貨了
Thread-2賣貨-1
沒貨了
Thread-1賣貨-2
沒貨了
Thread-2賣貨-3
沒貨了
Thread-1賣貨-4
沒貨了
Thread-2賣貨-5
沒貨了
Thread-1賣貨-6
  • 原因
    兩個(gè)消費(fèi)者都處于wait狀態(tài),然后生產(chǎn)者生產(chǎn)了一個(gè)notifyAll,兩個(gè)消費(fèi)者同時(shí)往下執(zhí)行,導(dǎo)致product為負(fù)數(shù)。
  • 解決方法:防止虛假喚醒,應(yīng)該放在循環(huán)中,多次進(jìn)行檢查,直到滿足條件才進(jìn)行下一步

4.守護(hù)線程解決線程阻塞

  • 當(dāng)多個(gè)消費(fèi)者和一個(gè)生產(chǎn)者的時(shí)候,生產(chǎn)者有可能先結(jié)束循環(huán),但是消費(fèi)者還沒結(jié)束,結(jié)果到了其他消費(fèi)者的時(shí)候發(fā)現(xiàn)product是小于0的于是就wait,程序一直等待得不到結(jié)束,就會(huì)一直在wait()
  • 解決方式
    在共享資源clerk類中定義生產(chǎn)者線程標(biāo)志位,在main線程中創(chuàng)建一個(gè)線程設(shè)置為守護(hù)線程 并啟動(dòng),在該守護(hù)線程中創(chuàng)建匿名內(nèi)部類Runnable,并在run方法中判斷生產(chǎn)者線程isAlive() 。如果生產(chǎn)者線程結(jié)束,就把標(biāo)志位置為false,該標(biāo)識(shí)位和消費(fèi)者線程的while判斷條件中串聯(lián),當(dāng)生產(chǎn)者線程為false的之后短路,使得消費(fèi)和線程啥都不做,直到線程結(jié)束。
  • Clerk中設(shè)置Factory 線程標(biāo)志位
    private boolean facctoryFlg = true;//工廠線程結(jié)束的標(biāo)志位,為false表示線程執(zhí)行完畢
    public boolean isFacctoryFlg() {
        return facctoryFlg;
    }

    public void setFacctoryFlg(boolean facctoryFlg) {
        this.facctoryFlg = facctoryFlg;
    }
  • Main中創(chuàng)建守護(hù)線程
        //創(chuàng)建守護(hù)線程
        Thread daemon = new Thread(new Runnable() {
            @Override
            public void run() {
               while(true){
                  if(!tf.isAlive()){
                      clerk.setFacctoryFlg(false);
                      System.out.println("factory--------------"+tf.isAlive());
                      break;
                  }
               }
            }
        });
        daemon.setDaemon(true);//設(shè)置為守護(hù)線程(后臺(tái)線程)
        daemon.start();
  • 修改·Clerk的sale方法
    public synchronized void sale(){
        while(product<=0){
            //當(dāng)Factory線程結(jié)束的時(shí)候,直接結(jié)束sale方法
            if(!isFacctoryFlg()){
                return;
            }
            System.out.println("沒貨了");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"賣貨"+product);
        --product;
        notifyAll();
    }

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容