почему следующая программа может работать под спецификацией JLS на модели памяти JVM - PullRequest
0 голосов
/ 10 июля 2011

в JLS 3, 17.5 Вторая часть обсуждения Final Field Semantics: http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.5

Говорят, что myS может равняться "/ tmp", я лично не могу этого понять.Кто-нибудь может дать больше объяснений?Другой момент - это то, что говорит этот пример, означает ли это, что если мы хотим разделить Global.s между многопоточными, нам нужно сделать его окончательным (если он окончательным, то не может быть изменен после построения) или нужно синхронизировать при чтении и записи?или объявить массив String длиной 1 и последний, чтобы его можно было изменить и предоставить общий доступ ??

исходное содержимое в JLS:


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

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

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

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

объяснение в JLS:

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

Ответы [ 3 ]

2 голосов
/ 10 июля 2011

Строки реализованы с использованием char [], offset и count.Метод substring создает новый объект String с тем же char [] и новым смещением и числом.Исходя из семантики выполнения, новая строка может быть частично инициализирована, когда поток 2 попытается получить к ней доступ.Согласно источнику , подстрока возвращает новый объект String, созданный с помощью простого конструктора:

644       // Package private constructor which shares value array for speed.
645       String(int offset, int count, char value[]) {
646           this.value = value;
647           this.offset = offset;
648           this.count = count;
649       }

Таким образом, без пометки char [], offset и count как final в определении класса Stringтогда поток 2 может увидеть несогласованные значения при обращении к ним.Если это произойдет, то можно установить char [], но смещение и число могут быть неправильными.Если смещение по-прежнему отображается по умолчанию 0, вы увидите всю строку.Конечно, это потребовало бы изумительного времени, особого переупорядочения инструкций со стороны JIT и целого ряда «удач», чтобы это произошло.

1 голос
/ 10 июля 2011

Это зависит от , когда "/tmp/usr".substring(4) выполняется.

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

фактически выполняется как:

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

Если поток 2 ищет Global.s перед выполнением этого кода или (очень маловероятно) между этими двумя строками он будет видеть s не как "/usr/", а как null или "/tmp/usr" в зависимости от времени.

Строки являются неизменяемыми, но ссылки на строки не являются:

String str = "A"; // "A" is immutable
str = "B";        // variable "str" may be changed to refer to a different String
0 голосов
/ 10 июля 2011

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

Смысл этого в том, что без специальной семантики final один поток мог видеть неизменный объект, созданный другим потоком в несогласованном состоянии. Специальная семантика final предотвращает это; см. JLS 17.5.1 . В этом случае тот факт, что 3 внутренних поля класса String равны final, означает, что между концом конструктора String и любой операцией с результирующей строкой существует отношение «до начала».


Но JLS сказал, что это может быть "/ tmp", чего я не могу понять.

Операция подстроки направлена ​​на создание объекта String с состоянием {offset: 4, count: 4, chars: "/tmp/usr".chars}.

Но модель памяти не гарантирует, что несинхронизированный поток увидит обновления полей в том порядке, в котором они сделаны. В частности, он может видеть строку в состоянии {offset: 0, count: 4, chars: "/tmp/usr".chars} ... т.е. "/tmp".

(На самом деле этого не происходит из-за особой семантики полей final, как описано непосредственно под примером.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...