2011-12-21 2 views
4

Я новичок в Java, и следующее может быть очевидным, но это меня озадачивает. Рассмотрим следующий код:Java: оператор if в бесконечном цикле

while(1>0){ 
    if(x!=0){ 
    //do something 
    } 
} 

Переменная x изменяется в другом потоке. Однако код в операторе if никогда не выполняется, даже если x не равен нулю. Если изменить код следующим

while(1>0){ 
    System.out.println("here"); 

    if(x!=0){ 
    //do something 
    } 
} 

код в заявлении, если не теперь выполняется при х больше не равна нулю. Я подозреваю, что это связано с правилами компилятора Java, но это очень сбивает меня с толку. Любая помощь, разъясняющая это, будет очень признательна.

+0

'x' является INT? или любой объект-обертку? – Vaandu

+2

просто FYI, и вы можете это знать уже: вы можете сделать 'while (true)', если вы хотите, чтобы цикл while был всегда запущен. – Joel

+0

x является char и принадлежит объекту X. В свою очередь, X является статическим членом класса. Я понимаю, что это не лучший способ сделать это для моих целей, но я был удивлен результатами. – Ivan

ответ

2

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

+0

Вы правы, объявляя x как volatile, чтобы первый код выполнялся правильно. Но почему второй код не влияет на то, как объявляется x? – Ivan

0

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

private volatile int x; 
+0

Почему «System.out.println» («здесь»), 'компилятор не должен оптимизировать' if'? –

7

Если x изменяется в другом потоке, то вы, вероятно, видя побочный эффект того, что вы не синхронизированного доступа к этой переменной ,

Модель памяти Java и потоковой передачи довольно сложная, поэтому я рекомендую вам получить копию Java Concurrency in Practice от Brain Goetz и прочитать.

Короткий ответ, чтобы убедиться, что доступ к x заключен в synchronized блоке:

while (1 > 0) { 
    int temp; 
    synchronized (this) { 
     temp = x; 
    } 
    if (temp != 0) { 
     // Do something 
    } 
} 

И точно так же в коде, который модифицирует x.

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

В качестве альтернативы вы можете просто объявить xvolatile, которого, вероятно, будет достаточно для вашего использования. Я предлагаю вам пойти с версией synchronized, потому что вам в конечном итоге нужно будет знать, как правильно использовать , чтобы вы могли изучить его сейчас.

+0

Благодарим вас за ответ. Не могли бы вы также объяснить, почему второй код, который я работал без необходимости волатильности или синхронизации? – Ivan

+0

Я не знаю, почему он работал со второй версией. Ошибки синхронизации тонкие и трудно диагностируются именно по этой причине: иногда все работает, иногда они этого не делают. Вы часто не обнаружите ошибку синхронизации, пока ваш код не будет запущен в течение некоторого времени. Возможно, факт, что 'System.out.println' очень медленный, означает, что JVM имеет возможность обновить модель памяти потока, но это чистая спекуляция. –

0

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

1

Причина, по которой ничего не происходит без System.out.println("here");, объясняется ответом Камерона Скиннера.

Так почему же блок внутри if(x!=0) работает, когда используется println? println выполняет блок synchronized (см. PrintStream.write(String s)). Это заставляет текущий поток извлекать состояние System.out из основной памяти и обновлять кеш потока, прежде чем позволить потоку выполнить любую дальнейшую строку кода.Неожиданным побочным эффектом является то, что также сохраняются состояния других переменных, таких как ваш x, но блокировка x не включалась в синхронизацию. Это называется с помидорами.

Если я буду использовать свободный текст, чтобы описать формальности, описанные в Java Memory Model Specification: он говорит, что операции выполняются до того, как выпуск замка произойдет, перед тем операций выполняются после очередного получения этого замка.

Я продемонстрирую это на примере. Предположим, что выполняется Thread 1, и только когда он заканчивается, начинается Thread 2. Также предположим, что x, y и z являются переменными, разделяемыми обоими потоками. Обратите внимание, что мы можем определить значение z только внутри блока synchronizedy.

Thread 1: 
x = 0; 
synchronized(y) { 
} 

Thread 2: 
x = 1 
z = x; 
    // here there's no guarantee as to z value, could be 0 or 1 
synchronized(y) { 
    z = x; 
     // here z has to be 0! 
} 

Это, конечно, очень плохая практика полагаться на синхронизацию ...

+0

Спасибо, это имело большой смысл! – Ivan

+0

Еще один вопрос, однако, почему это не утверждение if, доступное даже после того, как поток, который модифицирует x, сделан? – Ivan

+0

Спецификация JMM не подразумевает никаких гарантий по порядку операций, которые происходят в двух разных потоках, по завершении потока. Но он гарантирует заказ при синхронизации. – yair

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