Летучие контрейлерные. Этого достаточно для видимости?

Это о летучем контрейлере. Цель: Я хочу достичь облегченного взгляда. Согласованность 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.