wait/nofity

原理

WAITING状态的线程是获得了锁,但是又主动放弃了锁的。它会进入Monitor的WaitSet中排队等等。

而BLOCKED状态的是还没获得锁,在Monitor的EntryList中等待唤醒。

那WAITING怎么唤醒呢?只有在Owner调用notify时,它们会去EntryList中排队,重新竞争锁。

API使用

  • obj.wait()让进入 object 监视器的线程到 waitSet 等待
  • obj.notify()在 object 上正在 waitSet 等待的线程中挑一个唤醒
  • obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

它们是线程间协作的方法,都是属性Object对象的方法,而且必须是获得对象锁才能调用

final static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        }).start();
        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        }).start();
        // 主线程两秒后执行
        Thread.sleep(2);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
//            obj.notify(); // 唤醒obj上一个线程
             obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }

wait vs sleep

  1. sleep是Thread的方法,而wait是Object的方法
  2. sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起用
  3. sleep在睡眠时,不释放锁,但wait在等待时会释放锁
  4. 它们的状态都是TIME_WAITING

注意虚假唤醒

错误唤醒等待的线程,就叫虚假唤醒。

因为nofity()是随机唤醒WaitSet中的一个线程,唤醒错了,这就不好处理了。

可以使用notifyAll()把waiting中的线程都唤醒了。

(但好像还是不能精准唤醒)

可以使用while包着wait,即使唤醒了,但是不符合条件,也会重新进入wait()

最佳实践

// 一个线程
synchronized(lock){
	while(条件判断){
        // ...
        room.wait();
    }
    // 干活
}

// 另一个线程
synchronized(lock){
	lock.notifyAll();
}

同步模式之保护性暂停

Guarded Suspension,用在一个线程等待另一个线程的执行结果。

要点

  • 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject
  • 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
  • JDK 中,join 的实现、Future 的实现,采用的就是此模式
  • 因为要等待另一方的结果,因此归类到同步模式
public class TestGuardObject {
    public static void main(String[] args) {
        GuardedObject guardedObject = new GuardedObject();

        new Thread(()->{
            System.out.println(guardedObject.get());
        }, "t1").start();

        new Thread(()->{
            System.out.println("下载。。。");
            guardedObject.complete(download());
        }, "t2").start();
 
    }

    private static Object download() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new Dog();
    }
}

class GuardedObject{
    private Object response;

    /**
     * 获取结果
     * @return
     */
    public Object get(){
        synchronized (this){
            while (response == null){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return response;
        }
    }

    public void complete(Object response){
        synchronized (this){
            // 赋值
            this.response = response;
            this.notifyAll();
        }
    } 
}

这样做还是有好处的,首先看t2,它放完值后可以做别的事,而别的线程不用等待。但是如果你用join的话,只能等待它做完所有事情;而且,等待结果的变量不用设置成全局的,而是局部的。


超时版的get

join的源码一模一样。。。。

public Object get(long timeout){
    synchronized (this){
        long start = System.currentTimeMillis();
        long passedTime = 0;
        while (response == null){
            long waitTime = timeout - passedTime;
            // 经历的时间超过了最大等待时间
            if(waitTime <= 0){
                break;
            }
            try {
                // 不能是timeout,小心虚假唤醒
                this.wait(waitTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            passedTime = System.currentTimeMillis() - start;
        }
        return response;
    }
}