Разъяснение из спецификации Java Janguage

Разве это не должно быть myS.equals("/usr") в этом объяснении из JLS ?

Заключительные поля предназначены для обеспечения необходимых гарантий безопасности. Рассмотрим следующий пример. Один поток (который мы будем называть потоком 1) выполняет

Global.s = "/tmp/usr".substring(4);

в то время как другой поток (поток 2) выполняется

String myS = Global.s;
if (myS.equals("/tmp"))System.out.println(myS);

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


person user2757106    schedule 19.09.2013    source источник


Ответы (5)


На самом деле нет, это может быть то, что вы могли бы подумать на первый взгляд, но это сделано намеренно. Цитирую дальше по тексту (выделено мной):

[...] если бы поля класса String не были окончательными, то было бы возможно (хотя и маловероятно), что поток 2 мог бы изначально видеть значение по умолчанию 0 для смещения строкового объекта, позволяя ему сравнить как равный "/tmp"

person Joachim Sauer    schedule 19.09.2013

Они описывают гипотетическую ситуацию, в которой возвращаемая подстрока может оказаться либо /tmp, либо /usr из-за состояния гонки. В результате не имеет большого значения, какая строка используется для сравнения; Суть примера в том, что любое из них могло бы быть правильным, если бы выполнялись условия, описанные в этом примере.

person Ernest Friedman-Hill    schedule 19.09.2013

Я не вижу ничего плохого. Оба фрагмента кода указывают на другой поток, и пример взят, чтобы оправдать истинную неизменность класса String в java. Ранее значение строки было "/THREADS...." при выполнении потока 2, а затем оно было изменено на "/usr" потоком 1. Это ясно объяснено в описании.

person Abhijith Nagarajan    schedule 19.09.2013

Нет, не должно.

Этот пример хорош, потому что он относится к ошибке, которая присутствовала в некоторых реализациях JVM (к сожалению, я не помню, в каких). String.substring не создавал новую строку, а указывал на старую с неконечными полями offset и length, указывающими на правильное местоположение. Однако, поскольку поля не были окончательными (и другой синхронизации не было), то мог произойти именно тот сценарий, который упоминается в абзаце, следующем за примером кода:

В частности, если бы поля класса String не были окончательными, то было бы возможно (хотя и маловероятно), что поток 2 мог бы изначально видеть значение по умолчанию 0 для смещения строкового объекта, позволяя ему сравниваться как равные " /tmp". Более поздняя операция над объектом String может увидеть правильное смещение 4, чтобы объект String воспринимался как "/usr".

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

person subsub    schedule 19.09.2013

String представляет собой массив символов с смещением и длиной. Этот массив символов использовался для повторного использования между несколькими строками. В качестве оптимизации использования памяти substring() будет возвращать новый String, поддерживаемый тем же массивом символов, что и исходная строка. Что делает этот метод, так это определяет новое смещение и длину в этом массиве символов для строки результата, а затем вызывает частный конструктор, чтобы создать этот объект результата и вернуть его.

(Как отмечает Иоахим, String больше не работает, но пример в JLS основан на более старых внутренних компонентах.)

Теперь, к чему может привести реализация этого закрытого конструктора, переупорядочивание инструкций JIT или ЦП, или просто странный способ работы памяти, разделяемой между потоками, заключается в том, что этот новый объект String сначала будет иметь длину, установленную на 4. смещение останется равным 0, что сделает значение этого String "/tmp". Всего через долю секунды его смещение будет установлено на 4, и его значение будет правильно равно "/usr".

Другими словами: поток 2 сможет наблюдать за этой строкой в ​​середине выполнения своего конструктора. Это кажется нелогичным, потому что люди интуитивно понимают код потока 1 как сначала полностью выполняющий правую часть присваивания и только затем изменяющий значение Global.s. К сожалению, без соответствующей синхронизации памяти другие потоки могут наблюдать другую последовательность событий. Использование полей final — это один из способов заставить JVM правильно обрабатывать это. (Я считаю, что объявление Global.s как volatile также сработает, но я не уверен на 100%.)

person millimoose    schedule 19.09.2013
comment
Просто примечание: они больше не реализуются таким образом. Теперь каждый String имеет свой собственный char[] и не имеет поля смещения или длины). Подробности см. в этом вопросе. - person Joachim Sauer; 19.09.2013
comment
@JoachimSauer Спасибо, я отредактирую это. Все еще правильно сказать, что это устаревшее поведение является объяснением утверждения в JLS, верно? (Даже если этот конкретный пример больше недействителен.) - person millimoose; 19.09.2013
comment
Я почти уверен, что это (все еще) причина. - person Joachim Sauer; 19.09.2013