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



мой учитель в классе 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;
}
}
643   7  

7 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

...

thread model

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

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

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

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

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

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

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

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

public static int i ;

при определении переменной, как это значение по умолчанию i = 0; вот почему есть возможность получить Вас 0. затем основной поток обновляет значение boolean ready до true. поскольку 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, которая не будет назначать их в непрерывной области (строка кэша).

Comments

    Ничего не найдено.