Java переменная видимость - PullRequest
5 голосов
/ 14 июля 2020

Вот мой код

public class Test {
    private static boolean running = true;

    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "get running:" + running);
            while (running) {
            }
            System.out.println(Thread.currentThread().getName() + "end");
        }, "t1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            running = false;
            System.out.println(Thread.currentThread().getName() + "change running to:" + running);
        }, "t2").start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "get running:" + running);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "get running:" + running);
        }, "t3").start();
    }
}

вывод на консоль

t1get running:true
t3get running:true
t2change running to:false
t3get running:false

, поэтому поток t1 застревает, а l oop. И я знаю, что если я изменю running на private static volatile boolean running, могу это исправить.

Я вопрос, t3 и t2 - разные потоки, почему t3 может получить новое значение running но t1 не может

EDIT1

@ andrew-tobilko сказал, что это возможно, потому что я вызываю Thread.sleep в теле l oop, поэтому я измените код t3

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "get running:" + running);
            long start = System.nanoTime();
            long now = System.nanoTime();
            while ((now-start)<3*1000L*1000L*1000L){
                now = System.nanoTime();
            }
            System.out.println(Thread.currentThread().getName() + "get running:" + running);
        }, "t3").start();

результат все тот же

Ответы [ 2 ]

6 голосов
/ 14 июля 2020

Если у вас есть переменная, которую вы хотите использовать для связи между потоками, тогда пометьте эту переменную как изменчивую :

private static volatile boolean running = true;

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

Добавление модификатора volatile к переменной предотвращает эту оптимизацию и заставляет оптимизатор Предположим, что эта переменная может измениться в любой момент по любой причине.

1 голос
/ 14 июля 2020

Похоже, что TimeUnit.SECONDS.sleep(3); в третьем потоке помогает перезагрузить running, а не брать его из кеша в регистрах.

Обратите внимание, что компилятор не обязан это делать.

В частности, компилятор не должен sh записывать кэшированные в регистры в общую память перед вызовом Thread.sleep или Thread.yield, , а также компилятору не нужно перезагружать значения кэшируется в регистрах после вызова Thread.sleep или Thread.yield.

https://docs.oracle.com/javase/specs/jls/se13/html/jls-17.html#jls -17.3

Так что все зависит от компилятора (и к возможной оптимизации).

Например, в следующем (сломанном) фрагменте кода предположим, что this.done является энергонезависимым логическим полем:

while (!this.done)
    Thread.sleep(1000);

Компилятор может прочитать поле this.done только один раз и повторно использовать кэшированное значение при каждом выполнении l oop. Это будет означать, что l oop никогда не завершится. , даже если другой поток изменил значение this.done.

* 102 8 * Если вы поместите оператор sleep внутри l oop в первом потоке, он также может получить новое значение running. Опять же, никаких гарантий.
...