Ваш код (в основном) правильный, и это распространенная идиома.
// 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.
В initialized
есть 3 записи: #B, # 0 и # 2. Только № 2 присваивает истину. Поэтому, если № 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
; это важно для обеспечения безопасной публикации: все потоки будут иметь правильно инициализированную защелку.