Почему у всех объектов Java есть wait() и notify(), и это приводит к поражению производительности?
Каждая Java Object
имеет методы wait()
и notify()
(и дополнительные варианты). Я никогда не использовал их, и я подозреваю, что многие другие нет. Почему они настолько фундаментальны, что каждый объект должен иметь их, и есть ли успех при их использовании (предположительно, какое-то состояние хранится в них)?
EDIT, чтобы подчеркнуть этот вопрос. Если у меня есть List<Double>
с 100 000 элементов, то каждый Double
имеет эти методы, поскольку он расширяется от Object
. Но маловероятно, что все они должны знать о потоках, которые управляют List
.
ИЗМЕНИТЬ отличные и полезные ответы. @Jon имеет очень хорошее сообщение в блоге, которое кристаллизовало мои чувства кишки. Я также полностью согласен с @Bob_Cross, что вы должны показать проблему с производительностью, прежде чем беспокоиться об этом. (Также как n-й закон успешных языков, если бы это был удар производительности, то Sun или кто-то его исправил).
Ответы
Ответ 1
Ну, это означает, что каждый объект должен иметь связанный с ним монитор. Один и тот же монитор используется для synchronized
. Если вы согласны с решением о возможности синхронизации на любом объекте, то wait()
и notify()
больше не добавляются для каждого объекта. JVM может выделять фактический монитор лениво (я знаю,.NET делает), но должно быть какое-то пространство для хранения, чтобы сказать, какой монитор связан с объектом. По общему признанию, возможно, что это очень маленькая сумма (например, 3 байта), которая на самом деле не сохранит какую-либо память из-за заполнения остальной части служебных данных объекта - вам нужно будет посмотреть, как каждая отдельная JVM обрабатывает память, чтобы сказать наверняка.
Обратите внимание, что просто наличие дополнительных методов не влияет на производительность (кроме очень незначительно из-за того, что код явно присутствует где-то). Это не похоже на то, что каждый объект или даже каждый тип имеет свою собственную копию кода для wait()
и notify()
. В зависимости от того, как работают vtables, каждый тип может содержать дополнительную запись vtable для каждого унаследованного метода - но это все еще только для каждого типа, а не для каждого объекта. Это в основном будет затеряться в шуме по сравнению с основной частью хранилища, которая предназначена для самих реальных объектов.
Лично я чувствую, что и .NET, и Java допустили ошибку, связав монитор с каждым объектом - вместо этого я бы предпочел иметь явные объекты синхронизации. Я написал немного больше об этом в сообщении о перепроектировании java.lang.Object/System.Object.
Ответ 2
Почему они настолько фундаментальны, что каждый объект должен иметь их и там была поражена производительность (предположительно, какое-то состояние сохраняется в их)?
tl; dr: Они являются методами защиты от потоков, и они имеют небольшие затраты по сравнению с их значением.
Основные реалии, которые эти методы поддерживают:
- Java всегда многопоточна. Пример: просмотрите список потоков, используемых процессом, используя jconsole или jvisualvm некоторое время.
- Правильность важна, чем "производительность". Когда я оценивал проекты (много лет назад), мне приходилось объяснять, что "получить неправильный ответ очень быстро все еще не так".
В принципе, эти методы предоставляют некоторые из перехватов для управления мониторами на основе объектов, которые используются в синхронизации. В частности, если у меня есть synchronized(objectWithMonitor)
в определенном методе, я могу использовать objectWithMonitor.wait()
для вывода этого монитора (например, если мне нужен другой метод для завершения вычисления, прежде чем я смогу продолжить). В этом случае это позволит заблокировать еще один метод, ожидающий завершения этого монитора.
С другой стороны, я могу использовать objectWithMonitor.notifyAll()
, чтобы потоки, ожидающие монитора, знали, что скоро я откажусь от монитора. Они фактически не могут действовать, пока я не покину синхронизированный блок.
В отношении конкретных примеров (например, длинных списков парных разрядов), где вы можете беспокоиться о том, что производительность или память влияют на механизм мониторинга, вот некоторые моменты, которые вы, вероятно, должны учитывать:
- Сначала докажите это. Если вы считаете, что есть существенное влияние на основной Java-механизм, такой как многопоточная правильность, есть отличная вероятность, что ваша интуиция ложна. Сначала измерьте воздействие. Если это серьезно, и вы знаете, что вам никогда не понадобится синхронизация на отдельном Double, рассмотрите вместо этого использование двойников.
- Если вы не уверены, что вы, ваш сотрудник, будущий кодер обслуживания (который может быть самим собой через год) и т.д., никогда не понадобятся тонкой гранулярности вашего доступа к вашим данным, там отличная вероятность того, что снятие этих мониторов сделает ваш код менее гибким и удобным.
Последующее наблюдение в ответ на вопрос о объектах с явным монитором per-Object:
Короткий ответ: @JonSkeet: да, удаление мониторов создаст проблемы: это создаст трение. Сохранение этих мониторов в Object
напоминает нам, что это всегда многопоточная система.
Встроенные мониторы объектов не сложны, но они: легко объясняются; работать предсказуемым образом; и ясны в их назначении. synchronized(this)
- это четкое выражение о намерениях. Если мы вынуждаем начинающих кодеров использовать пакет concurrency исключительно, мы вводим трение. Что в этом пакете? Что такое семафор? Вилочных присоединиться?
Новичок-кодер может использовать объектные мониторы для написания приличного кода контроллера модели. synchronized
, wait
и notifyAll
могут использоваться для реализации наивной (в смысле простой, доступной, но, возможно, неэффективной) эффективности потоков. Канонический пример - это один из этих парных разрядов (по определению OP), который может иметь один поток, задающий значение, в то время как поток AWT получает значение, чтобы поместить его в JLabel. В этом случае нет веских оснований для создания явного дополнительного объекта только для внешнего монитора.
При несколько более высоком уровне сложности эти же методы полезны в качестве метода внешнего мониторинга. В приведенном выше примере я явно сделал это (см. Фрагменты objectWithMonitor выше). Опять же, эти методы действительно удобны для создания относительно простой безопасности потоков.
Если вы хотите быть еще более сложным, я думаю, вам следует серьезно подумать о том, чтобы читать Java concurrency In Practice (если вы еще нет). Чтение и запись блокировок очень мощные, не добавляя слишком много дополнительной сложности.
Punchline: Используя основные методы синхронизации, вы можете использовать большую часть производительности, поддерживаемую современными многоядерными процессорами с безопасностью потоков и без больших накладных расходов.
Ответ 3
Все объекты в Java имеют связанные с ними наблюдатели. Примитивы синхронизации полезны практически во всем многопоточном коде, и его семантически очень приятно синхронизировать объекты (объекты), к которым вы обращаетесь, а не от отдельных объектов "Монитор".
Java может выделять Мониторы, связанные с объектами по мере необходимости - как это делает .NET - и в любом случае фактические накладные расходы для простого выделения (но не использования) блокировки были бы довольно маленькими.
Вкратце: очень удобно хранить объекты со своими битами поддержки потока, а также очень мало влияет на производительность.
Ответ 4
Эти методы предназначены для взаимодействия между потоками.
Отметьте эту статью по теме.
Правила для этих методов, взятые из этой статьи:
- wait() сообщает вызывающему потоку отказаться от монитора и спать до тех пор, пока не будет нить входит в тот же монитор и вызывает notify().
- notify() просыпает первый поток, который вызывает wait() на одном и том же объекте.
- notifyAll() просыпает все потоки, которые вызывают wait() на одном и том же объекте. сначала будет выполняться поток с наивысшим приоритетом.
Надеюсь, что это поможет...