Почему все объекты Java имеют функции wait () и notify (), и это приводит к снижению производительности? - PullRequest
20 голосов
/ 30 октября 2009

Каждая Java Object имеет методы wait() и notify() (и дополнительные варианты). Я никогда не использовал их, и я подозреваю, что многие другие не использовали. Почему они настолько фундаментальны, что каждый объект должен иметь их и есть ли снижение производительности при их наличии (предположительно, в них хранится некоторое состояние)?

РЕДАКТИРОВАТЬ , чтобы подчеркнуть вопрос. Если у меня есть List<Double> с 100 000 элементов, то каждый Double имеет эти методы, поскольку он расширен от Object. Но маловероятно, что все они должны знать о потоках, управляющих List.

РЕДАКТИРОВАТЬ отличные и полезные ответы. У @Jon есть очень хорошее сообщение в блоге, которое кристаллизовало мои интуитивные чувства. Я также полностью согласен с @Bob_Cross, что вы должны показать проблему с производительностью, прежде чем беспокоиться об этом. (Также в качестве n-го закона успешных языков, если бы это было ударом по производительности, то Sun или кто-то бы это исправил).

Ответы [ 4 ]

33 голосов
/ 30 октября 2009

Ну, это означает, что с каждым объектом потенциально должен быть связан монитор. Этот же монитор используется для synchronized. Если вы согласны с решением о возможности синхронизации на любом объекте, то wait() и notify() не добавляют больше состояния для каждого объекта. JVM может выделять фактический монитор лениво (я знаю .NET это делает), но должно быть некоторое пространство для хранения, чтобы сказать, какой монитор связан с объектом. По общему признанию, возможно, что это очень небольшое количество (например, 3 байта), которое на самом деле не сохранит память в любом случае из-за заполнения остальной части служебных данных объекта - вам придется посмотреть, как каждая отдельная JVM обрабатывает память, чтобы точно.

Обратите внимание, что наличие дополнительных методов не влияет на производительность (кроме очень незначительного из-за очевидного присутствия кода где-то ). Это не похоже на каждый объект или даже каждый тип имеет свою собственную копию кода для wait() и notify(). В зависимости от того, как работают vtables, каждый тип может заканчиваться дополнительной записью vtable для каждого унаследованного метода - но это все еще только для каждого типа, а не для каждого объекта. По сути, это затеряется в шуме по сравнению с объемом хранилища, предназначенного для самих реальных объектов.

Лично я чувствую, что и .NET, и Java допустили ошибку, связав монитор с каждым объектом - я бы предпочел вместо этого явные объекты синхронизации. Я написал немного больше об этом в блоге о редизайне java.lang.Object / System.Object .

12 голосов
/ 30 октября 2009

Почему это так фундаментально, что каждый объект должен иметь их и есть хит производительности в том, чтобы их (предположительно некоторое состояние хранится в их)?

tl; dr: Это методы обеспечения безопасности потоков и имеют небольшую стоимость по сравнению с их стоимостью.

Фундаментальные реальности, которые поддерживают эти методы , таковы:

  1. Java всегда многопоточная. Пример: посмотрите список потоков, используемых процессом, использующим jconsole или jvisualvm, когда-то.
  2. Правильность важнее, чем «производительность». Когда я оценивал проекты (много лет назад), мне приходилось объяснять, что «получить неправильный ответ очень быстро все еще неправильно».

По сути, эти методы предоставляют некоторые ловушки для управления мониторами для каждого объекта, используемыми при синхронизации. В частности, если у меня есть synchronized(objectWithMonitor) в конкретном методе, я могу использовать objectWithMonitor.wait() для выдачи этого монитора (например, если мне нужен другой метод для завершения вычисления, прежде чем я смогу продолжить). В этом случае это позволит еще одному заблокированному методу дождаться продолжения работы этого монитора.

С другой стороны, я могу использовать objectWithMonitor.notifyAll(), чтобы сообщить темам, ожидающим монитор, о том, что я собираюсь вскоре отказаться от монитора. Они не могут продолжать, пока я не покину синхронизированный блок.

