1. 程式人生 > 實用技巧 >wait和notify

wait和notify

wait notify 原理

  • Owner 執行緒發現條件不滿足,呼叫 wait 方法,即可進入 WaitSet 變為 WAITING 狀態
  • BLOCKED 和 WAITING 的執行緒都處於阻塞狀態,不佔用 CPU 時間片
  • BLOCKED 執行緒會在 Owner 執行緒釋放鎖時喚醒
  • WAITING 執行緒會在 Owner 執行緒呼叫 notify 或 notifyAll 時喚醒,但喚醒後並不意味者立刻獲得鎖,仍需進入EntryList 重新競爭

API:

  • obj.wait() 讓進入 object 監視器的執行緒到 waitSet 等待
  • obj.notify() 在 object 上正在 waitSet 等待的執行緒中挑一個喚醒
  • obj.notifyAll() 讓 object 上正在 waitSet 等待的執行緒全部喚醒

sleep(long n) 和 wait(long n) 的區別:

  1. sleep 是 Thread 方法,而 wait 是 Object 的方法

  2. sleep 不需要強制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用

  3. sleep 在睡眠的同時,不會釋放物件鎖的,但 wait 在等待的時候會釋放物件鎖

  4. 它們狀態 TIMED_WAITING

演示:

1.示例程式碼

import lombok.extern.slf4j.Slf4j;
import static com.dalianpai.Sleeper.sleep;
/**
 * @author WGR
 * @create 2020/12/29 -- 14:57
 */
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有沒有煙
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("有煙沒?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("沒煙,先歇會!");
                    sleep(2);
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以開始幹活了");
                }
            }
        }, "小南").start();


        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("可以開始幹活了");
                }
            }, "其它人").start();
        }

        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasCigarette = true;
                log.debug("煙到了噢!");
            }
        }, "送煙的").start();
    }

}

其它幹活的執行緒,都要一直阻塞,效率太低
小南執行緒必須睡足 2s 後才能醒來,就算煙提前送到,也無法立刻醒來
加了 synchronized (room) 後,就好比小南在裡面反鎖了門睡覺,煙根本沒法送進門,main 沒加synchronized 就好像 main 執行緒是翻窗戶進來的
解決方法,使用 wait - notify 機制

2.示例程式碼

import lombok.extern.slf4j.Slf4j;
import static com.dalianpai.Sleeper.sleep;
/**
 * @author WGR
 * @create 2020/12/29 -- 15:13
 */
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep2 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("有煙沒?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("沒煙,先歇會!");
                    try {
                        room.wait(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以開始幹活了");
                }
            }
        }, "小南").start();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("可以開始幹活了");
                }
            }, "其它人").start();
        }

        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasCigarette = true;
                log.debug("煙到了噢!");
                room.notify();
            }
        }, "送煙的").start();
    }

}

解決了其它幹活的執行緒阻塞的問題,但如果有其它執行緒也在等待條件呢?

3.示例程式碼

import lombok.extern.slf4j.Slf4j;
import static com.dalianpai.Sleeper.sleep;
/**
 * @author WGR
 * @create 2020/12/29 -- 15:17
 */
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep3 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    // 虛假喚醒
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("有煙沒?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("沒煙,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以開始幹活了");
                } else {
                    log.debug("沒幹成活...");
                }
            }
        }, "小南").start();

        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("外賣送到沒?[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("沒外賣,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外賣送到沒?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以開始幹活了");
                } else {
                    log.debug("沒幹成活...");
                }
            }
        }, "小北").start();

        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外賣到了噢!");
                room.notifyAll();
            }
        }, "送外賣的").start();


    }

}

用 notifyAll 僅解決某個執行緒的喚醒問題,但使用 if + wait 判斷僅有一次機會,一旦條件不成立,就沒有重新判斷的機會了
解決方法,用 while + wait,當條件不成立,再次 wait

4.示例程式碼

import lombok.extern.slf4j.Slf4j;
import static com.dalianpai.Sleeper.sleep;
/**
 * @author WGR
 * @create 2020/12/29 -- 15:19
 */
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep5 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {


        new Thread(() -> {
            synchronized (room) {
                log.debug("有煙沒?[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("沒煙,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以開始幹活了");
                } else {
                    log.debug("沒幹成活...");
                }
            }
        }, "小南").start();

        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("外賣送到沒?[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("沒外賣,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外賣送到沒?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以開始幹活了");
                } else {
                    log.debug("沒幹成活...");
                }
            }
        }, "小女").start();

        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外賣到了噢!");
                room.notifyAll();
            }
        }, "送外賣的").start();


    }

}