当做多线程同步时,等待-通知机制是我们比较常用的一种选择,而 Java 中,等待-通知机制有多种实现,我们接触最早也是最熟悉的,应该就是 Java 语言内置的 synchronized 配合 wait()、notify()、notifyAll() 这三个方法来实现。
如何利用 synchronized 实现等待-通知机制,我想大家都比较熟悉,就无需多说了。notify()
和 notifyAll()
都可以唤醒等待的线程,但是应该使用 notify()
还是 notifyAll()
就比较有争议了。
一种比较流行的说法就是,除非深思熟虑,否则尽量使用 notifyAll()。
我们今天就这个问题,来讨论一下这两个方法如何选择。
2.1 什么是等待通知机制
在此之前,先来聊聊什么是等待-通知机制,以及它能解决什么问题?
在使用并发编程时,利用多线程来提高任务的执行效率,但是每个线程在执行时,都有一些先决条件需要被满足。例如生产者消费者模式下,消费者线程能够执行的先决条件,就是生产者产生了一个待消费的数据。
那么如果线程要求的条件,不满足时,循环等待是一种方案,循环间隔一段时间,再重新尝试验证条件是否满足,但是这样的循环等待,在某些场景下,可能会循环很多次,导致大量消耗 CPU 资源。
更好的方案,则是等待-通知机制。当线程要求的条件不满足时,主动进入等待状态,等线程等待的条件,再次被满足后,通知这个等待的线程重新执行。这就解决了 CPU 资源,因为循环而导致消耗的问题。
对标到 Synchronized 中,被 Synchronized 标记的代码块,被称为临界区,在同一时刻,只有一个线程能过获取 Synchronized 的互斥锁,进入临界区执行。
线程处于临界区时,一旦发现执行条件不满足时,则可以调用 wait()
或者wait(time_out)
方法,进入等待,例如消费者发现没有更多需要处理的数据,此时就调用 wait()
方法,进入等待,等待生产者产生一条新的待消费数据。接下来如果生产者线程,产生了一个新的数据后,就需要唤醒之前等待的消费者线程,去处理这条数据。这里的唤醒操作,就是调用 notify()
或者 notifyAll()
方法。
可以看到,notify()
和 notifyAll()
的作用是一致的,都是去唤醒等待中的线程。但是也正如他们方法名所描述的,notify()
会"随机"唤醒一个等待线程,而notifyAll()
会尝试唤醒所有的等待中的线程。
注意这里的唤醒,并不是真的唤醒去执行,实际上只是让处于等待的线程,有重新获取锁的争抢权,也就是说,哪怕此时有一百个线程处于等待状态,此时调用notifyAll()
也只会有一个线程获取到锁,允许进入临界区执行。
这在底层中,其实是利用了两个等待队列来实现的,分别是入口队列(EntrySet)和等待队列(WaitSet)。
被 Synchronized 阻塞等待的线程,会进入入口队列,而当条件不满足时,主动调用 wait()
方法进入等待的线程,则会进入等待队列。
在等待队列中的线程,如果不被唤醒,则永远没有锁的争抢权,无法获取锁也就无法被执行。
2.2 为什么说尽量使用 notifyAll?
终于进入主题了,就前面的描述,看似应该是使用 notify()
更好一些。因为即便我们通知了等待队列中,所有的线程,但同一时刻,也只有一个线程可以获取互斥锁,进入临界区执行,这么看来 notify()
会更高效一些。
但是这里埋下来一个风险,就是只使用 notify() 可能会导致某些线程,一直处于等待队列中,而永远不会被唤醒并获得执行权。
理想情况下,一次等待(wait)对应一次通知(notify),是非常完美的,但是实际业务场景下,可能做不到。
举个例子,在多生产者消费者模式下,待处理的数据队列只有一条数据了,理想场景下,消费者在处理掉一条数据后,理论上应该唤醒生产者再生产一条新的待消费数据。可是 notify()
是随机唤醒,也就是它可能会唤醒一个消费者线程,这个消费者线程,发现没有待处理的数据,此时条件不满足,又主动进入等待队列。
也正是因为如此,在并发编程中有个范式模板:
synchronized(this){ while(条件不满足) { wait(); } // … }
这段代码,大家应该很熟悉,notify()
只能保证唤醒一个线程,但是不保证线程执行的时候,曾经的等待条件已经被满足了。为了保证可靠性,此处使用循环检测的方式,只有必要条件满足时,才继续执行。
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618165277)
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!