Что касается конкретных примеров (например, длинных списков двойных чисел), в которых вы можете беспокоиться о том, что в механизм мониторинга попала производительность или память, вот некоторые моменты, которые вам, вероятно, следует учитывать:

  1. Сначала докажи это. Если вы считаете, что основной механизм Java, такой как многопоточная корректность, оказывает серьезное влияние, есть отличный шанс, что ваша интуиция ложна. Измерьте воздействие в первую очередь. Если это серьезно, и вы знаете , что вам никогда не понадобится синхронизироваться на отдельном дубле, рассмотрите возможность использования пар вместо.
  2. Если вы не уверены, что вам, вашему коллеге, будущему программисту по техническому обслуживанию (который может стать самим собой через год) и т. Д., Никогда и никогда не понадобится когда-либо точная гранулярность theaded доступ к вашим данным, есть отличный шанс, что удаление этих мониторов сделает ваш код менее гибким и обслуживаемым.

Последующие действия в ответ на вопрос об объектах «на объект» и явных объектах монитора:

Краткий ответ: @JonSkeet: да, удаление мониторов создаст проблемы: это создаст трение. Хранение этих мониторов в Object напоминает нам, что это всегда многопоточная система.

Встроенные мониторы объектов не сложны, но они просты в объяснении; работать предсказуемо; и ясно в их назначении. synchronized(this) является четким заявлением о намерениях. Если мы заставляем начинающих кодеров использовать исключительно пакет параллелизма, мы вводим трение. Что в этом пакете? Что такое семафор? Вилочных присоединиться?

Начинающий кодер может использовать мониторы объектов для написания приличного кода модель-представление-контроллер. synchronized, wait и notifyAll могут использоваться для реализации наивной (в смысле простой, доступной, но, возможно, не передовой) безопасности потоков. Каноническим примером может быть один из этих двойников (заданный OP), который может иметь один поток, устанавливающий значение, в то время как поток AWT получает значение для помещения его в JLabel. В этом случае нет веской причины для создания явного дополнительного объекта только для того, чтобы иметь внешний монитор.

При несколько более высоком уровне сложности эти же методы полезны в качестве метода внешнего мониторинга. В приведенном выше примере я это сделал явно (см. Фрагменты objectWithMonitor выше). Опять же, эти методы действительно удобны для создания относительно простой безопасности потоков.

Если вы хотите быть еще более изощренным, я думаю, вам следует серьезно подумать о прочтении Параллелизма Java на практике (если вы этого еще не сделали) Блокировки чтения и записи очень мощные, не добавляя слишком много дополнительной сложности.

Punchline: Используя базовые методы синхронизации, вы можете использовать большую часть производительности, обеспечиваемой современными многоядерными процессорами с поточной безопасностью и без лишних затрат.

2 голосов
/ 30 октября 2009

Все объекты в Java имеют связанные с ними мониторы. Примитивы синхронизации полезны практически для всего многопоточного кода, и его семантически очень приятно синхронизировать с объектами, к которым вы обращаетесь, а не с отдельными объектами «Мониторинга».

Java может распределять мониторы, связанные с объектами, по мере необходимости - как это делает .NET - и в любом случае фактические издержки на простое выделение (но не использование) блокировки будут весьма малы.

Короче говоря: действительно удобно хранить объекты с их битами поддержки безопасности потоков, и это очень мало влияет на производительность.

1 голос
/ 30 октября 2009

Эти методы существуют для реализации межпотоковой связи.

Отметьте эту статью на эту тему .

Правила для этих методов, взятые из этой статьи:

  • wait () говорит вызывающему потоку отказаться от монитора и перейти в спящий режим, пока какой-нибудь другой поток входит в тот же монитор и вызывает notify ().
  • notify () пробуждает первый поток, вызвавший wait () для того же объекта.
  • notifyAll () активирует все потоки, вызвавшие wait () для одного и того же объекта. поток с наивысшим приоритетом будет запущен первым.

Надеюсь, это поможет ...

...