Разделяются ли статические переменные между потоками? - PullRequest
89 голосов
/ 08 февраля 2011

Мой учитель из класса Java по многопоточности сказал что-то, в чем я не был уверен.

Он заявил, что следующий код не обязательно обновит переменную ready. По его словам, два потока не обязательно совместно используют статическую переменную, особенно в случае, когда каждый поток (основной поток против ReaderThread) работает на своем собственном процессоре и, следовательно, не использует одни и те же регистры / кэш / и т. и один процессор не будет обновлять другой.

По сути, он сказал, что возможно, что ready обновляется в главном потоке, но НЕ в ReaderThread, так что ReaderThread будет зацикливаться бесконечно.

Он также утверждал, что программа могла напечатать 0 или 42. Я понимаю, как можно печатать 42, но не 0. Он упомянул, что это будет случай, когда переменная number установлена ​​в значение по умолчанию.

Я подумал, что, возможно, не гарантируется, что статическая переменная обновляется между потоками, но это кажется мне очень странным для Java. Устраняет ли проблема ready volatile эту проблему?

Он показал этот код:

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;  
    }  
}

Ответы [ 7 ]

73 голосов
/ 08 февраля 2011

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

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

Вот цитата из этой ссылки (предоставлена ​​в комментарии Джедом Уэсли-Смитом):

Глава 17 Спецификации языка Java определяет отношение «до того» для операций с памятью, таких как чтение и запись общих переменных. Результаты записи одним потоком гарантированно будут видимы для чтения другим потоком, только если операция записи происходит до операции чтения. Синхронизированные и изменчивые конструкции, а также методы Thread.start () и Thread.join () могут образовывать отношения «до того». В частности:

  • Каждое действие в потоке происходит перед каждым действием в этом потоке, которое происходит позже в порядке программы.

  • Разблокировка (выход синхронизированного блока или метода) монитора происходит перед каждой последующей блокировкой (вход синхронизированного блока или метода) того же монитора. А поскольку отношение «происходит до» является транзитивным, все действия потока перед разблокировкой происходят до всех действий, следующих за любой блокировкой потока этим монитором.

  • Запись в энергозависимое поле происходит перед каждым последующим чтением того же поля. Запись и чтение энергозависимых полей имеют эффекты согласованности памяти, аналогичные входящим и выходящим мониторам, но не влекут за собой взаимную блокировку исключения.

  • Вызов для запуска в потоке происходит до любого действия в запущенном потоке.

  • Все действия в потоке происходят до того, как какой-либо другой поток успешно вернется из соединения в этом потоке.

34 голосов
/ 08 февраля 2011

Он говорил о видимости и не следует понимать слишком буквально.

Статические переменные действительно разделяются между потоками, но изменения, сделанные в одном потоке, могут быть невидимы для другого потока сразу, из-за чего создается впечатление, что существует две копии переменной.

Эта статья представляет точку зрения, которая соответствует тому, как он представил информацию:

Во-первых, вы должны кое-что понять о модели памяти Java. Я изо всех сил пытался объяснить это кратко и хорошо. На сегодняшний день лучший способ описать это, если представить это так:

  • Каждый поток в Java происходит в отдельном пространстве памяти (это явно не соответствует действительности, так что потерпите меня на этом).

  • Вам необходимо использовать специальные механизмы, чтобы гарантировать, что связь между этими потоками происходит так же, как в системе передачи сообщений.

  • Операции записи в память, которые происходят в одном потоке, могут «просачиваться» и просматриваться другим потоком, но это ни в коем случае не гарантируется. Без явного сообщения вы не можете гарантировать, какие записи будут видны другим потокам, или даже порядок их просмотра.

...

thread model

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

12 голосов
/ 08 февраля 2011

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

Поэтому Java определяет Модель памяти , которая указывает, при каких условиях потокиможет видеть согласованное состояние общих данных.

В вашем конкретном случае добавление volatile гарантирует видимость.

7 голосов
/ 08 февраля 2011

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

И теоретически записи, сделанные другим потоком, могут выглядеть в другом порядке, если только переменные не объявлены volatile или записи явно синхронизированы.

4 голосов
/ 08 февраля 2011

В одном загрузчике классов статические поля всегда используются совместно. Чтобы явным образом охватить данные потоками, вам нужно использовать средство типа ThreadLocal.

2 голосов
/ 19 апреля 2018

Когда вы инициализируете переменную статического типа примитива, java default назначает значение для статических переменных

public static int i ;

, когда вы определяете переменную, как это, значение по умолчанию i = 0;поэтому есть возможность получить 0, после чего основной поток обновляет значение boolean ready to true.поскольку ready является статической переменной, основной поток и другой поток ссылаются на один и тот же адрес памяти, поэтому переменная ready изменяется.поэтому вторичный поток выходит из цикла while и печатает значение.при печати значения инициализируемое значение числа равно 0. если процесс потока прошел цикл while перед переменной числа обновления основного потока.тогда есть возможность распечатать 0

0 голосов
/ 11 февраля 2011

@ dontocsata Вы можете вернуться к своему учителю и немного обучить его:)

несколько заметок из реального мира и независимо от того, что вы видите или вам говорят.ОБРАТИТЕ ВНИМАНИЕ, приведенные ниже слова относятся к этому конкретному случаю в указанном точном порядке.

Следующие 2 переменные будут находиться в одной и той же строке кэша практически любой известной архитектуры.

private static boolean ready;  
private static int number;  

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

Запущенный поток ReaderThreadсобирается поддержать процесс, так как он не демон!Таким образом, ready и number будут сброшены вместе (или число до, если произойдет переключение контекста), и нет никакой реальной причины для переупорядочения в этом случае, по крайней мере, я даже не могу думать об одном.Вам нужно что-то действительно странное, чтобы увидеть что-либо, кроме 42.Я снова предполагаю, что обе статические переменные будут находиться в одной строке кэша.Я просто не могу представить строку кэша длиной 4 байта ИЛИ JVM, которая не будет назначать их в непрерывной области (строка кэша).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...