Летучий против не летучий

Давайте рассмотрим следующий кусок кода на Java

int x = 0;
int who = 1
Thread #1:
   (1) x++;
   (2) who = 2;

Thread #2
   while(who == 1);
   x++;   
   print x; ( the value should be equal to 2 but, perhaps, it is not* )    

(Я не знаю моделей памяти Java - давайте предположим, что это сильная модель памяти - я имею в виду: (1) и (2) не поменяется местами)
Модель памяти Java гарантирует, что доступ / хранение к 32-битным переменным является атомарным, поэтому наша программа безопасна. Но, тем не менее, мы должны использовать атрибут volatile так как *. Значение x может быть равно 1 так как x может храниться в реестре, когда Thread#2 прочитай это. Чтобы решить это, мы должны сделать x переменная volatile, Ясно.

Но как насчет этой ситуации:

    int x = 0;
    mutex m; ( just any mutex)
Thread #1:
       mutex.lock()
       x++;
       mutex.unlock()

    Thread #2
       mutex.lock()
       x++;   
       print x; // the value is always 2, why**?
       mutex.unlock()

Значение x всегда 2 хотя мы не делаем это volatile, Правильно ли я понимаю, что блокировка / разблокировка мьютекса связана со вставкой барьеров памяти?

2 ответа

Решение

Я постараюсь заняться этим. Модель памяти Java довольно сложна, и ее трудно вместить в один пост Stackru. Пожалуйста, обратитесь к Java параллелизма Брайана Гетца на практике для полной истории.

Значение x всегда равно 2, хотя мы не делаем его изменчивым. Правильно ли я понимаю, что блокировка / разблокировка мьютекса связана со вставкой барьеров памяти?

Во-первых, если вы хотите понять модель памяти Java, это всегда глава 17 спецификации, которую вы хотите прочитать.

Эта спецификация говорит:

Разблокировка на мониторе происходит перед каждой последующей блокировкой на этом мониторе.

Так что да, есть событие видимости памяти при разблокировке вашего монитора. (Я предполагаю, что под "мьютексом" вы подразумеваете монитор. Большинство замков и других классов в java.utils.concurrent Пакет также имеет место до семантики, проверьте документацию.)

Случайность раньше - это то, что означает Java, когда она гарантирует не только упорядочение событий, но и гарантированную видимость памяти.

We say that a read r of a variable v is allowed to observe a write w
to v if, in the happens-before partial order of the execution trace:

    r is not ordered before w (i.e., it is not the case that 
    hb(r, w)), and

    there is no intervening write w' to v (i.e. no write w' to v such
    that hb(w, w') and hb(w', r)).

Informally, a read r is allowed to see the result of a write w if there
is no happens-before ordering to prevent that read. 

Это все из 17.4.5. Это немного сбивает с толку, чтобы прочитать, но информация все там, если вы читаете ее.

Давайте рассмотрим некоторые вещи. Следующее утверждение верно: модель памяти Java гарантирует, что доступ / хранение 32-битных переменных является атомарным. Однако из этого не следует, что первая указанная вами псевдопрограмма является безопасной. Тот факт, что два оператора упорядочены синтаксически, не означает, что видимость их обновлений также упорядочена так, как их видят другие потоки. Поток #2 может увидеть обновление, вызванное who=2, до того, как будет видно увеличение x. Установка x volatile все равно не сделает программу правильной. Вместо этого, использование переменной 'who' voliatile сделает программу правильной. Это потому, что volatile взаимодействует с моделью памяти Java определенным образом.

Я чувствую, что в основе здравого смысла понимания изменчивости, которое неверно, лежит некое понятие "обратная запись в основную память". Volatile не записывает значение в основную память на Java. То, что делает чтение и запись в изменчивую переменную, создает то, что называется отношением "происходит до". Когда поток #1 пишет в переменную volatile, вы создаете связь, которая гарантирует, что любые другие потоки #2, просматривающие эту переменную переменную, также смогут "просматривать" все действия, которые поток #1 выполнял до этого. В вашем примере это означает, что "кто" изменчив. Записывая значение 2 в поле "кто", вы создаете отношение "происходит раньше", так что когда поток № 2 просматривает кто = 2, он также увидит обновленную версию x.

Во втором примере (при условии, что вы хотели иметь переменную 'who'), разблокировка мьютекса создает отношение "происходит до", как я указывал выше. Так как это означает, что другие потоки просматривают разблокировку мьютекса (т.е. они могут заблокировать его самостоятельно), они увидят обновленную версию x.

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