Входит ли синхронный блок атома?

Знаете ли вы, что гарантируется, что синхронизированный блок в java является атомарным?

Представьте себе следующий случай

Thread_1,2:

synchronized(object){object.modify();}

(объект является общей переменной.)

Представьте, что thread_M изменит ссылку на объект, например

synchronized(object){object = new Object()}

теперь представьте, что потоки 1 и 2 конкурируют за получение блокировки объекта

Возможно ли следующее:
1. Thread1: прочитайте старый объект
2. ThreadM: изменить ссылку на объект и отменить блокировку старого объекта
3. Thread2: читать новый объект; проверить блокировку; заблокировать его
4. Thread1: проверить блокировку (ok cos старый объект был прочитан); заблокировать его
теперь оба потока имеют блокировку и изменяют один и тот же (новый) объект

Итак, чтобы указать мой вопрос - где-то гарантировано, что в синхронизированных (объектных) шагах (1 и 4) являются атомарными (как показано на шаге 3)?

Ответ 1

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

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

Модификации, сделанные потоком перед освобождением блокировки, гарантируются, чтобы быть видимыми для потоков, которые впоследствии приобретают блокировку. Но так как вы переназначаете сам замок, то нисходящий поток может не видеть, что он был изменен, и получить блокировку старого значения. Тогда они все равно не увидели бы, что object был переназначен.

Объявление object как переменной volatile гарантирует, что его "текущее" значение используется для блокировки. Но это не помешало двум видам одновременно изменять один и тот же экземпляр:

  • Thread M получает блокировку по старому значению. В Thread 1 читается старое значение.
  • Thread M изменяет значение.
  • Thread M освобождает блокировку по старому значению. В Thread 2 читается новое значение.
  • Thread 1 получает блокировку по старому значению. Thread 2 получает блокировку нового значения.
  • Тема 1 читает новое значение. В Thread 2 читается новое значение.
  • Тема 1 изменяет новое значение. Тема 2 изменяет новое значение.

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

Ответ 2

Предположим, что у вас есть переменная, foo:

Foo foo;

И пусть он содержит ссылку на объект:

foo = new Foo(...);

И пусть у нас есть блок synchronized:

synchronized(foo) {
    ...
}

Клавиша synchronized не работает с переменной, foo, и она не работает с операторами в синхронизированном блоке.

Единственное, что делает ключевое слово synchronized, это предотвращение синхронизации других потоков в одном экземпляре одновременно.

Если вы переназначите переменную foo, чтобы ссылаться на какой-то другой экземпляр, когда поток A находится внутри блока, тогда другой поток B сможет одновременно ввести один и тот же блок, потому что каждый из двух потоков будет синхронизироваться в другом экземпляре.

Ответ 3

Вы синхронизируете объект, на который указывает этот объект, а не переменная, содержащая значение.

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

Вы можете найти меньше путаницы, если вместо синхронизированных блоков кода использовать синхронизированные методы.

Кроме того, только мое мнение, но синхронизированное (объект) похоже как очень плохой дизайн. Это только мое мнение, но я никогда не делаю что-то подобное.