Летучие контрейлерные. Этого достаточно для наглядности?
Это о летучих контрейлерных. Цель: я хочу, чтобы облегчить видимость. Согласованность 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}
5 ответов
Модель памяти Java определяет отношение " происходит до", которое имеет следующие свойства (среди прочих):
- "Каждое действие в потоке происходит - перед каждым действием в этом потоке, которое происходит позже в программном порядке" (правило программного порядка)
- "Запись в изменяемое поле происходит - перед каждым последующим чтением того же самого изменчивого" (правило изменяемой переменной)
Эти два свойства вместе с транзитивностью отношения " происходит до" подразумевают видимость, гарантирующую, что 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, любые значения переменных, которые были видны A во время записи V, теперь гарантированно будут видны B.
Увеличение значения между потоками никогда не является потокобезопасным, просто volatile
, Это только гарантирует, что каждый поток получает актуальное значение, а не то, что приращение является атомарным, потому что на уровне ассемблера ваш ++ на самом деле представляет собой несколько инструкций, которые можно чередовать.
Вы должны использовать AtomicInteger
для быстрого увеличения атома.
Изменить: Чтение еще раз, что вам нужно на самом деле забор памяти. В Java нет инструкции ограничения памяти, но вы можете использовать блокировку для ограничения памяти "побочный эффект". В этом случае объявите метод синхронизации синхронизированным, чтобы ввести неявный забор:
void synchronized sync() {
sync++;
}
Шаблон обычно такой.
public void setup() {
a = 2;
b = 3;
c = 4;
sync();
}
Однако, хотя это гарантирует, что другие потоки увидят это изменение, другие потоки могут увидеть неполное изменение. например, Thread2
может видеть a = 2, b = 3, c = 0. или даже возможно a = 2, b = 0, c = 4;
Использование sync() для читателя мало помогает.
Из Javadoc:
Разблокировка (синхронизированный блок или выход метода) монитора происходит перед каждой последующей блокировкой (синхронизированный блок или вход метода) того же монитора. И поскольку отношение "происходит до" является транзитивным, все действия потока перед разблокировкой происходят до всех действий, следующих за блокировкой любого потока, отслеживающей этот процесс.
Запись в изменчивое поле происходит - перед каждым последующим чтением того же поля. Запись и чтение изменяемых полей имеют эффекты согласованности памяти, аналогичные входящим и выходящим мониторам, но не влекут за собой взаимную блокировку исключения.
Поэтому я думаю, что запись в volatile var не является эквивалентом синхронизации в этом случае, и это не гарантирует, что произойдет перед порядком и видимость изменений в Thread1 в Thread2
Вам вообще не нужно вручную синхронизировать, просто используйте автоматически синхронизированную структуру данных, например java.util.concurrent.atomic.AtomicInteger
,
Вы могли бы альтернативно сделать sync()
метод synchronized
,