2016-04-03 5 views
7

Я пытаюсь понять концепцию Java happens-before order, и есть несколько вещей, которые кажутся очень запутанными. Насколько я могу судить, раньше это всего лишь заказ на набор действий и не дает никаких гарантий относительно порядка выполнения в реальном времени. На самом деле (подчеркнуть мое):Понимание происходит до и синхронизации

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

Таким образом, все это говорит, что если есть два действия w (запись) и r (чтение), такие, что ро (ш, г), чем rмогущества на самом деле происходят до w в исполнении, но нет никакой гарантии, что это произойдет. Кроме того, запись w наблюдается путем считывания r.

Как я могу определить, что два действия выполняются впоследствии во время выполнения? Например:

public volatile int v; 
public int c; 

Действия:

Thread A 
v = 3; //w 

Thread B 
c = v; //r 

Здесь мы имеем hb(w, r), но это не означает, что c будет содержать значение 3 после назначения. Как мне обеспечить, чтобы c был назначен с 3? Предоставляют ли такие гарантии synchronization order?

ответ

8

Когда JLS говорит, что какое-то событие X в потоке A устанавливает происходит до отношений с событием Y в нити B, это не означает, что X произойдет до Y.

Это означает, что IF X происходит до Y, то оба потока согласны, что X произошло до Y. то есть, оба потока будут видеть память программы в состоянии, которое соответствует X происходит прежде, чем Y.


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

Это отлично работает, когда потоки не взаимодействуют друг с другом, но это вызывает проблемы, когда они действительно хотят взаимодействовать. Если поток A сохраняет значение в обычную переменную, Java не гарантирует, когда (или даже , если) нить B увидит изменение значения.

Для того, чтобы преодолеть эту проблему, когда это важно, Java дает вам определенные средства: синхронизация потоков. То есть, чтобы потоки согласовали состояние памяти программы. Ключевое слово volatile и ключевое слово synchronized - это два способа установления синхронизации между потоками.


Я думаю, что причина, почему они называют это «происходит раньше», чтобы подчеркнуть переходную природу отношений: если вы можете доказать, что А происходит до B, и вы можете доказать, что B происходит до С, то согласно с правилами, указанными в JLS, вы доказали, что происходит перед С.

+0

@Eugene, спасибо. Я обновил свой ответ, чтобы отразить ваш комментарий. –

4

Я хотел бы связать вышеуказанный оператор с некоторым примером потока кода.

Чтобы понять это, возьмем ниже класс, который имеет два поля: counter и isActive.

class StateHolder { 
    private int counter = 100; 
    private boolean isActive = false; 

    public synchronized void resetCounter() { 
      counter = 0; 
      isActive = true; 
    } 

    public synchronized void printStateWithLock() { 
     System.out.println("Counter : " + counter); 
     System.out.println("IsActive : " + isActive); 
    } 

    public void printStateWithNoLock() { 
     System.out.println("Counter : " + counter); 
     System.out.println("IsActive : " + isActive); 
    } 
} 

И предположим, что есть три нити T1, T2, T3 вызвать следующие методы на одном объекте StateHolder:

T1 называет resetCounter() и Т2 вызывает printStateWithLock() в то же время и Т1 получает блокировку
T3 -> вызывает printStateWithNoLock() после T1 завершения его выполнения

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

и непосредственная линия говорит,

В соответствии с вышеуказанным заявлением, что дает гибкость для виртуальной машины Java, OS или базового оборудования, чтобы изменить порядок утверждения в рамках метода resetCounter(). И по мере запуска T1 он может выполнять инструкции в следующем порядке.

public synchronized void resetCounter() { 
      isActive = true; 
      counter = 0; 
    } 

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

Теперь, рассматривая его с точки зрения T2, это переупорядочение не оказывает никакого отрицательного воздействия, поскольку оба T1 и T2 синхронизируются на одном и том же объекте, и T2 гарантированно увидит изменения изменений в обоих полях, независимо от того, о том, произошло ли переупорядочение или нет, как это происходит - до отношений. Так выход всегда будет:

Counter : 0 
IsActive : true 

Это согласно заявлению, Если переназначения дает результаты в соответствии с юридическим оформлением, это не является незаконным

