Задержка в выполнении потока из-за системы.из.заявление println [дубликат]


На этот вопрос уже есть ответ здесь:

В следующем коде, если я использую оператор sysout внутри цикла for, то код выполняется и входит в цикл после выполнения условия, но если я не использую оператор sysout внутри цикла, то бесконечный цикл продолжается без входя внутрь, если состояние, даже если если условие выполняется.. кто-нибудь, пожалуйста, может помочь мне выяснить точную причину этого. Просто констатация sysout сделать условие if, чтобы стать правдой. почему это так?

Код выглядит следующим образом: -

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;

   RunnableDemo( String name){
       threadName = name;
       System.out.println("Creating " +  threadName );
   }
   public void run() {
      System.out.println("Running " +  threadName );
       for(;;)
       {
           //Output 1: without this sysout statement.
           //Output 2: After uncommenting this sysout statement
           //System.out.println(Thread.currentThread().isInterrupted());
           if(TestThread.i>3)
           {
               try {
                   for(int j = 4; j > 0; j--) {
                       System.out.println("Thread: " + threadName + ", " + j);
                   }
               } catch (Exception e) {
                   System.out.println("Thread " +  threadName + " interrupted.");
               }
               System.out.println("Thread " +  threadName + " exiting.");
           }
       }
   }

   public void start ()
   {
      System.out.println("Starting " +  threadName );
      if (t == null)
      {
         t = new Thread (this, threadName);
         t.start ();
      }

   }

}

public class TestThread {
       static int i=0;
   public static void main(String args[]) {

       RunnableDemo R1 = new RunnableDemo( "Thread-1");
      R1.start();
      try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {

        e.printStackTrace();
    }

    i+=4;
     System.out.println(i);
   }   
}

Вывод без оператора sysout в бесконечном цикле: - введите здесь описание изображения

Вывод с помощью оператора sysout в бесконечном цикле: - введите здесь описание изображения

2 5

2 ответа:

Проблему здесь можно решить, изменив

static int i=0;

К

static volatile int i=0;

Создание переменной volatile имеет ряд сложных последствий, и я не эксперт в этом. Итак, я попытаюсь объяснить, как я думаю об этом.

Переменная

Живет в вашей основной памяти, вашей оперативной памяти. Но Оперативная память работает медленно, поэтому ваш процессор копирует ее в более быструю (и меньшую) память: кэш. Несколько тайников на самом деле, но это не имеет значения.

Но когда две нити на двух разные процессоры помещают свои значения в разные кэши, что происходит, когда значение изменяется? Ну, если поток 1 изменяет значение в кэше 1, поток 2 все еще использует старое значение из кэша 2. Если только мы не скажем обоим потокам, что эта переменная i может изменяться в любое время, как будто это волшебство. Вот что делает ключевое слово volatile.

Так почему же он работает с оператором print? Ну, заявление печати вызывает много кода за кулисами. Часть этого кода, скорее всего, содержит синхронизированный блок или другая переменная volatile, которая (случайно) также обновляет значение i в обоих кэшах. (Спасибо Marco13 за указание на это).

В следующий раз, когда вы попытаетесь получить доступ к i, вы получите обновленное значение!

PS: Я говорю RAM здесь, но это, вероятно, самая близкая общая память между двумя потоками, которая может быть кэшем, если они гиперпространственны, например.

Это тоже отличное объяснение (с картинки!):

Http://tutorials.jenkov.com/java-concurrency/volatile.html

Когда вы обращаетесь к значению переменной, изменения не записываются (или загружаются) каждый раз в фактическую ячейку памяти. Значение может быть загружено в регистр процессора или кэшировано и находиться там до тех пор, пока кэш не будет сброшен. Более того, поскольку TestThread.i не изменяется внутри цикла вообще, оптимизатор может решить просто заменить его проверкой перед циклом и полностью избавиться от оператора if (я не думаю, что это действительно происходит в вашем случае, но суть в том, что это не так). мог бы ).

Команда, которая заставляет поток очистить свои кэши и синхронизировать их с текущим содержимым физической памяти, называется барьер памяти. В Java есть два способа преодолеть барьер памяти: войти или выйти из блока synchronized или получить доступ к переменной volatile. Когда происходит одно из этих событий, кэшированные данные сбрасываются, и поток гарантированно видит актуальное представление содержимого памяти и имеет все внесенные изменения локально фиксируется в памяти.

Итак, как было предложено в комментариях, если вы объявите TestThread.i как volatile, проблема исчезнет, потому что всякий раз, когда значение изменяется, изменение будет немедленно зафиксировано,и оптимизатор будет знать, что не оптимизатор, e проверка вне цикла и не кэшировать значение.

Теперь, почему добавление инструкции print изменяет поведение? Ну, там много синхронизации происходит внутри io, поток попадает в барьер памяти где-то, и загружает свежую ценность. Это просто совпадение.