为什么所有 Java 对象都有 wait() 和 notify(),这是否会导致性能下降?

2022-09-02 05:33:53

每个Java都有方法和(以及其他变体)。我从未使用过这些,我怀疑许多其他人也没有。为什么这些是如此基本,以至于每个对象都必须拥有它们,并且拥有它们是否会对性能产生影响(大概是存储在其中的某些状态)?Objectwait()notify()

编辑以强调问题。如果我有一个有100,000个元素,那么每个都有这些方法,因为它是从.但是,所有这些似乎都不太可能必须了解管理 .List<Double>DoubleObjectList

编辑优秀和有用的答案。@Jon有一篇非常好的博客文章,使我的直觉结晶。我也完全同意@Bob_Cross,即您应该在担心之前显示性能问题。(同样作为成功语言的第n定律,如果它是性能打击,那么Sun或某人会修复它)。


答案 1

好吧,这确实意味着每个对象都必须有一个与之关联的监视器。同一监视器用于 。如果您同意能够在任何对象上进行同步的决定,则不要再添加任何每个对象状态。JVM可能会懒惰地分配实际的监视器(我知道.NET确实如此),但是必须有一些存储空间来说明哪个监视器与对象相关联。诚然,这可能是一个非常小的数量(例如3个字节),由于填充了对象开销的其余部分,它实际上不会节省任何内存 - 你必须看看每个单独的JVM如何处理内存才能确定。synchronizedwait()notify()

请注意,仅仅拥有额外的方法不会影响性能(除了非常轻微的,因为代码明显存在于某个地方)。这并不是说每个对象甚至每个类型都有自己的 和 的代码副本。根据 vtables 的工作方式,每个类型最终可能会为每个继承的方法提供一个额外的 vtable 条目 - 但这仍然只是基于每个类型,而不是基于每个对象。与实际对象本身的大部分存储相比,这基本上会迷失在噪音中。wait()notify()

就个人而言,我觉得.NET和Java都犯了一个错误,将监视器与每个对象相关联 - 我宁愿使用显式同步对象。我在一篇关于重新设计java.lang.Object/System.Object的博客文章中写了更多关于此的文章。


答案 2

为什么这些是如此基本,以至于每个对象都必须拥有它们,并且拥有它们是否会对性能产生影响(大概是存储在其中的某些状态)?

tl;dr:它们是线程安全方法,相对于其价值而言,它们的成本很小。

这些方法支持的基本现实是:

  1. Java始终是多线程的。示例:查看使用 jconsole 或 jvisualvm 的进程使用的线程列表。
  2. 正确性比“性能”更重要。当我给项目评分时(很多年前),我曾经不得不解释“很快得到错误的答案仍然是错误的”。

从根本上说,这些方法提供了一些钩子来管理同步中使用的每个对象监视器。具体来说,如果我有一个特定的方法,我可以用来产生那个监视器(例如,如果我需要另一种方法来完成计算,然后才能继续)。在这种情况下,这将允许另一个被阻止的方法等待该监视器继续。synchronized(objectWithMonitor)objectWithMonitor.wait()

另一方面,我可以使用让等待监视器的线程知道我将很快放弃监视器。但是,在我离开同步块之前,它们实际上无法继续。objectWithMonitor.notifyAll()

对于特定示例(例如,长双打列表),您可能会担心监视机制的性能或内存受到影响,以下是您应该考虑的一些要点:

  1. 首先,证明这一点。如果你认为核心Java机制(如多线程正确性)有重大影响,那么你的直觉很有可能是错误的。首先衡量影响。如果情况很严重,并且您知道您永远不需要在单个 Double 上进行同步,请考虑改用 Doubles。
  2. 如果你不确定你,你的同事,未来的维护程序员(一年后可能是你自己)等,永远不会需要对你的数据进行精细的粒度访问,那么很有可能把这些监视器拿走只会让你的代码不那么灵活和可维护。

跟进以响应有关每个对象与显式监视对象的问题:

简短的回答:@JonSkeet:是的,移除显示器会产生问题:它会产生摩擦。保持这些监视器提醒我们,这始终是一个多线程系统。Object

内置对象监视器并不复杂,但它们是:易于解释;以可预测的方式工作;并且目的明确。 是一个明确的意图声明。如果我们强制新手编码人员以独占方式使用并发包,则会引入摩擦。该软件包中包含哪些内容?什么是信号量?分叉连接?synchronized(this)

新手编码人员可以使用对象监视器编写体面的模型-视图-控制器代码。,并且可用于实现朴素(在简单,可访问但可能不是前沿性能的意义上)线程安全。规范的例子是其中一个双精度值(由OP提出),它可以让一个线程设置一个值,而AWT线程获取值以将其放在JLabel上。在这种情况下,没有充分的理由创建一个显式的附加对象只是为了有一个外部监视器。synchronizedwaitnotifyAll

在稍微高一点的复杂性下,这些相同的方法作为外部监视方法很有用。在上面的例子中,我明确地做到了这一点(参见上面的objectWithMonitor片段)。同样,这些方法对于将相对简单的线程安全性放在一起非常方便。

如果你想变得更加复杂,我认为你应该认真考虑阅读Java并发实践(如果你还没有的话)。读和写锁非常强大,而不会增加太多额外的复杂性。

重点:使用基本的同步方法,您可以利用具有线程安全性的现代多核处理器所支持的大部分性能,而无需大量开销。