Создает ли JVM мьютекс для каждого объекта, чтобы реализовать "синхронизированное" ключевое слово? Если нет, то как?

Как программист на С++, более знакомый с Java, мне немного странно видеть поддержку уровня языка для блокировки на произвольных объектах без какого-либо объявления, которое объект поддерживает такую ​​блокировку. Создание мьютексов для каждого объекта выглядит как высокая стоимость, на которую автоматически выбирается. Кроме использования памяти, мьютексы являются ограниченным ресурсом ОС на некоторых платформах. Вы можете блокировать блокировку, если мьютексы недоступны, но характеристики производительности значительно различаются, что я ожидаю, что урон предсказуем.

Является ли JVM достаточно умным во всех случаях, чтобы признать, что конкретный объект никогда не станет целью синхронизированного ключевого слова и, таким образом, избежать создания мьютекса? Мьютексы могут создаваться лениво, но это создает проблему самонастройки, которая сама по себе требует мьютекса, и даже если бы это было сработано, я предполагаю, что все еще будут некоторые накладные расходы для отслеживания того, был ли уже создан мьютекс или нет. Поэтому я предполагаю, что такая оптимизация возможна, она должна выполняться во время компиляции или запуска. В С++ такая оптимизация невозможна из-за модели компиляции (вы не могли знать, будет ли блокировка для объекта использоваться для границ библиотеки), но я недостаточно знаю о компиляции Java и связывании с ней если применяются те же ограничения.

Ответы

Ответ 1

Говоря как человек, который посмотрел, как некоторые JVM реализуют блокировки...

Обычный подход - начать с пары зарезервированных бит в заголовочном слове объекта. Если объект никогда не заблокирован, или если он заблокирован, но нет споров, он остается таким. Если и когда возникает конфликт на заблокированном объекте, JVM раздувает блокировку в полноразмерную структуру данных мьютекса, и она остается таким образом для времени жизни объекта.

EDIT. Я только заметил, что OP говорил о мьютексах, поддерживаемых OS. В примерах, которые я рассмотрел, неизолированные мьютексы были реализованы непосредственно с использованием инструкций CAS и т.п., а не с использованием функций библиотеки pthread и т.д.

Ответ 2

Вы никогда не можете быть уверены, что объект никогда не будет использоваться как замок (рассмотрите отражение). Обычно каждый объект имеет заголовок с некоторыми битами, выделенными для блокировки. Это можно реализовать так, чтобы заголовок добавлялся только по мере необходимости, но это немного усложняется, и вам все равно понадобится заголовок (класс (эквивалент "vtbl" и размер выделения в C++), хеш-код и мусор сбор).

Вот вики-страница о реализации синхронизации в OpenJDK.

(По моему мнению, добавление блокировки для каждого объекта было ошибкой.)

Ответ 3

Это действительно деталь реализации JVM, и разные JVM могут реализовывать ее по-разному. Тем не менее, это определенно не то, что можно оптимизировать во время компиляции, так как ссылки Java во время выполнения, и это возможно, чтобы ранее неизвестный код мог удержать объект, созданный в более раннем коде, и начать синхронизацию с ним.

Обратите внимание, что в Java lingo примитив синхронизации называется "monitor", а не mutex, и поддерживается специальными операциями байт-кода. Здесь довольно подробное объяснение здесь.

Ответ 4

не может JVM напрямую использовать команду сравнения и замены? пусть каждый объект имеет поле lockingThreadId, сохраняющее идентификатор потока, который его блокирует,

while( compare_and_swap (obj.lockingThreadId, null, thisThreadId) != thisTheadId )
    // failed, someone else got it
    mark this thread as waiting on obj.
    shelf this thead

//out of loop. now this thread locked the object

do the work

obj.lockingThreadId = null;
wake up threads waiting on the obj

это игрушечная модель, но она не кажется слишком дорогой и не полагается на ОС.