wait/notify

wait/notify

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

@Slf4j
public class Test1_4_2 {

    public static void main(String[] args) throws InterruptedException {
        A1 a1 = new A1();

        Thread t1 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                log.info("executing1");
                a1.f1();
            }
        });
        Thread t11 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                log.info("executing11");
                a1.f1();
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                log.info("executing2");
                a1.f2();
            }
        });

        t1.start();
        t11.start();
        Thread.sleep(50);
        t2.start();
    }

}

@Slf4j
class A1 {
    private Object obj1 = new Object();

    public void f1() throws InterruptedException {
        // 1)使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
        synchronized (obj1) {
            log.info("f1");
            // 2)调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
            // 5)从wait()方法返回的前提是获得了调用对象的锁。
            obj1.wait();
            // obj1.wait(1000);
            log.info("f1-2");
        }
    }

    public void f2() throws InterruptedException {
        // 1)使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
        synchronized (obj1) {
            log.info("f2");
            // 4)notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。
            // obj1.notify();
            obj1.notifyAll();
            log.info("f2-2");
            // 3)notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
            Thread.sleep(2000);
            log.info("f2-3");
        }
    }
}

上述例子主要说明了调用wait()、notify()以及notifyAll()时需要注意的细节,如下。

1)使用wait()、notify()和notifyAll()时需要先对调用对象加锁。

2)调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。

3)notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。

4)notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。

5)从wait()方法返回的前提是获得了调用对象的锁。

从上述细节中可以看到,等待/通知机制依托于同步机制,其目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量做出的修改。

 

为什么必须和synchronized一起使用

在 Java 里面,wait()和 notify()是 Object 的成员函数, 是基础中的基础。为什么 Java 要把wait()和 notify()放在如此 基础的类里面,而不是作为像 Thread 一类的成员函数,或者其他类 的成员函数呢?

在回答这个问题之前,先要回答为什么wait()和notify()必 须和synchronized一起使用?

上面代码的例子中 线程A调用f1(),线程B调用f2()。答案 已经很明显:两个线程之间要通信,对于同一个对象来说,一个线程 调用该对象的wait(),另一个线程调用该对象的notify(),该对象本身就需要同步!所以,在调用wait()、notify()之前,要先 通过synchronized关键字同步给对象,也就是给该对象加锁。

synchronized关键字可以加在任何对象的成员函 数上面,任何对象都可能成为锁。那么,wait()和notify()要同 样如此普及,也只能放在Object里面了。

 

为什么wait()的时候必须释放锁

当线程A进入synchronized(obj1)中之后,也就是对obj1上了 锁。此时,调用wait()进入阻塞状态,一直不能退出synchronized 代码块;那么,线程B永远无法进入synchronized(obj1)同步块里, 永远没有机会调用notify(),岂不是死锁了?

这就涉及一个关键的问题:在wait()的内部,会先释放锁obj1,然后进入阻塞状态,之后,它被另外一个线程用notify()唤醒, 去重新拿锁!其次,wait()调用完成后,执行后面的业务逻辑代 码,然后退出synchronized同步块,再次释放锁。

wait()内部的伪代码如下:

 只有如此,才能避免上面所说的死锁问题。

 

 

 

参考: java并发编程的艺术 4.3.2 等待/通知机制

  Java并发实现原理:JDK源码剖析 1.4 wait()与notify()

 

posted @ 2020-07-25 09:07  草木物语  阅读(525)  评论(0编辑  收藏  举报