Летучие контрейлерные. Этого достаточно для наглядности? - PullRequest
22 голосов
/ 07 января 2012

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

33 голосов
/ 07 января 2012

Модель памяти 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 теперь гарантированно виден Б.

4 голосов
/ 07 января 2012

Увеличение значения между потоками никогда не является поточно-ориентированным, просто volatile.Это только гарантирует, что каждый поток получает актуальное значение, а не то, что приращение является атомарным, потому что на уровне ассемблера ваш ++ на самом деле представляет собой несколько инструкций, которые можно чередовать.

Вы должны использовать AtomicInteger для быстрого атомного приращения.

Редактировать : Считывание снова того, что вам нужно, на самом деле является ограничением памяти.В Java нет инструкции ограничения памяти, но вы можете использовать блокировку для ограничения памяти "побочный эффект".В этом случае объявите метод синхронизации синхронизированным, чтобы ввести неявный забор:

void synchronized sync() {
    sync++;
}
0 голосов
/ 07 января 2012

Шаблон обычно такой.

public void setup() {
    a = 2;
    b = 3;
    c = 4;
    sync();
}

Однако, хотя это гарантирует, что другие потоки увидят это изменение, другие потоки могут увидеть неполное изменение. например Thread2 может видеть a = 2, b = 3, c = 0. или даже, возможно, a = 2, b = 0, c = 4;

Использование sync () на считывателе мало помогает.

0 голосов
/ 07 января 2012

От javadoc:

Разблокировка (выход синхронизированного блока или метода) монитора происходит перед каждой последующей блокировкой (вход синхронизированного блока или метода) того же монитора.И поскольку отношение «происходит до» является транзитивным, все действия потока перед разблокировкой выполняются до того, как все действия, последующие за любым потоком, блокирующим этот монитор.

Запись в энергозависимое поле происходит до каждого последующего чтенияэто же поле.Запись и чтение изменяемых полей имеют эффекты согласованности памяти, аналогичные входящим и выходящим мониторам, но не влекут за собой взаимную блокировку исключения.

Поэтому я думаю, что запись в volatile var не эквивалентна синхронизациислучай, и это не гарантирует, что произойдет до того, как порядок и видимость изменений в Thread1 в Thread2

0 голосов
/ 07 января 2012

Вам вообще не нужно вручную синхронизировать, просто используйте автоматически синхронизированную структуру данных, как java.util.concurrent.atomic.AtomicInteger.

В качестве альтернативы можно использовать метод sync() synchronized.

...