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 (<condition does not hold条件不满足>)
obj.wait();
...
}
}
代码举例
@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 {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1其它代码...");
}
}, "t1").start();
new Thread(() -> {
synchronized (obj) {
log.debug("t2执行...");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t2其它代码...");
}
}, "t2").start();
Thread.sleep(1000);
log.debug("唤醒waitSet中的线程!");
synchronized (obj) {
obj.notifyAll();
}
}
}
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);
} 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(() -> {
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();
} 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();
} 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) {
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)中的所有线程,这些线程被唤醒后,又会开始竞争对象的锁
- 在某一时刻,只有唯一一个线程可以拥有对象的锁。
|