2009-12-17 2 views
18

Я ищу образец кода из «Java Concurrency in Practice» Брайана Гетца. Он говорит, что возможно, что этот код останется в бесконечном цикле, потому что «значение« готово »никогда не станет видимым для потока читателя». Я не понимаю, как это может произойти ...вопрос о «Java Concurrency in Practice» пример

public class NoVisibility { 
    private static boolean ready; 
    private static int number; 

    private static class ReaderThread extends Thread { 
     public void run() { 
      while (!ready) 
       Thread.yield(); 
      System.out.println(number); 
     } 
    } 

    public static void main(String[] args) { 
     new ReaderThread().start(); 
     number = 42; 
     ready = true; 
    } 
} 

ответ

27

Поскольку ready не помечено как volatile и значение может быть кэшируются в начале цикла while, потому что она не изменяется в течение цикла while , Это один из способов оптимизации кода дрожания.

Так что возможно, что поток начинается до ready = true и читает ready = false, который кэширует этот поток локально и никогда не читает его снова.

Отъезд the volatile keyword.

+0

Если «готов» является изменчивым, но число не является, возможно ли, что число будет напечатано как 0? – zhiyuany

+0

Привет, Qberticus. Это просто разъяснение, если основной поток завершен до чтения нити, тогда у нас есть шанс напечатать номер 42 правильно? –

8

Причина объясняется в разделе, следующем за примером с образцом кода.

3.1.1 Устаревшие данные

NoVisibility продемонстрировал на из способов, которые недостаточно синхронизированные программы могут привести к неожиданным результатам: устаревшие данные. Когда читающая нить рассмотрит ready, она может видеть устаревшее значение. Если не используется синхронизация каждый раз, когда к переменной обращаются, можно увидеть устаревшее значение для этой переменной.

6

Модель памяти Java позволяет JVM оптимизировать ссылки доступ и такие, как если бы это однопоточное приложение, если поле не помечено как volatile или доступы с замком проводятся (история становится немного сложнее с блокировками на самом деле).

В примере, вы предоставили, виртуальная машина может сделать вывод, что ready поле не может быть изменен в течение текущего потока, поэтому было бы заменить !ready с false, вызывая бесконечный цикл. Маркировка поля как volatile заставит JVM каждый раз проверять значение поля (или, по крайней мере, обеспечивать, чтобы изменения ready распространялись на текущий поток).

+0

Чуть любопытно, почему JVM будет так небрежно заменить '! Ready' на' false'? Afterall 'ready' не является полем внутри' ReaderThread'. – sleepsort

+0

Потому что, если он не отмечен как изменчивый, JVM не обязана перечитывать его в соответствии с моделью памяти Java. Эта оптимизация (переменный подъем) на самом деле довольно распространена. – assylias

2
private static boolean ready; 
private static int number; 

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

Jeremy Manson and Brian Goetz:

В многопроцессорных системах, процессоры обычно имеют один или несколько слоев из кэш-памяти, что повышает производительность и за счет ускорения доступа к данным (поскольку данные ближе к процессору) и уменьшения трафика шина общей памяти (поскольку многие операции с памятью могут выполняться локальными кешами.) Кассеты памяти могут значительно улучшить производительность, но они представляют множество новых задач. Что, например, происходит, когда два процессора одновременно проверяют одну и ту же ячейку памяти? При каких условиях они будут видеть одно и то же значение?

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

+0

Спасибо всем - теперь имеет смысл. Таким образом, это специфично для того, как JVMs отображают код Java в исполняемый код. Я предполагаю, что подобные проблемы могут возникнуть в C++? Кто-нибудь знает, как C++ справляется с этой проблемой? –

4

Проблема связана с аппаратным обеспечением - каждый процессор имеет другое поведение в отношении когерентности кэш-памяти, видимости памяти и переупорядочения операций. Java здесь лучше, чем C++, потому что он определяет кросс-платформенную модель памяти, на которую могут рассчитывать все программисты. Когда Java работает в системе, модель памяти которой слабее той, которая требуется для модели памяти Java, JVM должна внести свой вклад.

Языки, подобные C "наследуют" модель памяти базового оборудования. Есть работа, чтобы дать C++ формальную модель памяти, чтобы программы на C++ могли означать одно и то же на разных платформах.

+0

Я понятия не имею, является ли это настоящим бразианским гетцем, но для того, чтобы действительно ехать в этом месте, я бы хотел увидеть пример, который на самом деле терпит неудачу. Я пробовал пример кода и не могу заставить его потерпеть неудачу даже после многих исполнений. Любой совет? – jbu

2
public class NoVisibility { 

    private static boolean ready = false; 
    private static int number; 

    private static class ReaderThread extends Thread { 

     @Override 
     public void run() { 
      while (!ready) { 
       Thread.yield(); 
      } 
      System.out.println(number); 
     } 
    } 

    public static void main(String[] args) throws InterruptedException { 
     new ReaderThread().start(); 
     number = 42; 
     Thread.sleep(20000); 
     ready = true; 
    } 
} 

Поместите Thread.sleep() вызов в течение 20 секунд, что будет происходить в JIT начнется в течение этих 20 секунд, и он будет оптимизировать проверку и кэшировать значение или удалить состояние в целом. И поэтому код не будет отображаться.

Чтобы прекратить это, вы ДОЛЖНЫ использовать volatile.

+0

это не * кажется * терпеть неудачу после десятков испытаний. используя java 1.8 – jbu

Смежные вопросы