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

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

  1. А написать в a в потоке 1 происходит - перед записью в sync в вызове sync() в потоке 1 (правило порядка программ).
  2. Запись в sync в призыве к sync() в потоке 1 происходит - перед чтением sync в вызове sync в потоке 2 (правило изменяемой переменной).
  3. Чтение из 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,

Другие вопросы по тегам