Глубокое понимание изменчивости в Java

Позволяет ли Java выводить 1, 0? Я проверил это очень интенсивно, и я не могу получить этот вывод. Я получаю только 1, 1 или же 0, 0 или же 0, 1,

public class Main {
    private int x;
    private volatile int g;

    // Executed by thread #1
    public void actor1(){
       x = 1;
       g = 1;
    }

    // Executed by thread #2
    public void actor2(){
       put_on_screen_without_sync(g);
       put_on_screen_without_sync(x);
    }
}

Зачем?

На мой глаз можно попасть 1, 0, Мои рассуждения.g является энергозависимым, поэтому он обеспечивает порядок в памяти. Итак, это выглядит так:

actor1:

(1) store(x, 1)
(2) store(g, 1)
(3) memory_barrier // on x86

и я вижу следующую ситуацию: изменить порядок store(g, 1) до store(x,1) (memory_barrier - после (2)). Теперь запустите поток № 2. Так, g = 1, x = 0, Теперь мы ожидали выхода. Что неверного в моих рассуждениях?

3 ответа

Решение

Любые действия перед изменчивой записью происходят перед (HB) любым последующим изменчивым чтением той же переменной. В вашем случае напишите x происходит перед записью g (из-за заказа программы).

Таким образом, есть только три возможности:

  • actor2 запускается первым, а x и g равны 0 - вывод равен 0,0
  • actor1 запускается первым, а x и g равны 1 из-за того, что произошло до отношения HB - вывод равен 1,1
  • методы работают одновременно и только x=1 выполняется (не g=1) и результат может быть либо 0,1, либо 0,0 (нет записи с изменяемым значением, поэтому нет гарантии)

Нет, это невозможно. Согласно JMM, все, что было видно потоку 1, когда он записывает в энергозависимое поле, становится видимым потоку 2, когда он читает это поле.

Вот еще один пример, похожий на ваш:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }

  public void reader() {
    if (v == true) {
      //uses x - guaranteed to see 42.
    }
  }
}

Ты никогда не увидишь 1, 0, но правильно объяснить это будет непросто. Сначала давайте сделаем несколько очевидных вещей. В спецификации сказано:

Если x и y являются действиями одного и того же потока и x стоит перед y в программном порядке, тогда hb(x, y).

Это означает, что на стороне пишущей нити hb(x, g)и на чтение стороны hb(g, x). Но это только в том случае, если вам придется рассуждать о каждом потоке индивидуально, поскольку в главе о Program orderговорит::

Среди всех межпотоковых действий, выполняемых каждым потоком t...

Итак, если вы представите, что запускаете каждый поток за раз, тогда happens-beforeбудет правильным для каждого из них индивидуально. Но ты этого не сделаешь. Ваши актеры (я уверен, что вы используете jcstressтам) работать одновременно. Так что полагаться на "программный порядок" для рассуждений недостаточно (и это тоже правильно).

Теперь вам нужно как-то синхронизировать эти два действия - чтение и запись. И вот как в спецификации сказано, что это можно сделать:

Запись в изменчивую переменную синхронизируется со всеми последующими чтениями v любым потоком (где "последующие" определены в соответствии с порядком синхронизации).

А потом говорит:

Если действие x синхронизируется со следующим действием y, то у нас также есть hb(x, y).

Если вы сейчас сложите все это вместе:

          (hb)              (hb)             (hb)
write(x) ------> write(g) -------> read(g) -------> read(x)

Это также называется "переходным" закрытием program order и synchronizes-with order. Поскольку есть hb на каждом шагу, видя 1, 0 (краткое прочтение), согласно спецификации невозможно.

Нет, а на самом деле это свойство volatile используется в таких классах, как ConcurrentHashMap реализовать свободный путь без блокировки, примерно так:

volatile int locked = 0;
...
void mutate() {
    if (Unsafe.compareAndSwapInt(locked,0,1)) { 
    /*this isn't exactly how you call this method, but the point stands: 
      if we read 0, we atomically replace it with 1 and continue on the happy 
      path */
       //we are happy
       //so we mutate the structure and then
       locked = 0;           
    } else {
       //contended lock, we aren't happy
    }
}

Поскольку записи до энергозависимой записи не могут быть переупорядочены после энергозависимой записи, а чтения после энергозависимого чтения не могут быть переупорядочены до энергозависимого чтения, код, подобный этому, действительно работает как "блокировка без блокировки".

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