20

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

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

编辑优秀和有用的答案。@Jon 有一篇非常好的博客文章,它具体化了我的直觉。我也完全同意@Bob_Cross 的观点,即您应该在担心之前显示性能问题。(也是成功语言的第 n 条定律,如果它受到性能影响,那么 Sun 或其他人会修复它)。

4

4 回答 4

34

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

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

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

于 2009-10-30T08:13:37.003 回答
12

为什么这些如此重要以至于每个对象都必须拥有它们,并且拥有它们是否会影响性能(可能某些状态存储在它们中)?

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

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

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

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

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

对于您可能担心监控机制会影响性能或内存的特定示例(例如,长的 Doubles 列表),您可能应该考虑以下几点:

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

针对每个对象与显式监控对象的问题的后续行动:

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

内置对象监视器并不复杂,但它们: 易于解释;以可预测的方式工作;并且他们的目的很明确。 synchronized(this)是明确的意向声明。如果我们强制新手编码员专门使用并发包,我们就会引入摩擦。那个包裹里有什么?什么是信号量?分叉加入?

新手程序员可以使用对象监视器来编写体面的模型-视图-控制器代码。 synchronizedwait并且notifyAll可以用来实现简单的(在简单、可访问但可能不是前沿性能的意义上)线程安全。典型示例是这些 Doubles 之一(由 OP 提出),它可以让一个 Thread 设置一个值,而 AWT 线程获取该值以将其放在 JLabel 上。在这种情况下,没有充分的理由仅仅为了拥有一个外部监视器而创建一个显式的附加对象。

在复杂程度稍高的情况下,这些相同的方法可用作外部监控方法。在上面的示例中,我明确地这样做了(参见上面的 objectWithMonitor 片段)。同样,这些方法对于组合相对简单的线程安全非常方便。

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

Punchline:使用基本的同步方法,您可以利用现代多核处理器所提供的大部分性能,具有线程安全且没有大量开销。

于 2009-10-30T08:58:45.463 回答
2

Java 中的所有对象都有与之关联的监视器。同步原语在几乎所有多线程代码中都很有用,并且它在语义上非常适合在您正在访问的对象上同步,而不是在单独的“监视器”对象上同步。

Java 可以根据需要分配与对象关联的监视器——就像 .NET 所做的那样——在任何情况下,简单地分配(但不使用)锁的实际开销都非常小。

简而言之:使用线程安全支持位存储对象非常方便,并且对性能的影响很小。

于 2009-10-30T08:16:38.683 回答
1

这些方法都是用来实现线程间通信的。

检查有关该主题的这篇文章

这些方法的规则,取自那篇文章:

  • wait() 告诉调用线程放弃监视器并进入睡眠状态,直到其他线程进入同一个监视器并调用 notify()。
  • notify() 唤醒对同一对象调用 wait() 的第一个线程。
  • notifyAll() 唤醒所有在同一个对象上调用 wait() 的线程。最高优先级的线程将首先运行。

希望这可以帮助...

于 2009-10-30T08:12:17.210 回答