Многопоточность и квантовая суперпозиция
Всем привет! Я java-нуб и сейчас эксперементирую с многопоточностью. Во время одного из экспериментов наткнулся на интересное поведение программы.
Собственно код:
public static void main(String[] args) {
Runnable r = () -> IMF.getFund();
Thread thread = new Thread(r);
Thread thread1 = new Thread(r);
thread.start();
while (true) {
//System.out.println("Бесконечность!");
if (IMF.imf != null) {
System.out.println("Состояние первого потока - " + thread.getState());
System.out.println("Состояние второго потока - " + thread1.getState());
System.out.println(IMF.imf);
break;
}
}
}
static class IMF {
static String imf;
static String getFund() {
imf = Math.random() * 1000 + " Это поток - " + Thread.currentThread().getName();
return imf;
}
}
}
В цикле я мониторю состояние переменной imf и когда она изменится вывожу сообщения. При таком исполнении программа попадает в некоторое состояние бесконечности и ничего не выводит в консоль (хотя дебаггером все завершается корректно).
Однако, если в цикл я добавляю наблюдателя (System.out.println("Бесконечность!");), то программа завершается корректно, с выводом всех сообщений.
Объясните пожалуйста почему так происходит?
Ответы (1 шт):
Проблема в том, что осуществляется доступ к imf из разных потоков. thread и thread1 пишут, а main читает. При этом изменения сделанные пишущими потоками, должным образом не публикуются (смотрите, например, раздел 5 в https://habr.com/ru/company/otus/blog/353414/ или https://dev.cheremin.info/2012/05/unsafe-publication.html).
То есть, читающий поток не видит изменений сделанных пищущими, и потому цикл не заканчивается. По сути читающий поток все время работает с локально закешированным значением imf. Добавление же System.out.println, очевидно, имеет побочный эффект, который приводит к публикации изменений.
Для того, чтобы все работало, как задумано, нужно воспользоваться одним из методов безопасной публикации, проще всего в данном случае объявить imf volatile:
static volatile String imf;