Разделяются ли статические переменные между потоками?

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

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

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

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

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

открытый класс NoVisibility {  
    приватный статический логический готовый;  
    закрытый статический номер int;  
    закрытый статический класс ReaderThread расширяет Thread {   
        public void run() {  
            while (!ready)   Thread.yield();  
            System.out.println(число);  
        }  
    }  
    public static void main(String[] args) {  
        новый ReaderThread().start();  
        число = 42;  
        готов = правда;  
    }  
}

7 ответов

Решение

Нет проблем с видимостью, характерных для статических переменных. Существует проблема видимости, навязанная моделью памяти JVM. Вот статья, рассказывающая о модели памяти и о том, как записи становятся видимыми для потоков. Вы не можете рассчитывать на изменения, которые один поток делает видимыми для других потоков своевременно (на самом деле JVM вообще не обязана делать эти изменения видимыми для вас), если только вы не установили связь "до того, как это произошло", вот цитата из эта ссылка (предоставлена ​​в комментарии Джеда Уэсли-Смита):

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

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

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

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

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

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

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

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

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

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

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

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

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

...

модель потока

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

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

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

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

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

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

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

При инициализации статической переменной типа примитива java по умолчанию присваивает значение статическим переменным

public static int i ;

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

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

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

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

private static boolean ready;  
private static int number;  

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

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

Другие вопросы по тегам