Летучие контрейлерные. Этого достаточно для видимости?
Это о летучем контрейлере.
Цель: Я хочу достичь облегченного взгляда. Согласованность a_b_c не важна. У меня есть куча варсов, и я не хочу, чтобы они были неустойчивыми.
Является ли этот код небезопасным?
class A {
public int a, b, c;
volatile int sync;
public void setup() {
a = 2;
b = 3;
c = 4;
}
public void sync() {
sync++;
}
}
final static A aaa = new A();
Thread0:
aaa.setup();
end
Thread1:
for(;;) {aaa.sync(); logic with aaa.a, aaa.b, aaa.c}
Thread2:
for(;;) {aaa.sync(); logic with aaa.a, aaa.b, aaa.c}
Ответы
Ответ 1
Модель памяти Java определяет взаимосвязь между событиями, которые имеют следующие свойства (среди прочих):
- "Каждое действие в потоке происходит - перед каждым действием в этом потоке, которое приходит позже в порядке программы" (правило порядка программы)
- "Выполняется запись в поле volatile - перед каждым последующим чтением того же volatile" (правило изменчивой переменной)
Эти два свойства вместе с транзитивностью взаимосвязи "дожидаться" подразумевают, что видимость гарантирует, что OP ищет следующим образом:
- Запись в
a
в потоке 1 происходит - перед записью в sync
при вызове sync()
в потоке 1 (правило порядка программы).
- Запись в
sync
в вызове sync()
в потоке 1 происходит - перед чтением до sync
при вызове sync
в потоке 2 (правило изменчивых переменных).
- Чтение из
sync
в вызове sync()
в потоке 2 происходит до чтения из a
в потоке 2 (правило порядка программы).
Это означает, что ответ на вопрос "да", то есть вызов sync()
на каждой итерации в потоках 1 и 2 обеспечивает видимость изменений в a
, b
и c
для другого потока ( с). Обратите внимание, что это обеспечивает только видимость. Никаких взаимных исключений не существует, и поэтому все инварианты, связывающие a
, b
и c
, могут быть нарушены.
См. также Теория и практика Java: Фиксация модели памяти Java, часть 2. В частности, раздел "Новые гарантии для летучих", в котором говорится
В новой модели памяти, когда поток A записывает в изменчивый переменная V, а поток B считывает из V любые значения переменных, которые были видимые А в момент написания V, теперь гарантированы видимый B.
Ответ 2
Приращение значения между потоками никогда не является потокобезопасным только с помощью volatile
. Это гарантирует, что каждый поток получает обновленное значение, а не то, что приращение является атомарным, потому что на уровне ассемблера ваш ++ на самом деле является несколькими инструкциями, которые можно чередовать.
Вы должны использовать AtomicInteger
для быстрого атомного приращения.
Изменить. Чтение снова того, что вам нужно, - это на самом деле забор памяти. Java не имеет инструкции по сохранению памяти, но вы можете использовать блокировку для забора памяти "побочный эффект". В этом случае объявите синхронизированный метод синхронизации, чтобы ввести неявный забор:
void synchronized sync() {
sync++;
}
Ответ 3
Из javadoc:
Разблокировка (синхронизированный выход блока или метода) монитора происходит до каждой последующей блокировки (синхронизированный блок или метод запись) того же монитора. А так как отношение "бывает раньше" транзитивно, все действия нити перед разблокировкой произойти - перед всеми действиями после любой блокировки потока, которая монитор.
Записывается в нестабильное поле - перед каждым последующим чтением это же поле. Сценарии и чтения изменчивых полей аналогичны эффект согласованности с памятью как вход и выход мониторов, но не влекут за собой блокировку взаимного исключения.
Итак, я думаю, что запись в volatile var не является эквивалентом синхронизации в этом случае, и это не гарантирует - до того, как порядок и видимость изменений в Thread1 to Thread2
Ответ 4
Обычно шаблон выглядит следующим образом.
public void setup() {
a = 2;
b = 3;
c = 4;
sync();
}
Однако, хотя это гарантирует, что другие потоки будут видеть это изменение, другие потоки могут видеть неполное изменение. например Thread2
может видеть a = 2, b = 3, c = 0. или даже возможно a = 2, b = 0, c = 4;
Использование функции sync() для чтения не очень помогает.
Ответ 5
Вам вообще не нужно вручную синхронизировать, просто используйте автоматически синхронизированную структуру данных, например java.util.concurrent.atomic.AtomicInteger
.
В качестве альтернативы вы можете сделать метод sync()
synchronized
.