多執行緒——虛假喚醒

斯內克喜歡吃益達發表於2020-11-08


一、虛假喚醒示例

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

        Producer producer = new Producer(clerk);
        Consumer consumer = new Consumer(clerk);
        Producer producer2 = new Producer(clerk);
        Consumer consumer2 = new Consumer(clerk);

        new Thread(producer, "生產者A").start();
        new Thread(consumer, "消費者B").start();
        new Thread(producer2, "生產者C").start();
        new Thread(consumer2, "消費者D").start();
    }
}

// 店員類:負責進貨和售貨
class Clerk {
    private int num = 0; //店裡當前的貨物量

    public synchronized void get() { //店員進貨  每次進貨一個(生產者)
        if (num >= 1) {
            System.out.println(Thread.currentThread().getName() + " 庫存已滿,無法進貨");
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName() + " wait後剩餘步驟");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " : " + (++num));
        this.notifyAll();
    }

    public synchronized void sale() { //店員賣貨 每次賣掉一個貨(消費者)
        if (num <= 0) {
            System.out.println(Thread.currentThread().getName() + " 庫存已空,無法賣貨");
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName() + " wait後剩餘步驟");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " : " + (--num));
        this.notifyAll();
    }
}

// 生產者 可以有很多生產者賣貨給這個店員
class Producer implements Runnable {
    private Clerk clerk;

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

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            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();
        }
    }
}

執行結果如下:

在這裡插入圖片描述
現在我們來分析一下這個過程

TIME發生操作倉庫數量
TIME1生產者A拿到鎖,進行生產1
TIME2生產者A再次拿到鎖,因為倉庫已滿,A等待1
TIME3生產者C拿到鎖,倉庫已滿,C也進入等待1
TIME4消費者D拿到鎖,進行消費,同時喚醒等待中的佇列(即下一輪鎖資源,將由A、B、C、D一起搶佔)0
TIME5消費者D拿到鎖,倉庫為空,D等待;A、B、C搶佔鎖資源0
TIME6消費者B拿到鎖,倉庫為空,B等待0
TIME7生產者C拿到鎖,因為C是從等待中被喚醒的,所以從wait之後開始執行,進行生產 ,並喚醒B、D1
TIME8生產者C再次拿到鎖,倉庫已滿,C進入等待1
TIME9生產者A拿到鎖,因為A是從等待中被喚醒的,所以從wait之後執行,進行生產,並喚醒C2(發生虛假喚醒)

  為什麼會產生這種原因呢?因為我們在進行是否wait是用的是if迴圈,if語句只執行一次,當等待中的鎖被喚醒,它將直接繼續向下執行,如果使用while,就會迴圈判斷是否滿足條件,如果不滿足將繼續進入等待。

二、正確開啟方式

程式碼如下:

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

        Producer producer = new Producer(clerk);
        Consumer consumer = new Consumer(clerk);
        Producer producer2 = new Producer(clerk);
        Consumer consumer2 = new Consumer(clerk);

        new Thread(producer, "生產者A").start();
        new Thread(consumer, "消費者B").start();
        new Thread(producer2, "生產者C").start();
        new Thread(consumer2, "消費者D").start();
    }
}

// 店員類:負責進貨和售貨
class Clerk {
    private int num = 0; //店裡當前的貨物量

    public synchronized void get() { //店員進貨  每次進貨一個(生產者)
        while (num >= 1) {
            System.out.println(Thread.currentThread().getName() + " 庫存已滿,無法進貨");
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName() + " wait後剩餘步驟");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " : " + (++num));
        this.notifyAll();
    }

    public synchronized void sale() { //店員賣貨 每次賣掉一個貨(消費者)
        while (num <= 0) {
            System.out.println(Thread.currentThread().getName() + " 庫存已空,無法賣貨");
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName() + " wait後剩餘步驟");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " : " + (--num));
        this.notifyAll();
    }
}

// 生產者 可以有很多生產者賣貨給這個店員
class Producer implements Runnable {
    private Clerk clerk;

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

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            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();
        }
    }
}

在這裡插入圖片描述

總結

在進行wait操作的條件判斷時,注意判斷語句的選擇,以防出現虛假喚醒。
Good Good Study,Day Day Up!

相關文章