2016-12-11 13 views
2

Я хотел бы понять, что происходит с точки зрения кеша (протокол MESI), если программист забыл добавить ключевое слово volatile в переменную, используемую для синхронизации.Неработающая синхронизация без изменчивой переменной

Следующий фрагмент кода просто останавливается после нескольких итераций. Я знаю, что это потому, что один из 2 потоков (предположим, THREAD 0) не видит обновление, сделанное переменной current THREAD 1, сделанное getNext(), поэтому оно продолжает цикл навсегда.

Однако я не понимаю, почему это так. THREAD 0 зацикливается на current и в какой-то момент должен увидеть, что линия кэша была обновлена ​​THREAD 1 (линия кэша переключена на Модифицированное состояние) и выдает сообщение «Чтение» на шине памяти, чтобы извлечь ее в свой локальный кеш. Добавляя модификатор volatile в переменную current, все будет работать нормально.

Что происходит с тем, чтобы предотвратить РЕЗЬБО 0 продолжить выполнение?

Ссылка: Memory Barriers: a Hardware View for Software Hackers

public class Volatile { 
    public static final int THREAD_0 = 0; 
    public static final int THREAD_1 = 1; 
    public static int current; 
    public static int counter = 0; 

    public static void main(String[] args) { 
     current = 0; 

     /** Thread 0 **/ 
     Thread thread0 = new Thread(() -> { 
      while(true) { /** THREAD_0 */ 
       while (current != THREAD_0); 

       counter++; 
       System.out.println("Thread0:" + counter); 

       current = getNext(THREAD_0); 
      } 
     }); 

     /** Thread 1 **/ 
     Thread thread1 = new Thread(() -> { 
      while(true) { /** THREAD_1 */ 
       while (current != THREAD_1); 

       counter++; 
       System.out.println("Thread1:" + counter); 

       current = getNext(THREAD_1); 
      } 
     }); 

     thread0.start(); 
     thread1.start(); 
    } 

    public static int getNext(int threadId) { 
     return threadId == THREAD_0 ? THREAD_1 : THREAD_0; 
    } 
} 

ответ

0

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

Если программист не добавляет указаний (например, используя синхронизированные или летучие ключевые слова), что переменная может быть разделена между потоками, JIT разрешает предположить, что ни один другой поток не модифицирует эту переменную, и он может оптимизировать соответственно , Байт-код, который проверяет эту переменную, может быть переупорядочен радикально. Если JIT переупорядочивает код достаточно, тогда, когда аппаратное обеспечение обнаруживает модификацию и получает новое значение, может не иметь значения.

Цитируя Java Параллелизм на практике 3.1:

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

(Есть несколько мест в книге JCIP делает дело, что рассуждения о недостаточно синхронизированной коде бесполезно.)

+1

Вы совершенно правы, эта проблема связана с JIT компилятором. Запуск программы только в интерпретируемом режиме (-Xint) заставит работать отлично навсегда, тогда как в скомпилированном режиме (-Xcomp) он просто не работает. Спасибо за этот ответ! –