wait和notify原理

文章目录

  • 1.wait和notify
    • 1.1 小故事
    • 1.2 原理
    • 1.3 API介绍
      • obj.wait() 和 obj.notify()
      • sleep(long n)和 wait(long n) 的区别
  • 2.wait/notify的正确使用
  • 3.关于wait与notify和notifyAll方法的总结

1.wait和notify

1.1 小故事

wait和notify原理_第1张图片
wait和notify原理_第2张图片

1.2 原理

  • 1.当前线程必须拥有此对象的monitor监视器(锁)。
  • 2.当前线程调用wait()方法,线程就会释放此锁的所有权,并等待
  • 3.直到另一个线程通过调用notify方法或notifyAll方法通知在该对象的监视器(锁)上等待的线程唤醒。
  • 4.然后线程等待,直到它可以重新获得该对象的监视器(锁)的所有权然后继续执行(被唤醒之后还需等待直到获取锁才能继续执行)。
    wait和notify原理_第3张图片

1.3 API介绍

obj.wait() 和 obj.notify()

  • obj.wait()让进入 object 监视器的线程到 waitSet 等待
  • wait(long n) : 当该等待线程没有被notify, 等待时间到了之后, 也会自动唤醒
  • obj.notify()在 object 上正在 waitSet等待的线程中挑一个唤醒
  • obj.notifyAll()让 object 上正在 waitSet等待的线程全部唤醒

它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法

wait()
使当前线程等待,直到另一个线程为此对象调用notify()方法或notifyAll()方法。换句话说,这个方法的行为就像它只是执行wait(0)一样(如果没有当前对象notify()唤醒就会一直等待)。

 synchronized (obj) {
		// while 当线程被唤醒的再去判断是否满足条件
          while (<condition does not hold条件不满足>) 
              obj.wait();
          ... // 当前行为的动作
      }
}

代码举例

/**
 * @ClassName WaitNotifyTest
 * @author: shouanzh
 * @Description WaitNotifyTest
 * @date 2022/3/8 21:13
 */
@Slf4j
public class WaitNotifyTest {

    static final Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            synchronized (obj) {
                log.debug("t1执行...");
                try {
                    // 只有获得锁对象之后, 才能调用wait/notify
                    obj.wait(); // 此时t1线程进入WaitSet等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("t1其它代码...");
            }
        }, "t1").start();


        new Thread(() -> {
            synchronized (obj) {
                log.debug("t2执行...");
                try {
                    obj.wait(); // 此时t2线程进入WaitSet等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("t2其它代码...");
            }
        }, "t2").start();

        // 让主线程等两秒在执行,为了`唤醒`,不睡的话,那两个线程还没进入waitSet,主线程就开始唤醒了
        Thread.sleep(1000);
        log.debug("唤醒waitSet中的线程!");
        // 只有获得锁对象之后, 才能调用wait/notify
        synchronized (obj) {
            // obj.notify(); // 唤醒waitset中的一个线程
            obj.notifyAll(); // 唤醒waitset中的全部等待线程
        }
    }
}

obj.notifyAll();
wait和notify原理_第4张图片
obj.notify();
wait和notify原理_第5张图片

sleep(long n)和 wait(long n) 的区别

  • sleep是 Thread方法,而wait是 Object的方法
  • sleep 不需要强制和synchronized配合使用,但wait需要和synchronized 一起用
  • sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁。

2.wait/notify的正确使用

Step 1
分析下面代码:

@Slf4j
public class WaitNotifyTest {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        Thread.sleep(2000);   // 会阻塞2s, 不会释放锁
                    } 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();
        }

        Thread.sleep(1000);
        new Thread(() -> {
            // 此时没有加锁, 所以会优先于其他人先执行
            // 这里能不能加 synchronized (room)?
            //synchronized (room) { // 如果加锁的话, 送烟人也需要等待小南睡2s的时间,此时即使送到了,小南线程也将锁释放了..
                hasCigarette = true;
                log.debug("烟到了噢!");
            //}
        }, "送烟的").start();
    }
}
2022-03-08 22:05:28 [小南] - 有烟没?[false]
2022-03-08 22:05:28 [小南] - 没烟,先歇会!
2022-03-08 22:05:29 [送烟的] - 烟到了噢!
2022-03-08 22:05:30 [小南] - 有烟没?[true]
2022-03-08 22:05:30 [小南] - 可以开始干活了
2022-03-08 22:05:30 [其它人] - 可以开始干活了
2022-03-08 22:05:30 [其它人] - 可以开始干活了
2022-03-08 22:05:30 [其它人] - 可以开始干活了
2022-03-08 22:05:30 [其它人] - 可以开始干活了
2022-03-08 22:05:30 [其它人] - 可以开始干活了

