(5) wait-notify

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) 的区别

  1. sleep 是 Thread 方法,而 wait 是 Object 的方法
  2. sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
  3. sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
  4. 它们状态 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

end

评论