Мой учитель в классе 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;
}
}
источник
Ответы:
Когда дело касается видимости, в статических переменных нет ничего особенного. Если они доступны, любой поток может получить к ним доступ, поэтому вы с большей вероятностью столкнетесь с проблемами параллелизма, поскольку они более уязвимы.
Модель памяти JVM создает проблему с видимостью. В статье рассказывается о модели памяти и о том, как записи становятся видимыми для потоков . Вы не можете рассчитывать на изменения, которые один поток делает видимыми для других потоков своевременно (на самом деле JVM не обязана делать эти изменения видимыми для вас вообще, в любой период времени), если вы не установите связь «произошло раньше» .
Вот цитата из этой ссылки (из комментария Джеда Уэсли-Смита):
источник
Он говорил о видимости, и не следует понимать ее слишком буквально.
Статические переменные действительно распределяются между потоками, но изменения, сделанные в одном потоке, могут не сразу быть видны другому потоку, из-за чего создается впечатление, что существует две копии переменной.
В этой статье представлена точка зрения, которая согласуется с тем, как он представил информацию:
Но опять же, это просто ментальная модель для размышлений о многопоточности и изменчивости, а не буквально о том, как работает JVM.
источник
В основном это правда, но на самом деле проблема более сложная. На видимость общих данных могут влиять не только кэши ЦП, но и выполнение инструкций вне очереди.
Поэтому Java определяет модель памяти , в которой говорится, при каких обстоятельствах потоки могут видеть согласованное состояние общих данных.
В вашем конкретном случае добавление
volatile
гарантирует видимость.источник
Конечно, они «общие» в том смысле, что оба ссылаются на одну и ту же переменную, но не обязательно видят обновления друг друга. Это верно для любой переменной, а не только для статической.
И теоретически записи, сделанные другим потоком, могут выглядеть в другом порядке, если только переменные не объявлены
volatile
или записи явно не синхронизированы.источник
В пределах одного загрузчика классов всегда используются общие статические поля. Для явной привязки данных к потокам вам нужно использовать такое средство, как
ThreadLocal
.источник
Когда вы инициализируете статическую переменную примитивного типа, java по умолчанию присваивает значение статическим переменным
когда вы определяете переменную таким образом, значение по умолчанию i = 0; вот почему есть возможность получить 0. тогда основной поток обновляет значение boolean ready до true. поскольку ready - это статическая переменная, основной поток и другой поток ссылаются на тот же адрес памяти, поэтому переменная ready изменяется. поэтому вторичный поток выходит из цикла while и выводит значение. при печати значение инициализированное значение number равно 0., если процесс потока прошел цикл while до обновления номера переменной основного потока. тогда есть возможность напечатать 0
источник
@dontocsata, ты можешь вернуться к своему учителю и немного поучить его :)
несколько заметок из реального мира и независимо от того, что вы видите или что вам говорят. ОБРАТИТЕ ВНИМАНИЕ, что слова ниже относятся к этому конкретному случаю в точном указанном порядке.
Следующие две переменные будут находиться в одной строке кэша практически в любой известной архитектуре.
Thread.exit
(основной поток) гарантированно завершится иexit
вызовет ограничение памяти из-за удаления потока группы потоков (и многих других проблем). (это синхронизированный вызов, и я не вижу единого способа реализации без синхронизирующей части, поскольку ThreadGroup также должна завершиться, если не осталось потоков демона и т. д.).Запущенный поток
ReaderThread
будет поддерживать процесс, поскольку он не является демоном! Таким образом,ready
иnumber
будут сброшены вместе (или число до, если произойдет переключение контекста), и в этом случае нет реальной причины для переупорядочения, по крайней мере, я даже не могу об этом подумать. Чтобы увидеть что угодно, вам понадобится нечто действительно странное42
. Опять же, я предполагаю, что обе статические переменные будут в одной строке кеша. Я просто не могу представить строку кеша длиной 4 байта ИЛИ JVM, которая не будет назначать их в непрерывной области (строка кеша).источник