Подтвердить что ты не робот

Почему конечные поля не могут быть volatile в Java?

Я хотел бы понять, почему ссылка, объявленная как final, не может быть объявлена ​​как volatile. Существует аналогичный вопрос о SO [Почему переменная-член объекта не является окончательной и изменчивой в Java?

[1]: Почему переменная-член объекта не может быть как окончательной, так и изменчивой в Java?, но я не уверен, понят ли FINAL в этом ответе.

Теперь состояние конечной переменной может быть обязательно изменено после ее инициализации. Только ссылка не может быть инициализирована другим объектом.

Например, рассмотрим приведенную ниже переменную-член

final StringBuilder sb = new StringBuilder("CAT");

Теперь другой поток изменяет sb как:

sb.append("S");

Будет ли это изменение доступно для разных потоков в соответствии с моделью памяти Java, если эта переменная является энергонезависимой?

EDIT: я изменил StringBuffer на StringBuilder, чтобы некоторые люди поняли мою точку зрения.

4b9b3361

Ответ 1

Будет ли это изменение доступно для разных потоков в соответствии с моделью памяти Java, если эта переменная является энергонезависимой.

Да, но это потому, что StringBuffer является потокобезопасным - это означает, что он внутренне обеспечивает блокировку, что приведет к барьерам памяти, поэтому другие потоки видят обновление.

Ссылка, являющаяся волатильной, не влияет на операции над объектом, она влияет на ссылку.

Итак, вы могли бы сделать

 volatile StringBuilder b = new StringBuilder();

 b = someOtherStringBuilder;

теперь, поскольку b является volatile, другие потоки будут видеть это обновление ссылки.

Однако выполнение

 b.append("foo");

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

Если вы хотите гарантировать, что b.append("foo"); будет видимым для других потоков без какой-либо блокировки, каждое поле участника в StringBuilder также должно быть нестабильным. (Хотя это не делает его потокобезопасным)

Ответ 2

volatile подразумевает, что поле изменится. Если он окончательный, вы никогда его не измените, и это не имеет смысла.

Будет ли это изменение доступно для разных потоков в соответствии с Java-памятью если эта переменная является энергонезависимой?

Объявление полевого volatile не влияет на его содержимое или модификации после возникновения летучей записи. Если поле является энергозависимым или энергонезависимым, эффекты памяти append продиктованы реализацией StringBuffer.

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

StringBuilder, с другой стороны, не синхронизирован, и видимость памяти не гарантируется. Поэтому, если вы попытались поменять местами два теста на многопоточность, вы увидите разные результаты.

Ответ 3

Потому что volatile влияет на поведение доступа Threads и редактирует переменную. В вашем примере кода переменная является ссылкой (sb) на объект. Таким образом, это означает, что volatile имеет эффект нет. final блокирует ссылку. Поэтому, если вы добавляете текст, вы не меняете ссылку. Таким образом, нет смысла использовать volatile и final в то же время.

Будет ли это изменение доступно для разных потоков в соответствии с моделью памяти Java, если эта переменная является энергонезависимой?

Поскольку вы используете безопасную реализацию без потоковой передачи с помощью StringBuilder, нет гарантии, что все потоки имеют последнее состояние StringBuilder.

Ответ 4

Помните, что volatile и final влияют на переменную/элемент, но не влияют на изменчивость (или безопасность потоков действий) объекта, названного/ссылающегося на него! (Другие ответы объясняют, почему модификаторы не имеют смысла вместе.)

В данном случае sb.append(..) изменяет именованный/ссылочный объект (хотя он не изменяет переменную/элемент) и не является безопасным для кросс-потоковой передачи с использованием либо модификатора.


Пока, как указывали другие, StringBuilder добавляет некоторую внутреннюю синхронизацию, это подразумевает, что он не будет "поврежден" на своем внутреннем уровне структуры данных.

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

Например, рассмотрим следующий код, выполняемый из нескольких потоков (и предположим, что sb теперь является объектом StringBuilder). Каким будет порядок?

sb.append("a").append("b");

Что произойдет, если код будет изменен следующим образом? [Более] "потокобезопасный"?

synchronized (this) {
  sb.append("a").append("b");
}

Ответ 5

Вопрос полностью неправильно понимает последствия ссылки объекта на несколько потоков. В общем, волатильность наиболее интересна вокруг примитивов, и это обычно там, где вы ее видите.

Финал и volatile влияют только на переменную, поскольку она содержит ссылку на объект, которая является дескриптором расположения объекта в памяти, и ничего более. Итак, для иллюстрации, скажем, ссылка на StringBuilder (или что-то еще) - это 9AF5. Если это окончательно, это не изменится после создания объекта. Поэтому (по крайней мере, с тех пор, как Java 1.5 не помню до этого) потокобезопасность, так как любой поток может ссылаться на эту переменную, ссылаться на ее копию или что-то, что CPU хочет сделать для оптимизации этого доступа, потому что в конце в день, это не изменится, и поэтому, если вы посмотрите на 9AF5, вам не нужно проверять, действительно ли изменилось значение ссылки, содержащейся в объекте с окончательным объявлением.

Поскольку окончательное гарантирует безопасность потока на том же уровне, что и волатильность, энергозависимая излишняя.

Если переменная не является окончательной, то volatile гарантирует, что все потоки будут последовательно отображать 9AF5, и если вы измените значение (например, назначив новый StringBuilder), чтобы теперь значение стало 1D7C, ни один поток не будет удерживая недействительную старую копию и считая, что значение равно 9AF5.

Ничего из этого не влияет на содержимое объекта StringBuilder, а только на Handle для его местоположения в памяти.

Ответ 6

Гарантии видимости памяти, предоставляемые volatile и final, на самом деле очень похожи:

  • volatile гарантирует, что любое изменение, сделанное потоком A перед записью в переменную volatile, будет видно потоку B после последующего чтения из этой переменной

  • final гарантирует, что любое изменение, сделанное потоком A в состоянии объекта, на которое ссылается поле final до инициализации этого поля, будет видно потоком B, если Thread B обращается к этому объекту через это поле

Как вы можете видеть, учитывая тот факт, что поле final обычно можно инициализировать только один раз, эти гарантии очень похожи - единственное существенное отличие заключается в том, что гарантия, предоставляемая полем final, охватывает только объекты, на которые ссылается это поле.

Поэтому нет смысла объединять эти гарантии в одном поле.

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

Ответ 7

когда вы объявляете поле объекта как final, вам нужно инициализировать его в конструкторе объекта, а затем конечное поле не изменит его значение. Когда вы объявляете поле объекта изменчивым, значение поля может меняться, но при каждом чтении значения из любого потока будет отображаться последнее значение.

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