并发编程(四):wait/notify、保护性暂停、生产者消费者、活跃性
本文目录
一、wait / notify
1.为什么需要wait notify
2.原理
- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
- BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
- BLOCKED 线程会在 Owner 线程释放锁时唤醒
- WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争
3.API
- wait(): 让获得对象锁的线程到waitSet中一直等待
- wait(long n) : 当该等待线程没有被notify, 等待时间到了之后, 也会自动唤醒
- notify(): 让获得对象锁的线程, 使用锁对象调用notify去waitSet的等待线程中挑一个唤醒
- notifyAll() : 让获得对象锁的线程, 使用锁对象调用notifyAll去唤醒waitSet中所有的等待线程
它们都属于Object对象的方法;且必须获得此对象的锁, 才能调用这些方法,即只有当对象被锁以后(成为Monitor的Owner),才能调用wait和notify方法。
@Slf4j(topic = "c.test")
public class WaitNotifyTest {
static final Object obj = new Object();
public static void main(String[] args) throws Exception {
new Thread(() -> {
// 只有获得锁对象之后, 才能调用wait/notify
synchronized (obj) {
log.debug("执行...");
try {
obj.wait(); // 此时t1线程进入WaitSet等待
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码...");
}
}, "t1").start();
new Thread(() -> {
synchronized (obj) {
log.debug("执行...");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码...");
}
}, "t2").start();
// 让主线程等1秒在执行,为了`唤醒`,不睡的话,那两个线程还没进入waitSet,主线程就开始唤醒了
Thread.sleep(1000);
// 只有获得锁对象之后, 才能调用wait/notify
synchronized (obj) {
// obj.notify();
obj.notifyAll(); // 唤醒waitset中的全部等待线程
}
}
}
4.wait 和 sleep不同点
- Sleep是Thread类的静态方法,Wait是Object的方法,Object又是所有类的父类,所以所有类都有Wait方法。
- Sleep在阻塞的时候不会释放锁,而Wait在阻塞的时候会释放锁 (不释放锁的话, 其他线程就无法唤醒该线程了)
- Sleep方法不需要与synchronized一起使用,而Wait方法需要与synchronized一起使用(wait/notify等方法, 必须要使用对象锁来调用)
5.wait notify 改进上锁过程
synchronized小例子:
public class WaitNotifyTest {
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("没烟,先歇会!");
Sleeper.sleep(2); // 会阻塞2s, 不会释放锁
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
log.debug("可以开始干活了");
}
}, "其它人").start();
}
Sleeper.sleep(1);
new Thread(() -> {
// 此时没有加锁, 所以会优先于其他人先执行
// 这里能不能加 synchronized (room)?
//synchronized (room) { // 如果加锁的话, 送烟人也需要等待小南睡2s的时间,此时即使送到了,小南线程也将锁释放了..
hasCigarette = true;
log.debug("烟到了噢!");
//}
}, "送烟的").start();
}
}
- 其它干活的线程,都要一直阻塞,效率太低
- 小南线程必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来
解决方法,使用 wait - notify 机制
public class WaitNotifyTest {
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(); // 此时进入到waitset等待集合, 同时会释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]"