Process finished with exit code 0

wait和notify原理_第6张图片
Step2: wait-notify机制

@Slf4j
public class WaitNotifyTest {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {

        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("可以开始干活了");
                }
            }
        }, "小南").start();

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

        Thread.sleep(1000);

        // 送烟线程
        new Thread(() -> {
            synchronized (room) {
                hasCigarette = true;
                log.debug("烟到了噢!");
                room.notify();
            }
        }, "送烟的").start();
    }
}
2022-03-08 22:17:11 [小南] - 有烟没?[false]
2022-03-08 22:17:11 [小南] - 没烟,先歇会!
2022-03-08 22:17:11 [其它人] - 可以开始干活了
2022-03-08 22:17:11 [其它人] - 可以开始干活了
2022-03-08 22:17:11 [其它人] - 可以开始干活了
2022-03-08 22:17:11 [其它人] - 可以开始干活了
2022-03-08 22:17:11 [其它人] - 可以开始干活了
2022-03-08 22:17:12 [送烟的] - 烟到了噢!
2022-03-08 22:17:12 [小南] - 有烟没?[true]
2022-03-08 22:17:12 [小南] - 可以开始干活了

Process finished with exit code 0

解决了其它干活的线程阻塞的问题
但如果有其它线程也在等待条件呢?

Step3

@Slf4j
public class WaitNotifyTest {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {

		// 虚假唤醒
        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        room.wait(); // 此时进入到waitset等待集合, 同时会释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        }, "小南").start();


        new Thread(() -> {
            synchronized (room) {
                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();

        Thread.sleep(1000);

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

    }
}

wait和notify原理_第7张图片
问题: 当外卖送到了, 却唤醒了小南, 此时就出现了问题

notify 只能随机唤醒一个 WaitSet中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线 程,称之为【虚假唤醒】
解决方法,改为 notifyAll

Step4: notifyAll

 		new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢!");
                room.notifyAll();
            }
        }, "送外卖的").start();
2022-03-08 22:33:29 [小南] - 有烟没?[false]
2022-03-08 22:33:29 [小南] - 没烟,先歇会!
2022-03-08 22:33:29 [小女] - 外卖送到没?[false]
2022-03-08 22:33:29 [小女] - 没外卖,先歇会!
2022-03-08 22:33:30 [送外卖的] - 外卖到了噢!
2022-03-08 22:33:30 [小女] - 外卖送到没?[true]
2022-03-08 22:33:30 [小女] - 可以开始干活了
2022-03-08 22:33:30 [小南] - 有烟没?[false]
2022-03-08 22:33:30 [小南] - 没干成活...

Process finished with exit code 0

还是唤醒了小南, 小南还是回去看看送来的是外卖还是烟. 很麻烦, 怎么解决?

用 notifyAll仅解决某个线程的唤醒问题,但使用 if+ wait 判断仅有一次机会,一旦条件不成立,就没有重新 判断的机会了

解决方法,用while + wait,当条件不成立,再次 wait

Step5:使用while循环来解决虚假唤醒

@Slf4j
public class WaitNotifyTest {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        room.wait(); // 此时进入到waitset等待集合, 同时会释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        }, "小南").start();


        new Thread(() -> {
            synchronized (room) {
                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();

        Thread.sleep(1000);

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

    }
}

wait和notify原理_第8张图片

wait()操作的代码

synchronized (lock) {

    while (条件不成立) {
       lock.wait();
    }
    // 干活
}

// 另一个线程
synchronized (lock) {
   // notifyAll() 唤醒
    lock.notifyAll();
}

3.关于wait与notify和notifyAll方法的总结

  • 当调用wait时,首先需要确保wait方法的线程已经持有了对象的锁(monitor)。
  • 当调用wait后,该对象就会释放掉这个对象的锁,然后进入到等待状态(wait set)
  • 当线程调用了wait后进入到等待状态时,它就可以等待其他线程调用相同对象的notify或notofyAll方法来使得自己被唤醒
  • 一旦这个线程被该对象的其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公平竞争);只有当该线程获取到了这个对象的锁后, 线程才会继续往下执行
  • 调用wait方法的代码片段需要放一个synchronized块或者是synchronized方法中,这样才可以确保线程在调用wait方法前已经获取到该对象的锁
  • 当调用对象的notify方法时,它会随机唤醒该对象等待集合(wait set)中的任意一个线程,当某个线程被唤醒后,它就会与其他线程一同竞争对象的锁。
  • 当调用对象的notifyAll方法时,它就会唤醒该对象等待集合(wait set)中的所有线程,这些线程被唤醒后,又会开始竞争对象的锁
  • 在某一时刻,只有唯一一个线程可以拥有对象的锁。

你可能感兴趣的:(Java并发编程,java)