● Owner线程发现条件不满足,调用wait( )方法即可进入WaitSet变为 WAITING状态
● BLOCKED 和 WAITING的线程都处于阻塞状态,不占用CPU时间片(相同点)
● BLOCKED 线程会在 Owner线程释放锁时唤醒
● WAITING 线程会在 Owner线程调用 notify
或 notifyAll
时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList重新竞争
● obj.wait() 让进入 object 监视器的线程到 waitSet 等待
● obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
● obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒
它们都是线程之间进行协作的手段,都属于 Object 对象的方法。无论是wait还是notify 必须获得此对象的锁,才能调用这几个方法
示例
正常运行:
import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.Test18")
public class Test18 {static final Object lock = new Object();public static void main(String[] args) {synchronized (lock) {try {/* 需先获取对象锁,成为Owner后才能调wait();这时才能进入lock所关联的Monitor对象中的WaitSet中WAITING*/lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}
}
notify():挑一个唤醒
import lombok.extern.slf4j.Slf4j;import static cn.itcast.n2.util.Sleeper.sleep;@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {final static Object obj = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (obj) {log.debug("执行....");try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}},"t1").start();new Thread(() -> {synchronized (obj) {log.debug("执行....");try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}},"t2").start();// 主线程两秒后执行sleep(2);log.debug("唤醒 obj 上其它线程");// 进入同一个对象中的Monitorsynchronized (obj) {// 唤醒obj上一个线程(挑一个线程唤醒)obj.notify();// obj.notifyAll(); // 唤醒obj上所有等待线程}}
}
运行结果:
notifyAll():全部唤醒
wait()
方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify 为止(wait(0)也会无限制等待)
wait(long n)
有时限的等待, 到 n 毫秒后结束等待,或是被 notify
import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {final static Object obj = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (obj) {log.debug("执行....");try {// 让线程t1在obj上等待1sobj.wait(1000);} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}},"t1").start();}}
运行结果:即使未唤醒也会结束
若在等待期间被其他线程唤醒,则会恢复,不会等够时间才才向下运行
import lombok.extern.slf4j.Slf4j;import static cn.itcast.n2.util.Sleeper.sleep;@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {final static Object obj = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (obj) {log.debug("执行....");try {// 让线程t1在obj上等待1sobj.wait(1000);} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}},"t1").start();// 主线程0.5秒后执行sleep(0.5);log.debug("唤醒 obj 上其它线程");// 进入同一个对象中的Monitorsynchronized (obj) {obj.notifyAll();}}
}
运行结果:
sleep(long n) 和 wait(long n) 的区别
sleep(0)
:触发操作系统立刻重新进行一次CPU的竞争。竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权
tip:作为锁的对象使用final修饰,final意味引用不可变(若引用发生变化,synchronized锁住的为不同对象)
sleep演示:
wait演示:(1s后主线程便成功获得锁)
问题背景:模拟线程使用共享的room来达到线程安全
// 共享变量(线程安全的操作)
static final Object room = new Object();
static boolean hasCigarette = false; // 是否有烟
static boolean hasTakeout = false; // 外卖是否送到
思考下面的解决方案是否较好,为什么?
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();}// 主线程等待1秒
sleep(1);
new Thread(() -> {// 这里能不能加 synchronized (room)?hasCigarette = true;log.debug("烟到了噢!");
}, "送烟的").start();
观察7个线程的工作流程:
出现的问题(缺点):
● 解决方法:使用 wait - notify 机制
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;@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("没烟,先歇会!");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();}sleep(1);// 主线程等待1s后启动睡眠线程new Thread(() -> {synchronized (room) {hasCigarette = true;log.debug("烟到了噢!");// 唤醒正在睡眠的线程room.notify();}}, "送烟的").start();}
}
运行结果:(并发效率得到大大提升)
深度思考
如果有其他线程也在等待条件呢?(送烟线程会不会错误唤醒其他线程)
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;@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("没烟,先歇会!");try {room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有烟没?[{}]", hasCigarette);if (hasCigarette) {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);// 主线程等待1s后启动睡眠线程new Thread(() -> {synchronized (room) {hasCigarette = true;log.debug("外卖到了噢!");room.notify(); // 调用notify()时,只能在room中等待的线程中随机挑一个唤醒}}, "送外卖的").start();}
}
运行结果:
出现的问题(缺点):notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为虚假唤醒
● 解决方法:使用 notifyAll将所有线程唤醒
运行结果:
出现的问题(缺点):用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了
● 解决方法:使用 while + wait,当条件不成立,再次 wait
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {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);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);// 主线程等待1s后启动睡眠线程new Thread(() -> {synchronized (room) {hasTakeout = true;log.debug("外卖到了噢!");room.notifyAll(); // 调用notifyAll()时,将所有在room中等待的线程全部唤醒}}, "送外卖的").start();}
}
运行结果:
总结:
● 正确使用wait-notify的格式:
synchronized (lock) {while(条件不成立){lock.wait();}// 条件成立,继续向下运行
}// 另一个线程
synchronized (lock) {lock.notifyAll();}
上一篇:23种Java设计模式