Статические переменные разделяемые между потоками?
мой учитель в классе java верхнего уровня по потоковой передаче сказал что-то, в чем я не был уверен.
он заявил, что следующий код не обязательно обновлять ready
переменной. По его словам, два потока не обязательно разделяют статическую переменную, особенно в случае, когда каждый поток (основной поток против ReaderThread) работает на своем собственном процессоре и, следовательно, не разделяет те же регистры / кэш / etc и один процессор не будет обновлять другой.
по существу, он сказал, что возможно, что 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 ответов
нет проблемы видимости, специфичной для статических переменных. Существует проблема видимости, наложенная моделью памяти JVM. вот статья, рассказывающая о модели памяти и о том, как записи становятся видимыми для потоков. Вы не можете рассчитывать на изменения, которые один поток делает видимыми для других потоков своевременно (на самом деле JVM не обязана делать эти изменения видимыми для вас вообще), если вы не установите происходит-раньше отношения, вот цитата из этой ссылки (приводится в комментарии Джеда Уэсли-Смита):
Глава 17 спецификации языка Java определяет отношение случается-перед операциями памяти, такими как чтение и запись общих переменных. Результаты записи одним потоком гарантированно будут видны для чтения другим потоком, только если операция записи происходит-до операции чтения. Синхронизированные и изменчивые конструкции, а также поток.start () и Thread.присоединяться() методы, может сформировать случается-перед отношениями. В частности:
каждое действие в потоке происходит-перед каждым действием в этом потоке, которое приходит позже в порядке программы.
разблокировка (синхронизированный блок или выход метода) монитора происходит-перед каждой последующей блокировкой (синхронизированный блок или вход метода) того же монитора. А поскольку отношение случается-до транзитивно, все действия потока до разблокировки happen-перед всеми действиями, последующими за любой блокировкой потока этого монитора.
запись в volatile поле происходит-перед каждым последующим чтением того же поля. Записи и считывания изменчивых полей имеют такие же эффекты согласованности памяти, как вход и выход мониторов, но не влекут за собой взаимную блокировку исключения.
вызов для запуска потока происходит-перед любым действием в начатом потоке.
все действия в потоке происходят-прежде чем любой другой поток успешно вернется из соединения в этом потоке.
Он говорил о видимость и не следует воспринимать слишком буквально.
статические переменные действительно разделяются между потоками, но изменения, сделанные в одном потоке, могут быть не видны другому потоку сразу, что делает его похожим на две копии переменной.
в этой статье представлено представление, которое согласуется с тем, как он представил info:
во-первых, вы должны кое-что понять о модели памяти Java. На протяжении многих лет я пытался объяснить это кратко и хорошо. На сегодняшний день, лучший способ, который я могу придумать, чтобы описать это, если вы представляете это так:
каждый поток в Java происходит в отдельном пространстве памяти (это явно неправда, так что потерпите меня на этом).
вам нужно использовать специальные механизмы, чтобы гарантировать, что связь происходит между этими потоками, как и в системе передачи сообщений.
память пишет, что в одном потоке может "просочиться" и быть замечено другим потоком, но это никоим образом не гарантируется. Без явной связи вы не можете гарантировать, что записи будут видны другим потокам или даже порядок, в котором их видят.
...
но опять же, это просто ментальная модель, чтобы думать о потоке и изменчивости, а не буквально, как работает JVM.
в основном это правда, но на самом деле проблема сложнее. На видимость общих данных могут влиять не только кэши ЦП, но и неупорядоченное выполнение инструкций.
поэтому Java определяет Модель, это состояния, при которых потоки могут видеть согласованное состояние общих данных.
в вашем конкретном случае, добавление volatile
гарантирует видимость.
они "общие", конечно, в том смысле, что они оба ссылаются на одну и ту же переменную, но они не обязательно видят обновления друг друга. Это справедливо для любой переменной, а не только для статической.
и теоретически записи, сделанные другим потоком, могут казаться в другом порядке, если переменные не объявлены volatile
или записи явно синхронизированы.
в пределах одного загрузчика классов статические поля всегда являются общими. Чтобы явно охватить данные потоками, вы хотите использовать средство, подобное ThreadLocal
.
при инициализации статической переменной примитивного типа java default присваивает значение статическим переменным
public static int i ;
когда вы определяете переменную, как это значение по умолчанию i = 0; вот почему есть возможность получить вам 0. затем основной поток обновляет значение boolean ready до true. поскольку ready-это статическая переменная, основной поток и другой поток ссылаются на один и тот же адрес памяти, поэтому готовая переменная изменяется. таким образом, вторичный поток выходит из while значение цикла и печати. при печати инициализированное значение number равно 0. если процесс потока прошел цикл while перед переменной номера обновления основного потока. тогда есть возможность распечатать 0
@dontocsata вы можете вернуться к своему учителю и школа его немного:)
несколько заметок из реального мира, и независимо от того, что вы видите или быть сказано. Обратите внимание, что слова ниже относятся к этому конкретному случаю в точном порядке.
следующая переменная 2 будет находиться в одной строке кэша практически под любой архитектурой know.
private static boolean ready;
private static int number;
Thread.exit
(основной поток) гарантированно выходит и exit
гарантированно вызывает забор памяти, из-за удаления потока группы потоков (и многих других проблем). (это синхронизированный вызов, и я не вижу единого способа реализации без части синхронизации, так как ThreadGroup также должен завершиться, если не осталось потоков демона и т. д.).
начатая нить ReaderThread
собирается сохранить процесс в живых, так как это не демон!
Таким образом ready
и number
будет сброшено вместе (или номер до, если происходит переключение контекста), и нет никакой реальной причины для переупорядочивания в этом по крайней мере, я не могу придумать ни одного.
Вам понадобится что-то действительно странное, чтобы увидеть что-нибудь, но 42
. Опять же, я предполагаю, что обе статические переменные будут находиться в одной строке кэша. Я просто не могу представить строку кэша длиной 4 байта или JVM, которая не будет назначать их в непрерывной области (строка кэша).