Почему Java не видит обновленное значение из другого потока?
пожалуйста, посмотрите на этот код (взятый из эффективной книги Java)
import java.util.concurrent.TimeUnit;
public class Main {
private static boolean stopReq;
public static void main(String[] args) throws InterruptedException {
Thread bgw = new Thread(new Runnable()
{
public void run(){
int i = 0;
while(!stopReq){ i++;}
}
});
bgw.start();
TimeUnit.SECONDS.sleep(1);
stopReq = true;
}
}
почему bgw
поток застрял в бесконечном цикле? Это кэширование собственной копии stopReq
когда он достиг петли? Поэтому он никогда не видит обновленное значение из другого потока?
Я понимаю, что решением этой проблемы будет синхронизация или изменчивая переменная, но мне любопытно, почему эта текущая реализация не работает.
спасибо
5 ответов
Ваше объяснение право.
компилятор обнаруживает, чем stopReq
никогда не изменяется в цикле, и поскольку это не volatile
, оптимизирует while(!stopReq)
поручение while(true)
.
даже если значение изменяется позже, поток даже не читает его больше.
вы должны прочитать больше о Модель Памяти Java чтобы лучше понять все последствия.
на stopReq переменная, не являющаяся изменчивой или включенной в синхронизированный блок, дает VM свободу использовать оптимизированное локальное хранилище (например. регистры и т. д.), который не гарантирует немедленного распространения изменений по потокам.когда вы объявляете переменную как летучие VM будет убедиться, что после каждого переменная написать "барьер записи памяти" вставляется, что заставит все локальные изменения быть пролитыми в реальное место памяти, таким образом, делая его видимым для всех других потоков (тот же барьер помещается в конце синхронизированного блока, например.)
сделайте stopReq равным true, тогда он будет остановлен. Вы снова устанавливаете stopReq в false, из-за того, что условие цикла всегда истинно, и оно находится в бесконечном цикле.
Я проверил это, и нет, переменные одинаковы. Пример также компилируется для меня.
ошибка здесь:
ваш цикл while продолжается, пока !stopReq истинно, это означает, что stopReq ложно. И через 1 сек вы устанавливаете stopReq в false - это ничего не меняет. Если вы установите его в true, !stopReq станет false, и ваш цикл закончится.
чтобы быть очень конкретным в вашем запросе, чтобы в полной мере использовать производительность современного многопроцессорного оборудования, при отсутствии синхронизации JVMs разрешила компилятору переупорядочивать операции и значения кэша в регистрах и в конкретных кэшах процессора. Поскольку основной поток записывает в stopReq без синхронизации, поэтому из-за переупорядочения и кэширования поток BTW может никогда не увидеть записанное значение и цикл навсегда.
при использовании синхронизации или volatile они гарантируют видимость и заставляют компилятор не кэшировать и не сбрасывать изменения в основную память.