1、API介绍
- obj.wait() 让进入 object 监视器的线程到 waitSet 等待
- obj.wait(long timeout) 让进入 object 监视器的线程到 waitSet 等待,但是会有一个时限,不会无限制等待
- obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
- obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒
它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁(成为 Owner),才能调用这几个方法
比如:
private final static Object OBJ = new Object();
@SneakyThrows
public static void main(String[] args) {
OBJ.wait();
}
报错误的监视器状态异常,也就是当前线程压根就没获取到 OBJ 锁,执行 wait 不知道去哪个 WaitSet?正确应该这样用
private final static Object OBJ = new Object();
@SneakyThrows
public static void main(String[] args) {
synchronized (OBJ) {
OBJ.wait();
}
}
示例
private final static Object OBJ = new Object();
@SneakyThrows
public static void main(String[] args) {
new Thread(() -> {
synchronized (OBJ) {
log.info("执行....");
try {
OBJ.wait(); // 让线程在OBJ上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("t1 其它代码....");
}
}, "t1").start();
new Thread(() -> {
synchronized (OBJ) {
log.info("执行....");
try {
OBJ.wait(); // 让线程在OBJ上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("t2 其它代码....");
}
}, "t2").start();
// 主线程两秒后执行
TimeUnit.SECONDS.sleep(2);
log.info("唤醒 OBJ 上其它线程");
synchronized (OBJ) {
OBJ.notify(); // 唤醒OBJ上一个线程
}
}
可以发现,notify 一次只能唤醒 WaitSet 中的随机一个,如果需要将 t1 和 t2 同时唤醒,可以使用 notiyAll,将所有的 WaitSet 中的线程都唤醒
2、原理
回顾下面这张图
- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
- BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
- BLOCKED 线程会在 Owner 线程释放锁时唤醒
- WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入 EntryList 重新竞争
3、wait和sleep的区别
- sleep 是 Thread 方法,而 wait 是 Object 的方法
- sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
- sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
- 它们状态都是 TIMED_WAITING
4、正确使用姿势
这里模拟一个场景,现在有一个工作室(锁),有一个人小南,他毛病比较多,它必须有烟才能干活,我们实现一下
static final Object ROOM = new Object();
static boolean hasCigarette = false;
@SneakyThrows
public static void main(String[] args) {
new Thread(() -> {
synchronized (ROOM) {
log.info("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.info("没烟,先歇会!");
try {
ROOM.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.info("可以开始干活了");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (ROOM) {
log.info("可以开始干活了");
}
}, "其它人").start();
}
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
synchronized (ROOM) {
ROOM.notify();
hasCigarette = true;
log.info("烟到了噢!");
}
}, "送烟的").start();
}
但是现在的程序还存在一个问题,如果有其他线程也在 wait 怎么办呢?岂不是容易唤醒错误的线程(虚假唤醒)?比如,现在还有一个人叫小女,她也很奇怪,只有外卖到了他才能干活
static final Object ROOM = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
@SneakyThrows
public static void main(String[] args) {
new Thread(() -> {
synchronized (ROOM) {
log.info("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.info("没烟,先歇会!");
try {
ROOM.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.info("可以开始干活了");
} else {
log.info("没干成活...");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (ROOM) {
Thread thread = Thread.currentThread();
log.info("外卖送到没?[{}]", hasTakeout);
if (!hasTakeout) {
log.info("没外卖,先歇会!");
try {
ROOM.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("外卖送到没?[{}]", hasTakeout);
if (hasTakeout) {
log.info("可以开始干活了");
} else {
log.info("没干成活...");
}
}
}, "小女").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
synchronized (ROOM) {
hasTakeout = true;
log.info("外卖到了噢!");
ROOM.notify();
}
}, "送外卖的").start();
}
为了解决虚假唤醒,导致正确的线程没有被唤醒,我们可以尝试使用 notifyAll ,这样正确的目标线程一定能被唤醒,但是也会唤醒不该唤醒的线程,所以此时,我们可以尝试使用 while 循环来判断是否是有效唤醒,否则再次 wait
static final Object ROOM = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
@SneakyThrows
public static void main(String[] args) {
new Thread(() -> {
synchronized (ROOM) {
log.info("有烟没?[{}]", hasCigarette);
while (!hasCigarette) {
log.info("没烟,先歇会!");
try {
ROOM.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.info("可以开始干活了");
} else {
log.info("没干成活...");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (ROOM) {
log.info("外卖送到没?[{}]", hasTakeout);
while (!hasTakeout) {
log.info("没外卖,先歇会!");
try {
ROOM.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("外卖送到没?[{}]", hasTakeout);
if (hasTakeout) {
log.info("可以开始干活了");
} else {
log.info("没干成活...");
}
}
}, "小女").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
synchronized (ROOM) {
hasTakeout = true;
log.info("外卖到了噢!");
ROOM.notifyAll();
}
}, "送外卖的").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
synchronized (ROOM) {
ROOM.notify();
hasCigarette = true;
log.info("烟到了噢!");
}
}, "送烟的").start();
}
所以正确的使用模板应该是
synchronized(lock) {
while(条件不成立) {
lock.wait();
}
// 干活
}
//另一个线程
synchronized(lock) {
lock.notifyAll();
}