1. 程式人生 > >Java多執行緒生產者消費者說明等待喚醒機制問題和虛假喚醒問題

Java多執行緒生產者消費者說明等待喚醒機制問題和虛假喚醒問題

不用等待喚醒機制實現的生產者與消費者

程式碼

package com.hust.juc;

/*
 * 生產者和消費者案例
 */
public class TestProductorAndConsumer {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Productor pro = new Productor(clerk);
        Consumer cus = new Consumer(clerk);

        new Thread(pro, "生產者 A"
).start(); new Thread(cus, "消費者 B").start(); /* * new Thread(pro, "生產者 C").start(); new Thread(cus, "消費者 D").start(); */ } } // 店員 class Clerk { private int product = 0; // 進貨 public synchronized void get() {// 迴圈次數:0 if (product >= 1) { System.out.println("產品已滿!"
); /*try { this.wait(); } catch (InterruptedException e) { }*/ } else { System.out.println(Thread.currentThread().getName() + " : " + ++product); //this.notifyAll(); } } // 賣貨 public
synchronized void sale() {// product = 0; 迴圈次數:0 if (product <= 0) { System.out.println("缺貨!"); /*try { this.wait(); } catch (InterruptedException e) { }*/ } else { System.out.println(Thread.currentThread().getName() + " : " + --product); //this.notifyAll(); } } } // 生產者 class Productor implements Runnable { private Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 20; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { } clerk.get(); } } } // 消費者 class Consumer implements Runnable { private Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { for (int i = 0; i < 20; i++) { clerk.sale(); } } }

執行結果

這裡寫圖片描述

只要執行緒得到了CPU就會執行,缺貨也會一直去缺貨,很不科學,所以這個時候我們得用等待喚醒機制。

使用等待喚醒機制

程式碼

就是把上面的程式碼中await和notify的註釋開啟

執行結果

這裡寫圖片描述

這個時候會發現都執行完了,但是程式並沒有結束,這就是等待喚醒機制遺留問題。

解決等待喚醒機制的問題

我們分析一下,很容易想到是因為,await被喚醒之後是從await的地方繼續執行,那麼直接從else出走掉了,假如那是消費者的最後一輪迴圈,那麼那個最後一輪迴圈的執行緒就結束了,生產者還有一輪或者多倫迴圈沒有結束,生產者執行緒生產出的商品就沒有執行緒去消費就會一直等待。

程式碼去掉else就解決了這個問題

package com.hust.juc;

/*
 * 生產者和消費者案例
 */
public class TestProductorAndConsumer {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Productor pro = new Productor(clerk);
        Consumer cus = new Consumer(clerk);

        new Thread(pro, "生產者 A").start();
        new Thread(cus, "消費者 B").start();

        /*
         * new Thread(pro, "生產者 C").start(); new Thread(cus, "消費者 D").start();
         */
    }

}

// 店員
class Clerk {
    private int product = 0;

    // 進貨
    public synchronized void get() {// 迴圈次數:0
        if (product >= 1) {
            System.out.println("產品已滿!");

            try {
                this.wait();
            } catch (InterruptedException e) {
            }

        }

        System.out
                .println(Thread.currentThread().getName() + " : " + ++product);
        this.notifyAll();
    }

    // 賣貨
    public synchronized void sale() {// product = 0; 迴圈次數:0
        if (product <= 0) {
            System.out.println("缺貨!");

            try {
                this.wait();
            } catch (InterruptedException e) {
            }
        }

        System.out
                .println(Thread.currentThread().getName() + " : " + --product);
        this.notifyAll();
    }
}

// 生產者
class Productor implements Runnable {
    private Clerk clerk;

    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }

            clerk.get();
        }
    }
}

// 消費者
class Consumer implements Runnable {
    private Clerk clerk;

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

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

多執行緒下的虛假喚醒

說白了其實這還是“單執行緒“,因為只有一個執行緒生產一個執行緒消費,如果現在有兩個執行緒去生產,兩個執行緒去消費,那麼就會出現虛假喚醒的情況。

虛假喚醒程式碼

把上面的程式碼中CD執行緒註釋掉的開啟就行。

執行截圖

這裡寫圖片描述

虛假喚醒問題分析

仔細考慮一下,假如現在消費者執行緒C執行,此時沒有商品,消費者執行緒C等待,這個時候消費者執行緒D也拿到了CPU,也是沒有商品的狀態,也等待。這個時候一個生產者生產出了一個商品,此時喚醒了兩個等待的消費者,往下執行,那麼很自然就產生了-1等負數的情況。

解決辦法就是把wait放在迴圈裡

package com.hust.juc;

/*
 * 生產者和消費者案例
 */
public class TestProductorAndConsumer {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Productor pro = new Productor(clerk);
        Consumer cus = new Consumer(clerk);

        new Thread(pro, "生產者 A").start();
        new Thread(cus, "消費者 B").start();

        new Thread(pro, "生產者 C").start();
        new Thread(cus, "消費者 D").start();

    }
}

// 店員
class Clerk {
    private int product = 0;

    // 進貨
    public synchronized void get() {// 迴圈次數:0
        while (product >= 1) {
            System.out.println("產品已滿!");

            try {
                this.wait();
            } catch (InterruptedException e) {
            }

        }

        System.out
                .println(Thread.currentThread().getName() + " : " + ++product);
        this.notifyAll();
    }

    // 賣貨
    public synchronized void sale() {// product = 0; 迴圈次數:0
        while (product <= 0) {
            System.out.println("缺貨!");

            try {
                this.wait();
            } catch (InterruptedException e) {
            }
        }

        System.out
                .println(Thread.currentThread().getName() + " : " + --product);
        this.notifyAll();
    }
}

// 生產者
class Productor implements Runnable {
    private Clerk clerk;

    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }

            clerk.get();
        }
    }
}

// 消費者
class Consumer implements Runnable {
    private Clerk clerk;

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

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

執行截圖

這裡寫圖片描述

這個才會得到解決。