Почему 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
.начиная с Java 5, в ФП, например, после того, как
while(a == 0)
вспыхивает b гарантированно будет 1Edit:
я запустил моделирование много раз и не сделал смотрите свой вывод.
какую ОС, версию 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)
для тех, кто имеет больше опыта работы с ассемблером, пожалуйста, взвесьте:)
Edit 4
должно быть мое последнее редактирование, так как параллелизм dev получает руку на нем, я тестировал С и без
int tt = b;
назначение еще немного. Я обнаружил, что когда я увеличиваю Макс от 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; } } }