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

pet*_*ust 20 java notify wait

每个Java Object都有方法wait()notify()(以及其他变体).我从来没有使用过这些,我怀疑很多其他人都没有.为什么这些如此基本以至于每个对象都必须拥有它们并且在使用它们时是否会有性能损失(可能是某些状态存储在它们中)?

编辑以强调这个问题.如果我有List<Double>100,000个元素,那么每个Double都有这些方法,因为它是扩展的Object.但似乎所有这些都不太可能知道管理它的线程List.

编辑优秀而有用的答案.@Jon有一篇非常好的博文,结合了我的直觉.我也完全赞同@Bob_Cross你应该在担心之前表现出性能问题.(同样作为成功语言的第n定律,如果它是一个性能命中,那么Sun或某人会修复它).

Jon*_*eet 34

那么,它确实意味着每个对象有可能有一个与之关联的监视器.使用相同的监视器synchronized.如果你有决定不服的,以便能够在任何对象上同步,然后wait()notify()不添加任何更多的每个对象的状态.JVM可以懒惰地分配实际的监视器(我知道.NET会这样做)但是必须有一些存储空间可以说明哪个监视器与该对象相关联.不可否认,这可能是一个非常小的数量(例如3个字节),由于填充了其余的对象开销而无法实际保存任何内存 - 您必须查看每个单独的JVM如何处理内存当然.

请注意,只是有额外的方法不会影响性能(除了非常轻微,因为代码明显存在于某处).它不像每个对象,甚至每个类型都有自己的代码副本wait()notify().根据vtable的工作方式,每种类型最终可能会为每个继承的方法添加一个额外的vtable条目 - 但这仍然只是基于每个类型,而不是基于每个对象.与用于实际物体本身的大部分存储相比,这基本上会在噪声中丢失.

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


Bob*_*oss 12

为什么这些如此基本以至于每个对象都必须拥有它们并且在使用它们时是否会有性能损失(可能是某些状态存储在它们中)?

tl; dr:它们是线程安全方法,它们的价值相对较小.

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

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

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

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

对于您可能担心监视机制存在性能或内存损坏的特定示例(例如,长双打列表),您可能会考虑以下几点:

  1. 首先,证明一下.如果您认为核心Java机制(如多线程正确性)会产生重大影响,那么您的直觉很可能是错误的.首先测量影响.如果它是严重的并且您知道您永远不需要在单个Double上同步,请考虑使用双打.
  2. 如果你不能确定你,你的同事,将来的维护编码器(谁可能是自己一年后)等,都将永远永远需要的theaded访问您的数据的粒度细,有一个极好的机会,将这些显示器拿走只会降低您的代码的灵活性和可维护性.

关于per-Object与显式监视器对象的问题的后续跟进:

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

内置的对象监视器并不复杂,但它们是:易于解释; 以可预测的方式工作; 并明确其目的. synchronized(this)是明确的意图陈述.如果我们强制新手编码器专门使用并发包,我们会引入摩擦.那个套餐里面有什么?什么是信号量?的fork-join?

新手编码器可以使用Object监视器来编写体面的模型 - 视图 - 控制器代码. synchronized,wait并且notifyAll可用于实现天真(在简单,可访问但可能不是前沿性能的意义上)线程安全.规范示例将是这些双打中的一个(由OP提出),其可以使一个Thread设置值,而AWT线程获取将其放置在JLabel上的值.在这种情况下,没有充分的理由创建一个显式的附加对象只是为了拥有一个外部监视器.

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

如果你想更加成熟,我认为你应该认真考虑阅读Java Concurrency In Practice(如果你还没有).读写锁非常强大,不会增加太多额外的复杂性.

Punchline:使用基本的同步方法,您可以利用现代多核处理器实现的大部分性能,并且具有线程安全性,而且不会产生大量开销.