等待/通知机制

什么是等待/通知机制?

多个线程之间也可以实现通信,原因就是多个线程共同访问同一个变量。但是这种通信机制不是 “等待/通知” ,两个线程完全是主动地读取一个共享变量。
简单的说,等待/通知机制就是一个【线程A】等待,一个【线程B】通知(线程A可以不用再等待了)。

场景:
一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行响应的操作,整个过程开始于一个线程,而最终执行又是另一个线程。前者是生产者,后者是消费者,这种模式隔离了“做什么”和“怎么做”,在功能层面上实现了解耦,体系结构上具备了良好的伸缩性,但是在java语言中如何实现类似的功能呢?

实现:
简单的办法是让消费者不断地循环检查是否符合预期,如下代码:
while(value!=desire){
Thread.sleep(1000);
}


上面的伪代码在条件不满足的时候就睡眠一段时间,这样做的目的是防止过快的“无效”的尝试,这种方法看似实现了所需的功能,但是却存在如下问题:
1.难以确保及时性。在睡眠时,基本上不消耗处理器资源,但是如果睡得太久,就不能及时发现条件已经改变,也就是及时性难以保证。
2.难以降低开销。如果降低了睡眠的时间,比如休眠1毫秒,这样消费者能更加迅速地发现条件变化,但是却可能消耗更多处理器资源,造成了无端的浪费。

解决:
以上两个问题看似难以解决,但是java通过内置的等待/通知机制能够很好地解决这个矛盾并实现所需的功能。
等待/通知的相关方法在任意java对象都具备,因为这些方法被定义在所有对象的超类java.lang.Object上。


相关方法:
notify():通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获得到了对象的锁。
notifyAll():通知所有等待在该对象上的线程。
wait():通知该方法在线程进入WAITING状态,只有等待另外线程的通知或者被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁。
wait(long):超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回。
wait(long,int):对于超时时间更加细粒度的控制,可以达到纳秒

如:

    public static void main(String[] arg)
    {

        Object lock=new Object();
        Thread td=new Thread(


                new Runnable() {
                    @Override
                    public void run() {
                       synchronized (lock)
                       {
                           try {
                               //通知该方法在线程进入WAITING状态
                               lock.wait();
                               System.out.println("线程开始工作,线程状态:"+ Thread.currentThread().isAlive());
                           } catch (InterruptedException e) {
                               e.printStackTrace();
                           }


                       }
                    }
                }

        );td.start();

        Thread td2=new Thread(
                new Runnable(){

                    @Override
                    public void run() {
                        synchronized(lock)
                        {
                             //通知一个在对象上等待的线程
                            lock.notify();
                            System.out.println("唤醒lock 线程状态:"+ Thread.currentThread().isAlive());
                            //通知所有等待在该对象上的线程
                            lock.notifyAll();
                            System.out.println("唤醒lock 线程状态:"+Thread.currentThread().isAlive());
                        }
                    }
                }



        );td2.start();

    }
}

结果:

唤醒lock 线程状态:true
唤醒lock 线程状态:true
线程开始工作,线程状态:true

 

等待方遵循如下原则

1.获取对象的锁

2.如果条件不满足,那么调用对象的wait()方法,被通知后任要检查条件

3.条件满足则进行对于的逻辑

通用代码 :

synchronized(对象) {

    while (条件不满足) {

        对象.wait();

    }

     

    对应的逻辑;

}

通知方遵循如下原则:

1 获得对象的锁

2 改变条件

3通知所有等待在对象上的线程

通用代码 :

synchronized(对象) {

    改变条件

    对象.notifyAll();

}

需要注意

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

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

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

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

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

 

a.为什么官方说wait() 要放在while里面?

一般在项目中,一个线程不可能无缘无故等待,总是需要在某种条件下进行等待,而且其他线程唤醒这个线程的时候,可能用的是notifyAll(),数据被其他线程消费了,这里需要在判断一下是否满足特定的条件再继续运行。

b.为什么wait()必须在同步方法/代码块中调用?

解释1:wait()本身设计的逻辑就是在释放锁进行等待,如果没有获取锁,谈何释放。

解释2:通常在wait()的方法前面都会有while语句的判断,在这两条语句中会有时间间隔,可能会破坏程序,需要加上synchronized同步代码块来保证原子操作。

c.为什么wait(), notify() 和 notifyAll()是定义在Object里面而不是在Thread里面?

因为wait()等方法都是锁级别操作,再者Java提供的锁都是对象级别的而不是线程级别的,每个对象都有锁。如果wait()方法定义在Thread类中,

posted on 2020-08-13 09:52  shumeigang  阅读(267)  评论(0)    收藏  举报

导航