notifyAll()

2024-06-23 15:38
文章标签 notifyall

本文主要是介绍notifyAll(),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一. 序

当做多线程同步时,等待-通知机制是我们比较常用的一种选择,而 Java 中,等待-通知机制有多种实现,我们接触最早也是最熟悉的,应该就是 Java 语言内置的 synchronized 配合 wait()/notify()/notifyAll() 这三个方法来实现。

如何利用 synchronized 实现等待-通知机制,我想大家都比较熟悉,就无需多说了。notify() 和 notifyAll() 都可以唤醒等待的线程,但是应该使用 notify() 还是 notifyAll() 就比较有争议了。

一种比较流行的说法就是,除非深思熟虑,否者尽量使用 notifyAll()

我们今天就这个问题,来讨论一下这两个方法如何选择。

二. 等待-通知机制

2.1 什么是等待通知机制

在此之前,先来聊聊什么是等待-通知机制,以及它能解决什么问题?

在使用并发编程时,利用多线程来提高任务的执行效率,但是每个线程在执行时,都有一些先决条件需要被满足。例如生产者消费者模式下,消费者线程能够执行的先决条件,就是生产者产生了一个待消费的数据。

那么如果线程要求的条件,不满足时,循环等待是一种方案,循环间隔一段时间,再重新尝试验证条件是否满足,但是这样的循环等待,在某些场景下,可能会循环很多次,导致大量消耗 CPU 资源。

更好的方案,则是等待-通知机制。当线程要求的条件不满足时,主动进入等待状态,等线程等待的条件,再次被满足后,通知这个等待的线程重新执行。这就解决了 CPU 资源,因为循环而导致消耗的问题。

对标到 Synchronized 中,被 Synchronized 标记的代码块,被称为临界区,在同一时刻,只有一个线程能过获取 Synchronized 的互斥锁,进入临界区执行。

线程处于临界区时,一旦发现执行条件不满足时,则可以调用 wait() 或者 wait(long) 方法,进入等待,例如消费者发现没有更多需要处理的数据,此时就调用 wait() 方法,进入等待,等待生产者产生一条新的待消费数据。接下来如果生产者线程,产生了一个新的数据后,就需要唤醒之前等待的消费者线程,去处理这条数据。这里的唤醒操作,就是调用 notify() 或者 notifyAll() 方法。

可以看到,notify() 和 notifyAll() 的作用有些类似,都是去唤醒等待中的线程。但是也正如他们方法名所描述的,notify() 会"随机"唤醒一个等待线程,而 notifyAll() 会尝试唤醒所有的等待中的线程。

注意这里的唤醒,并不是真的唤醒去执行,实际上只是让处于等待的线程,有重新获取锁的争抢权,也就是说,哪怕此时有一百个线程处于等待状态,此时调用 notifyAll() 也只会有一个线程获取到锁,允许进入临界区执行。

这在底层中,其实是利用了两个等待队列来实现的,分别是入口队列(EntrySet)和等待队列(WaitSet)。

为什么说“除非深思熟虑,尽量使用 notifyAll()”?

 

被 Synchronized 阻塞等待的线程,会进入入口队列,而当条件不满足时,主动调用 wait() 方法进入等待的线程,则会进入等待队列。在等待队列中的线程,如果不被唤醒,则永远没有锁的争抢权,无法获取锁也就无法被执行。

2.2 为什么说尽量使用 notifyAll?

终于进入主题了,就前面的描述,看似应该是使用 notify() 更好一些,因为即便我们通知了等待队列中,所有的线程,但同一时刻,也只有一个线程可以获取互斥锁,进入临界区执行,这么看来 notify() 会更高效一些。

但是这里埋下来一个风险,就是只使用 notify() 可能会导致某些线程,一直处于等待队列中,而永远不会被唤醒并获得执行权

理想情况下,一次等待(wait)对应一次通知(notify),是非常完美的,但是实际场景下,可能做不到。

例如,在多生产者消费者模式下,待处理的数据队列只有一条数据了,理想场景下,消费者在处理掉一条数据后,理论上应该唤醒生产着再生产一条新的待消费数据。可是 notify() 是随机唤醒,也就是它可能会唤醒一个消费者线程,这个消费者线程,发现没有待处理的数据,此时条件不满足,又主动进入等待队列。

