wait和notify原理

1.wait和notify

1.1 小故事

在这里插入图片描述
在这里插入图片描述

1.2 原理

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

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();
在这里插入图片描述
obj.notify();
在这里插入图片描述

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

在这里插入图片描述
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();

    }
}

在这里插入图片描述
问题: 当外卖送到了, 却唤醒了小南, 此时就出现了问题

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()操作的代码

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)中的所有线程,这些线程被唤醒后,又会开始竞争对象的锁
  • 在某一时刻,只有唯一一个线程可以拥有对象的锁。
`wait()` 和 `notify()` 方法是 Java 中用于线程间通信的两个重要方法。`wait()` 方法用于让线程进入等待状态,并释放锁,而 `notify()` 方法则用于唤醒等待状态的线程。这两个方法必须在同步方法或同步块中使用,否则会抛出 `IllegalMonitorStateException` 异常。 `wait()` 方法的调用会让线程进入等待状态,直到有其他线程调用了同一个对象的 `notify()` 或 `notifyAll()` 方法来唤醒它。在等待期间,线程会释放它持有的锁,以便其他线程可以进入同步块执行。`wait()` 方法可以使用以下两种方式调用: - `wait()`:让线程一直等待,直到其他线程调用了 `notify()` 或 `notifyAll()` 方法唤醒它。 - `wait(long timeout)`:让线程等待一段时间,如果在等待期间没有其他线程调用 `notify()` 或 `notifyAll()` 方法唤醒它,那么线程会自动醒来。 `notify()` 方法用于唤醒等待状态的线程。它会随机地唤醒一个等待状态的线程,如果有多个线程都在等待同一个对象的锁,那么只有其中一个线程会被唤醒。`notifyAll()` 方法则会唤醒所有等待状态的线程。 以下是一个简单的示例,展示了如何使用 `wait()` 和 `notify()` 方法进行线程间通信: ```java class MyThread implements Runnable { private final Object lock; public MyThread(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { System.out.println("Thread " + Thread.currentThread().getName() + " is waiting"); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread " + Thread.currentThread().getName() + " is awake"); } } } public class Main { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); Thread t1 = new Thread(new MyThread(lock)); Thread t2 = new Thread(new MyThread(lock)); t1.start(); t2.start(); Thread.sleep(1000); synchronized (lock) { lock.notify(); } } } ``` 在这个示例中,我们创建了两个线程 `t1` 和 `t2`,它们都在同一个对象 `lock` 上等待。在主线程中,我们等待 1 秒钟后调用了 `notify()` 方法来唤醒一个等待状态的线程。由于 `notify()` 方法是随机唤醒一个线程,因此我们无法确定哪个线程会被唤醒。在这个示例中,我们可以看到其中一个线程被唤醒并输出了 "Thread X is awake" 的信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值