wait-notify原理
- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
- BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
- BLOCKED 线程会在 Owner 线程释放锁时唤醒
- WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争
相关API
- obj.wait() 让进入 object 监视器的线程到 waitSet 等待。有参数的wait()等待n毫秒后结束等待,或在n毫秒之前被唤醒
- obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
- obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒
它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法
wait-notify的正确姿势
0. sleep(long n) 和 wait(long n) 的区别
- sleep 是 Thread 方法,而 wait 是 Object 的方法
- sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
- sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
- 它们状态 TIMED_WAITING
synchronized(lock){
while(条件不成立){
lock.wait();
}
// do
}
// 另外一个线程
synchronized(lock){
//使条件满足
lock.notifyAll();
}
例:
class Solution {
static final Object lock = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (lock) {
System.out.println("有烟没?");
while (!hasCigarette) {
System.out.println("没烟,小南休息会~");
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("有烟没?");
if (hasCigarette) {
System.out.println("有烟,小南开始干活--");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (lock) {
System.out.println("有外卖吗?");
while (!hasTakeout) {
System.out.println("没有外卖,小女休息一会~");
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("有外卖吗?");
if (hasTakeout) {
System.out.println("有外卖,小女开始干活");
}
}
}, "小女").start();
Thread.sleep(1000);
synchronized (lock) {
//送外卖
hasTakeout = true;
lock.notifyAll();
}
Thread.sleep(3000);
//送烟
synchronized (lock) {
hasCigarette = true;
lock.notifyAll();
}
}
}
park & unpark
基本使用
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)
也可以先unpark再使用park,此时线程就不停止。具体见原理
park & unpark原理
每个线程都有自己的一个 Parker 对象,由三部分组成 _counter , _cond 和 _mutex 打个比喻:
- 线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
- 调用 park 就是要看需不需要停下来歇息
- 如果备用干粮耗尽,那么钻进帐篷歇息
- 如果备用干粮充足,那么不需停留,继续前进
- 调用 unpark,就好比令干粮充足
- 如果这时线程还在帐篷,就唤醒让他继续前进
- 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
- 因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮
1.调用park时会判断counter是否为0,为0则需要暂停,获取_mutex,进入_cond变量阻塞,设置counter=0
2.调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1,唤醒 _cond 条件变量中的 Thread_0,Thread_0 恢复运行,设置 _counter 为 0
3.当先调用了一次unpark方法后(即counter=1),调用park方法时会检查counter发现=1,不暂停counter置为0
评论