也正是因为如此,在并发编程中有个范式模板:

 synchronized(this){while(条件不满足) {wait();}// ...}

这段代码,大家应该很熟悉,notify() 只能保证唤醒一个线程,但是不保证线程执行的时候,曾经的等待条件已经被满足了。为了保证可靠性,此处使用循环检测的方式,只有必要条件满足时,才继续执行。

正是因为 notify() 随机唤醒的特点,导致在多条件的情况下,会导致某些线程永远不会被通知到。稳妥的方式,是使用 notifyAll(),让等待中的线程,都有一次再执行的权利。

这也就是为什么说,除非深思熟虑,否则尽量使用 notifyAll()

2.3 什么是深思熟虑?

使用 notifyAll() 主要是为了稳定,减少程序的复杂度,我们程序员,解决的是一系列工程问题,虽然有时候需要挑战一些性能的极限,但是大多数时候应该是以稳定且易读易维护为出发点实现功能。

但是在程序的世界中,永远没有绝对的银丹。不带场景去分析问题,都是耍流氓。

前面说 “除非深思熟虑”,那什么场景下才可以用到深思熟虑?

其实只要满足三个条件即可:

  1. 线程进入等待队列的条件相同。
  2. 在满足条件时,所有线程执行的逻辑相同。
  3. 当一个线程执行完(无论是否异常),必定唤醒一个线程。

怎么理解呢?

当所有的线程,执行相同的业务逻辑,那么触发等待的条件也必定相同,只要保证一个线程在退出的时候,必定调用 notify() 唤醒一个线程,那么它就可以只使用 notify() 方法。

生产者消费者模式,必定是不满足的,它把线程分为两个性质。但是这种情况下,使用 Lock&Condition 会更适合。

三. 小结时刻

到这里,我相信大家已经理解了 notify 和 notifyAll 的区别了,在多数场景下,推荐使用 notifyAll() 的主要原因就是因为稳定,毕竟我们大多数时候解决的是工程问题。

这篇关于notifyAll()的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1087572

相关文章

java join sleep wait notify notifyAll

sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。  通过调用sleep使任务进入休眠状态,在这种情况下,任务在指定的时间内不会运行。 调用sleep的时候锁并没有被释放。 休眠  Java SE5引入了更加显示的sleep()版本作为TimeUnit类的一部分,这个方法允许你

java线程 yield,sleep,join,synchronized wait notify notifyAll,ReentrantLock lock condition, 生产者消费者

yield,sleep,join yield,join,sleep,join是Thread中的方法,不需要 在synchronized 代码块中调用,和synchronized 没关系,也不会释放锁。 Thread.sleep(100);Thread.yield();Thread t;t.join(); (1)yield()不一定保证让出cpu yield()只是使当前线程重新回

【线程间通信】sleep/wait/notify/notifyAll作用及使用

文章目录 背景wait代码及演示wait、notify代码及演示notifyAll()代码及演示 背景 在多线程开发过程中,由于线程最大的问题就是抢占式执行,随机调度, 因此线程之间执行的先后顺序难以预知。 但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序,让线程主动阻塞,主动放弃CPU,从而控制线程之间的执行顺序 ,去协调完成工作,怎么去控制线程之间的执行

java notify和notifyAll的区别

java中的notify和notifyAll有什么区别? 关注问题 写回答 Java Java 编程 Java EE Java 程序员 java中的notify和notifyAll有什么区别? 关注者 56 被浏览 6466 ​添

Java线程_Notify,NotifyAll,Wait方法

怎么办,好几天没写博客了,心里感觉不踏实。水一篇吧,水水更健康。在看Java线程这本书的电子版,看到第四章notify、wait、notifyAll这几个方法,前面的notify和wait还好,比较简单,就是需要注意的是notify和wait方法必须放在同步代码中。可是为什么要这样呢?原因是如果不将notify和wait放到同步代码中的话,他们之间可能会产生竞态条件。现设有两个线程,

线程中notify()和notifyAll()异同

相同:都是Object对象用于通知处在等待该对象的线程的方法 不同:notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,它们就会去竞争。notify则文明得多他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁此时如果

notify、notifyAll、wait思考

1、在java中,每个对象都有两个池,锁(monitor)池和等待池,每个对象都能够被作为“监视器monitor”——指一个拥有一个独占锁       wait()/notify()方法定义在Object类中。如果线程要调用对象的wait()方法,必须首先获得该对象的监视器锁,调用wait()之后,当前线程又立即释放掉锁,线程随后进入WAIT_SET(等待池)中。如果线程要调用对象的noti

Java并发编程系列之八:wait()、notify()和notifyAll()

一个线程修改一个对象的值,而另一个线程则感知到了变化,然后进行相应的操作,这就是wait()、notify()和notifyAll()方法的本质。具体体现到方法上则是这样的:一个线程A调用了对象obj的wait方法进入到等待状态,而另一个线程调用了对象obj的notify()或者notifyAll()方法,线程A收到通知后从对象obj的wait方法返回,继续执行后面的操作。 可以看到以上两个线程

JavaEE之线程 (6)—— 等待方法wait 和唤醒方法notify、notifyAll

线程间等待与唤醒机制  由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知。但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。比如  球场上的每个运动员都是独立的“执行流” ,可以认为是一个 ”线程“。而完成一个具体的进攻得分动作,则需要多个运动员相互配合,按照一定的顺序执行一定的动作,线程1 先 “传球” ,线程2 才能 "扣篮“ 完成上述所提的协调工作

【吊打面试官系列】Java高并发篇 - 为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?

大家好,我是锋哥。今天分享关于 【为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?】面试题,希望对大家有帮助; 为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里? Java 的每个对象中都有一个锁(monitor,也可以成为监视器) 并且 wait(),notify(