Ответ 1
Ваш код (в основном) правильный, и это распространенная идиома.
// reproducing your code
class A
state=false; //A
initialized=false; //B
boolean state;
volatile boolean initialized = false; //0
void setState(boolean newState)
state = newState; //1
initialized = true; //2
boolean getState()
if (!initialized) //3
throw ...;
return state; //4
Строка #A #B - псевдокод для записи значений по умолчанию для переменных (ака обнуления полей). Мы должны включать их в строгий анализ. Обратите внимание, что #B отличается от # 0; оба выполняются. Строка #B не считается изменчивой записью.
Все волатильные обращения (чтение/запись) по всем переменным находятся в общем порядке. Мы хотим установить, что # 2 до 3 в этом порядке, если # 4 достигнуто.
Есть 3 записи в initialized
: #B, # 0 и # 2. Только # 2 присваивает значение true. Поэтому, если # 2 после # 3, # 3 не может считаться истинным (это, вероятно, связано с отсутствием гарантии недостаточного уровня шума, который я не совсем понимаю), то №4 не может быть достигнуто.
Поэтому, если # 4 достигнуто, # 2 должно быть до # 3 (в общем порядке волатильных доступов).
Поэтому # 2 происходит - до # 3 (происходит летучая запись - перед последующим изменчивым чтением).
По порядку программирования, # 1 происходит - до # 2, # 3 происходит - до # 4.
По транзитивности, поэтому # 1 происходит - до # 4.
Строка # A, запись по умолчанию, происходит перед всеми (кроме других записей по умолчанию)
Поэтому все обращения к переменной state
находятся в цепочке, которая происходит до: #A → # 1 → # 4. Нет гонки данных. Программа правильно синхронизирована. Чтение # 4 должно наблюдать запись # 1
Однако есть небольшая проблема. Строка № 0, по-видимому, избыточна, так как #B уже назначил false. На практике волатильная запись не является незначительной по производительности, поэтому нам следует избегать # 0.
Хуже того, наличие # 0 может вызвать нежелательное поведение: # 0 может появиться после # 2! Поэтому может случиться, что вызывается setState()
, но последующие getState()
продолжают бросать ошибки.
Это возможно, если объект не опубликован безопасно. Предположим, что поток T1 создает объект и публикует его; поток T2 получает объект и вызывает на нем setState()
. Если публикация небезопасна, T2 может наблюдать ссылку на объект, прежде чем T1 завершит инициализацию объекта.
Вы можете игнорировать эту проблему, если вам требуется, чтобы все объекты A
были безопасно опубликованы. Это разумное требование. Это может быть неявно ожидаемым.
Но если у нас нет строки # 0, это не будет проблемой вообще. Запись по умолчанию #B должна произойти - до # 2, поэтому, пока вызывается setState()
, все последующие getState()
будут наблюдать initialized==true
.
В примере счетчика вниз, initialized
есть final
; что имеет решающее значение для обеспечения безопасной публикации: все потоки будут наблюдать правильно инициализированную защелку.