Но смотреть на него с точки зрения T3, с это переупорядочивает возможность того, что T3 увидит обновленное значение isActive как «true but still see the счетчик value as 100`, хотя T1 завершил выполнение.

Counter : 100 
IsActive : true 

Следующая точка в указанной выше ссылке далее уточняет заявление и говорит, что:

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

В этом примере T3 столкнулся с этой проблемой, так как ее не происходит - до отношения с T1 или T2. Это inline с Не обязательно должно появляться в том порядке, в каком порядке, с каким кодом, с которым они не имеют отношения «произойти раньше».

ПРИМЕЧАНИЕ: Для упрощения случая у нас есть одна нить T1, изменяющая состояние, а T2 и T3 считывают состояние. Можно иметь

обновления T1 counter to 0, позже
T2 изменяет IsActive в true и видит counter is 0, после того, как когда-то
T3, который печатает состояние все еще мог видеть only isActive as true but counter is 100, хотя оба T1 и T2 завершили выполнение.

Что касается последнего вопроса:

мы имеем Hb (ш, г), но это не значит, что с будет содержать значение 3 после присвоения. Как обеспечить, чтобы c был назначен 3?

public volatile int v; 
public int c; 

Thread A 
v = 3; //w 

Thread B 
c = v; //r 

Поскольку v является летучим, согласно Happens-before Order

Запись в летучем поле (§8.3.1.4) происходит-перед каждым последующим чтения этого поля.

Так что можно с уверенностью предположить, что, когда Thread B пытается прочитать переменную v всегда будет читать обновленное значение и c будут назначены 3 в приведенном выше коде.

+0

Один вопрос. Почему вы не объявили поля неустойчивыми? Безопасно ли использовать их таким образом? Я думаю, что поток, который выполняет построение объекта ... Я имею в виду, что изменения, сделанные потоком ('counter = 100'), могут не наблюдаться ни одним потоком, вызывающим метод объекта (не происходит до отношения). Может, я не совсем понял твою идею ...? –

+0

@ St.Antario [JLS # 8.8.3] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.8.3) указывает, что ** он блокирует строящийся объект, который, как правило, не доступен для других потоков, пока все конструкторы для объекта не завершили свою работу. ** Таким образом, по ссылке выше, потоки будут видеть только полностью инициализированный объект только после завершения конструктора и JVM заботится об этом. Если мы не опубликуем объект в конструкторе и не убедимся, как в разделе «Публикация и побег» книги «Совместимость Java на практике». –

+0

Да, он блокируется, но в нем четко не указано, что блокировка действует как «synchronized {...}». Я думал, они имели в виду, что операция по строительству является атомной, и все. Никаких гарантий видимости памяти не предоставляется. –

2

Устный ответ @James' мне по душе:

// Definition: Some variables 
private int first = 1; 
private int second = 2; 
private int third = 3; 
private volatile boolean hasValue = false; 

// Thread A 
first = 5; 
second = 6; 
third = 7; 
hasValue = true; 

// Thread B 
System.out.println("Flag is set to : " + hasValue); 
System.out.println("First: " + first); // will print 5 
System.out.println("Second: " + second); // will print 6 
System.out.println("Third: " + third); // will print 7 

, если вы хотите, чтобы состояние/значение памяти (память и кеш процессора). ееп в время заявления записи переменного по одной нити,

Состояния памяти видно на hasValue=true (заявление записи) в потоке А:

first, имеющее значение 5, second, имеющее значение 6 , third, имеющий значение 7

быть видно из каждого последующего (почему последующего, даже если только один читать резьбы B в этом примере? У многих из нас есть Thread C, который делает точно , аналогичный Thread B) читает инструкцию той же переменной другой нитью , затем отметьте эту переменную volatile.

Если X (hasValue=true) в Пронизывайте происходит до Y (sysout(hasValue)) в Thread B, поведение должно быть, как если Х произошло до Y в том же потоке (значения памяти увиденные на X должно быть той же стартовой от Y)

+0

https://dzone.com/articles/java-multi-threading-volatile-variables-happens-be-1 – user104309

1

Здесь у нас есть hb (w, r), но это не означает, что c будет содержать значение 3 после присвоения. Как обеспечить, чтобы c был назначен 3? Обеспечивают ли такие гарантии гарантии синхронизации?

И ваш пример

public volatile int v; 
public int c; 
Actions: 

Thread A 
v = 3; //w 

Thread B 
c = v; //r 

Вам не нужно volatile для v в вашем примере. Давайте посмотрим на аналогичный пример

int v = 0; 
int c = 0; 
volatile boolean assigned = false; 

Действия:

Нитки

v = 3; 
assigned = true; 

Thread B

while(!assigned); 
c = v; 
  1. assigned Поле неустойчиво.
  2. У нас будет c = v заявление в Thread B только после assigned будет true (while(!assigned) несет ответственность за это).
  3. если есть volatile - есть happens before.
  4. happens before означает, что если мы увидим assigned == true - мы увидим все, что произошло до выписки assigned = true: мы увидим v = 3.
  5. Итак, когда у нас есть assigned == true -> у нас есть v = 3.
  6. В результате у нас есть c = 3.

Что будет происходить без volatile

int v = 0; 
int c = 0; 
boolean assigned = false; 

Действия:

Нитки

v = 3; 
assigned = true; 

Thread B

while(!assigned); 
c = v; 

Мы assigned без volatile сейчас.

Значение c в Thread B может быть равно 0 или 3 в такой ситуации. Таким образом, нет никаких гарантий, что c == 3.

 Смежные вопросы

  • Нет связанных вопросов^_^