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

发布于 2024-08-09 14:58:03 字数 517 浏览 10 评论 0原文

每个 Java Object 都有方法 wait()notify() (以及其他变体)。我从未使用过这些,我怀疑许多其他人也没有使用过。为什么这些如此重要以至于每个对象都必须拥有它们,并且拥有它们是否会对性能造成影响(大概其中存储了一些状态)?

编辑以强调问题。如果我有一个包含 100,000 个元素的 List,那么每个 Double 都有这些方法,因为它是从 Object 扩展而来的。但似乎所有这些都必须知道管理 List 的线程。

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

Every Java Object has the methods wait() and notify() (and additional variants). I have never used these and I suspect many others haven't. Why are these so fundamental that every object has to have them and is there a performance hit in having them (presumably some state is stored in them)?

EDIT to emphasize the question. If I have a List<Double> with 100,000 elements then every Double has these methods as it is extended from Object. But it seems unlikely that all of these have to know about the threads that manage the List.

EDIT excellent and useful answers. @Jon has a very good blog post which crystallised my gut feelings. I also agree completely with @Bob_Cross that you should show a performance problem before worrying about it. (Also as the nth law of successful languages if it had been a performance hit then Sun or someone would have fixed it).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

初熏 2024-08-16 14:58:03

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

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

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

Well, it does mean that every object has to potentially have a monitor associated with it. The same monitor is used for synchronized. If you agree with the decision to be able to synchronize on any object, then wait() and notify() don't add any more per-object state. The JVM may allocate the actual monitor lazily (I know .NET does) but there has to be some storage space available to say which monitor is associated with the object. Admittedly it's possible that this is a very small amount (e.g. 3 bytes) which wouldn't actually save any memory anyway due to padding of the rest of the object overhead - you'd have to look at how each individual JVM handled memory to say for sure.

Note that just having extra methods doesn't affect performance (other than very slightly due to the code obvious being present somewhere). It's not like each object or even each type has its own copy of the code for wait() and notify(). Depending on how the vtables work, each type may end up with an extra vtable entry for each inherited method - but that's still only on a per type basis, not a per object basis. That's basically going to get lost in the noise compared with the bulk of the storage which is for the actual objects themselves.

Personally, I feel that both .NET and Java made a mistake by associating a monitor with every object - I'd rather have explicit synchronization objects instead. I wrote a bit more on this in a blog post about redesigning java.lang.Object/System.Object.

紅太極 2024-08-16 14:58:03

为什么这些如此重要
每个对象都必须有它们并且是
拥有它们会对性能产生影响
(大概某些状态存储在
他们)?

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

这些方法支持的是:

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

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

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

对于您可能担心监控机制会出现性能或内存问题的特定示例(例如,长双精度列表),您可能应该考虑以下几点:

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

针对每个对象与显式监视器对象问题的后续回答:

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

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

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

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

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

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

Why are these so fundamental that
every object has to have them and is
there a performance hit in having them
(presumably some state is stored in
them)?

tl;dr: They are thread-safety methods and they have small costs relative to their value.

The fundamental realities that these methods support are that:

  1. Java is always multi-threaded. Example: check out the list of Threads used by a process using jconsole or jvisualvm some time.
  2. Correctness is more important than "performance." When I was grading projects (many years ago), I used to have to explain "getting to the wrong answer really fast is still wrong."

Fundamentally, these methods provide some of the hooks to manage per-Object monitors used in synchronization. Specifically, if I have synchronized(objectWithMonitor) in a particular method, I can use objectWithMonitor.wait() to yield that monitor (e.g., if I need another method to complete a computation before I can proceed). In that case, that will allow one other method that was blocked waiting for that monitor to proceed.

On the other hand, I can use objectWithMonitor.notifyAll() to let Threads that are waiting for the monitor know that I am going to be relinquishing the monitor soon. They can't actually proceed until I leave the synchronized block, though.

With respect to specific examples (e.g., long Lists of Doubles) where you might worry that there's a performance or memory hit on the monitoring mechanism, here are some points that you should likely consider:

  1. First, prove it. If you think there is a major impact from a core Java mechanism such as multi-threaded correctness, there's an excellent chance that your intuition is false. Measure the impact first. If it's serious and you know that you'll never need to synchronize on an individual Double, consider using doubles instead.
  2. If you aren't certain that you, your co-worker, a future maintenance coder (who might be yourself a year later), etc., will never ever ever need a fine granularity of theaded access to your data, there's an excellent chance that taking these monitors away would only make your code less flexible and maintainable.

Follow-up in response to the question on per-Object vs. explicit monitor objects:

Short answer: @JonSkeet: yes, removing the monitors would create problems: it would create friction. Keeping those monitors in Object reminds us that this is always a multithreaded system.

The built-in object monitors are not sophisticated but they are: easy to explain; work in a predictable fashion; and are clear in their purpose. synchronized(this) is a clear statement of intent. If we force novice coders to use the concurrency package exclusively, we introduce friction. What's in that package? What's a semaphore? Fork-join?

A novice coder can use the Object monitors to write decent model-view-controller code. synchronized, wait and notifyAll can be used to implement naive (in the sense of simple, accessible but perhaps not bleeding-edge performance) thread-safety. The canonical example would be one of these Doubles (posited by the OP) which can have one Thread set a value while the AWT thread gets the value to put it on a JLabel. In that case, there is no good reason to create an explicit additional Object just to have an external monitor.

At a slightly higher level of complexity, these same methods are useful as an external monitoring method. In the example above, I explicitly did that (see objectWithMonitor fragments above). Again, these methods are really handy for putting together relatively simple thread safety.

If you would like to be even more sophisticated, I think you should seriously think about reading Java Concurrency In Practice (if you haven't already). Read and write locks are very powerful without adding too much additional complexity.

Punchline: Using basic synchronization methods, you can exploit a large portion of the performance enabled by modern multi-core processors with thread-safety and without a lot of overhead.

欢你一世 2024-08-16 14:58:03

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

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

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

All objects in Java have monitors associated with them. Synchronization primitives are useful in pretty much all multi-threaded code, and its semantically very nice to synchronize on the object(s) you are accessing rather than on separate "Monitor" objects.

Java may allocate the Monitors associated with the objects as needed - as .NET does - and in any case the actual overhead for simply allocating (but not using) the lock would be quite small.

In short: its really convenient to store Objects with their thread safety support bits, and there is very little performance impact.

離人涙 2024-08-16 14:58:03

这些方法都是为了实现线程间通信。

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

这些方法的规则,摘自该文章:

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

希望这有帮助...

These methods are around to implement inter-thread communication.

Check this article on the subject.

Rules for those methods, taken from that article:

  • wait( ) tells the calling thread to give up the monitor and go to sleep until some other
    thread enters the same monitor and calls notify( ).
  • notify( ) wakes up the first thread that called wait( ) on the same object.
  • notifyAll( ) wakes up all the threads that called wait( ) on the same object. The
    highest priority thread will run first.

Hope this helps...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文