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