Почему volatile в java 5+ не обеспечивает видимость из другого потока?
по:
http://www.ibm.com/developerworks/library/j-jtp03304/
В новой модели памяти, когда поток A записывает в изменчивую переменную V, а поток B считывает из V, любые значения переменных, которые были видны A во время записи V, теперь гарантированно будут видны B
и многие места в интернете утверждают, что следующий код никогда не должен печатать "ошибка":
public class Test {
volatile static private int a;
static private int b;
public static void main(String [] args) throws Exception {
for (int i = 0; i < 100; i++) {
new Thread() {
@Override
public void run() {
int tt = b; // makes the jvm cache the value of b
while (a==0) {
}
if (b == 0) {
System.out.println("error");
}
}
}.start();
}
b = 1;
a = 1;
}
}
b
должны быть 1 для всех потоков, когда a
это 1.
4 ответов
обновление:
для всех заинтересованных эта ошибка была устранена и исправлена для Java 7u6 build b14. Вы можете увидеть отчет об ошибке / исправления здесь
Оригинальный Ответ
при мышлении в терминах памяти видимость / порядок вам нужно будет подумать о том, что происходит перед отношениями. Важное предварительное условие для b != 0
на a == 1
. Если a != 1
тогда b может быть либо 0, либо 1.
как только поток видит a == 1
тогда этот поток гарантированно увидит b == 1
.
Post Java 5, в Примере OP, после while(a == 0)
breaks out b гарантированно будет 1
Edit:
я запускал симуляцию много раз и не делал смотрите ваши результаты.
какую ОС, версию Java и процессор вы тестируете?
я на Windows 7, Java 1.6_24 (пытаюсь с _31)
Edit 2:
престижность OP и Walter Laan - для меня это произошло только тогда, когда я переключился с 64-битной Java на 32-битную Java, на (но не может быть исключено) 64-битную windows 7.
Edit 3:
задание tt
, или, скорее, staticget из b
кажется, имеет значительное влияние (чтобы доказать это, удалите int tt = b;
и это всегда должно работать.
появляется нагрузка b
на tt
будет хранить поле локально, которое затем будет использоваться в if coniditonal (ссылка на это значение не tt
). Так что если b == 0
верно, это, вероятно, означает, что местный магазин tt
было 0 (на данный момент его гонка, чтобы назначить 1 местному tt
). Это похоже только на 32-битную Java 1.6 & 7 с набором клиента.
я сравнил две выходные сборки, и непосредственная разница была здесь. (Имейте в виду, что это фрагменты).
это напечатано "ошибка"
0x021dd753: test %eax,0x180100 ; {poll}
0x021dd759: cmp x0,%ecx
0x021dd75c: je 0x021dd748 ;*ifeq
; - Test::run@7 (line 13)
0x021dd75e: cmp x0,%edx
0x021dd761: jne 0x021dd788 ;*ifne
; - Test::run@13 (line 17)
0x021dd767: nop
0x021dd768: jmp 0x021dd7b8 ; {no_reloc}
0x021dd76d: xchg %ax,%ax
0x021dd770: jmp 0x021dd7d2 ; implicit exception: dispatches to 0x021dd7c2
0x021dd775: nop ;*getstatic out
; - Test::run@16 (line 18)
0x021dd776: cmp (%ecx),%eax ; implicit exception: dispatches to 0x021dd7dc
0x021dd778: mov x39239500,%edx ;*invokevirtual println
и
это не напечатало "ошибку"
0x0226d763: test %eax,0x180100 ; {poll}
0x0226d769: cmp x0,%edx
0x0226d76c: je 0x0226d758 ;*ifeq
; - Test::run@7 (line 13)
0x0226d76e: mov x341b77f8,%edx ; {oop('Test')}
0x0226d773: mov 0x154(%edx),%edx ;*getstatic b
; - Test::access@0 (line 3)
; - Test::run@10 (line 17)
0x0226d779: cmp x0,%edx
0x0226d77c: jne 0x0226d7a8 ;*ifne
; - Test::run@13 (line 17)
0x0226d782: nopw 0x0(%eax,%eax,1)
0x0226d788: jmp 0x0226d7ed ; {no_reloc}
0x0226d78d: xchg %ax,%ax
0x0226d790: jmp 0x0226d807 ; implicit exception: dispatches to 0x0226d7f7
0x0226d795: nop ;*getstatic out
; - Test::run@16 (line 18)
0x0226d796: cmp (%ecx),%eax ; implicit exception: dispatches to 0x0226d811
0x0226d798: mov x39239500,%edx ;*invokevirtual println
в этом примере первая запись из запуска, который напечатал "ошибку", а второй был из того, который не сделал.
кажется, что рабочий запуск загружен и назначено b
правильно перед тестированием он равен 0.
0x0226d76e: mov x341b77f8,%edx ; {oop('Test')}
0x0226d773: mov 0x154(%edx),%edx ;*getstatic b
; - Test::access@0 (line 3)
; - Test::run@10 (line 17)
0x0226d779: cmp x0,%edx
0x0226d77c: jne 0x0226d7a8 ;*ifne
; - Test::run@13 (line 17)
в то время как запуск, который напечатал "ошибку", загрузил кэшированную версию %edx
0x021dd75e: cmp x0,%edx
0x021dd761: jne 0x021dd788 ;*ifne
; - Test::run@13 (line 17)
для тех, кто имеет больше опыта работы с ассемблером, пожалуйста, взвесьте:)
изменить 4
должно быть моим последним редактированием, так как параллелизм dev получает руку на нем, я тестировал С и без
int tt = b;
назначение еще немного. Я обнаружил, что когда я увеличиваю max от 100 до 1000 кажется, что частота ошибок 100%, когда int tt = b
включен и 0% шанс, когда он исключен.
основываясь на выдержке из JCiP ниже, я бы подумал, что ваш пример никогда не должен печатать "error":
эффекты видимости изменчивых переменных выходят за пределы значения самой изменчивой переменной. Когда нить A записывает в переменную volatile и затем thread B читает ту же переменную, значения все переменные, которые были видны A до записи в volatile переменная становится видимой для B после чтения переменной volatile.
возможно, вы захотите проверить дискуссионный поток в списке рассылки параллелизма по этому вопросу:http://cs.oswego.edu/pipermail/concurrency-interest/2012-May/009440.html
похоже, что проблема более легко воспроизводится с клиентом JVM (- client).
на мой взгляд, проблема возникла из-за отсутствия синхронизация :
обратите внимание : Если b=1 heppens перед a=1, а A является изменчивым, А b нет, то b=1 фактически обновляется для всех потоков только после завершения a=1 (согласно логике quate).
то, что heppend в вашем коде, заключается в том, что b=1 был сначала обновлен только для основного процесса, а затем только после завершения Летучего назначения все потоки B обновлены. Я думаю, что возможно назначения volatile не работают как атомарные операции (нужно указать далеко и как-то обновить остальные ссылки, чтобы действовать как летучие), поэтому я бы предположил, почему один поток читает b=0 вместо b=1.
рассмотрим это изменение кода, которое показывает мое утверждение:
public class Test {
volatile static private int a;
static private int b;
private static Object lock = new Object();
public static void main(String [] args) throws Exception {
for (int i = 0; i < 100; i++) {
new Thread() {
@Override
public void run() {
int tt = b; // makes the jvm cache the value of b
while (true) {
synchronized (lock ) {
if (a!=0) break;
}
}
if (b == 0) {
System.out.println("error");
}
}
}.start();
}
b = 1;
synchronized (lock ) {
a = 1;
}
}
}