Looking for java Keywords? Try Ask4Keywords

Java Language Бывает - прежде чем рассуждения применимы к некоторым примерам


пример

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

Однопоточный код

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

public class SingleThreadExample {
    public int a, b;
    
    public int add() {
       a = 1;         // write(a)
       b = 2;         // write(b)
       return a + b;  // read(a) followed by read(b)
    }
}

By Happens-Before Правило № 1:

  1. Действие write(a) происходит - перед действием write(b) .
  2. Действие write(b) происходит - перед действием read(a) .
  3. read(a) происходит - перед действием read(a) .

By Happens-Before Правило № 4:

  1. write(a) происходит - перед write(b) И write(b) происходит - перед read(a) ПРОБЛЕМЫ write(a) происходит - перед read(a) .
  2. write(b) происходит - перед read(a) И read(a) происходит - перед read(b) ПРОБЛЕМЫ write(b) происходит - перед read(b) .

Подведение итогов:

  1. Отношение write(a) -before read(a) означает, что a + b гарантированно видит правильное значение a .
  2. Отношение write(b) -before read(b) означает, что a + b гарантированно видит правильное значение b .

Поведение «volatile» в примере с 2 потоками

Мы будем использовать следующий примерный код, чтобы изучить некоторые последствия модели памяти для `volatile.

public class VolatileExample {
    private volatile int a;
    private int b;         // NOT volatile
    
    public void update(int first, int second) {
       b = first;         // write(b)
       a = second;         // write-volatile(a)
    }

    public int observe() {
       return a + b;       // read-volatile(a) followed by read(b)
    }
}

Во-первых, рассмотрим следующую последовательность операторов с участием 2 потоков:

  1. Создается один экземпляр VolatileExample ; назовите это ve ,
  2. ve.update(1, 2) вызывается в одном потоке и
  3. ve.observe() вызывается в другом потоке.

By Happens-Before Правило № 1:

  1. Действие write(a) происходит - перед действием volatile-write(a) .
  2. volatile-read(a) - перед действием read(b) .

«Бывает» - до правила № 2:

  1. Действие volatile-write(a) в первом потоке происходит до выполнения volatile-read(a) действия во втором потоке.

By Happens-Before Правило № 4:

  1. Действие write(b) в первом потоке происходит - перед действием read(b) во втором потоке.

Другими словами, для этой конкретной последовательности мы гарантируем, что второй поток увидит обновление для нелетучей переменной b сделанной первым потоком. Тем не менее, также должно быть ясно, что если назначения в методе update были наоборот, или метод observe() прочитал переменную b перед a , то цепочка, которая произошла раньше , будет нарушена. Цепь также будет разбита, если во втором потоке volatile-read(a) будет следовать volatile-write(a) в первом потоке.

Когда цепь сломана, нет гарантии, что observe() увидит правильное значение b .

Летучие с тремя нитями

Предположим, что мы добавим третий поток в предыдущий пример:

  1. Создается один экземпляр VolatileExample ; назовите это ve ,
  2. Два потока требуют update :
    • ve.update(1, 2) вызывается в одном потоке,
    • ve.update(3, 4) вызывается во втором потоке,
  3. ve.observe() впоследствии вызывается в третьем потоке.

Чтобы полностью проанализировать это, нам нужно рассмотреть все возможные перемежения операторов в первом и втором потоках. Вместо этого мы рассмотрим только два из них.

Сценарий №1 - предположим, что update(1, 2) предшествует update(3,4) мы получаем следующую последовательность:

write(b, 1), write-volatile(a, 2)     // first thread
write(b, 3), write-volatile(a, 4)     // second thread
read-volatile(a), read(b)             // third thread

В этом случае, легко видеть , что существует непрерывная происходит до-цепь от write(b, 3) , чтобы read(b) . Кроме того, нет промежуточной записи в b . Таким образом, для этого сценария третий поток гарантированно видит, что b имеет значение 3 .

Сценарий № 2 - предположим, что update(1, 2) и update(3,4) перекрываются, а элементы чередуются следующим образом:

write(b, 3)                           // second thread
write(b, 1)                           // first thread
write-volatile(a, 2)                  // first thread
write-volatile(a, 4)                  // second thread
read-volatile(a), read(b)             // third thread

Теперь, в то время как есть происходит прежде , чем-цепь от write(b, 3) , чтобы read(b) , существует промежуточные write(b, 1) действие , выполняемое другой нить. Это означает, что мы не можем быть уверены, какое значение read(b) .

(Помимо этого: это демонстрирует, что мы не можем полагаться на volatile для обеспечения видимости энергонезависимых переменных, за исключением очень ограниченных ситуаций.)