Похоже, вы совершаете распространенную ошибку, слишком много думая о низкоуровневых аспектах JMM в изоляции. Что касается вашего вопроса «как люди используют это на практике», если вы говорите о программисте приложений, он (она) будет использовать это на практике, не думая все время о барьерах памяти или возможных переупорядочениях.
Что касается вашего примера:
public void someMethod() {
a = 2; // 1
b = 3; // 2
}
Учитывая, что a
и b
не-final
, не-volatile
.
Понятно, что в потоке w(a = 2) происходит раньше w(b = 3) в соответствии с порядком программы. Как компилятор/оптимизатор может быть уверен, что изменение порядка 1 и 2 не приведет к «незаконному выполнению» (строго с точки зрения модели)?
Здесь имеет неприятные последствия то, что вы сосредоточены на повторном заказе в изоляции. Во-первых, полученный код (оптимизация HotSpot, JIT-компиляция и т. д.) вообще не требует записи значений в динамическую память. Он может хранить новые значения в регистрах ЦП и использовать их оттуда в последующих операциях того же потока. Только при достижении точки эти изменения должны быть видны другим потокам, они должны быть записаны в кучу. Что может происходить в произвольном порядке.
Но если, например, вызывающая сторона метода входит в бесконечный цикл после вызова этого метода, значения никогда не должны записываться.
И почему, если мы установим b изменчивым, он будет ?
Объявление b
как volatile
не гарантирует, что a
и b
будут записаны. Это еще одна ошибка, возникающая из-за концентрации внимания на барьерах памяти.
Давайте более абстрактно:
Предположим, у вас есть два одновременных действия, A
и B
. Для параллельного выполнения в Java существует несколько совершенно допустимых вариантов поведения, в том числе:
A
может быть выполнен полностью до B
B
может быть выполнен полностью до A
- Все или части
A
и B
работают параллельно
в случае, если B
выполняется полностью до A
, нет смысла иметь барьер записи в A
и барьер чтения в B
, B
все равно не заметит никаких действий A
. Исходя из этой отправной точки, вы можете сделать выводы о различных параллельных сценариях.
Здесь в игру вступает отношение происходит до: запись значения в переменную volatile
происходит до чтения этого значения из этой переменной em> другим потоком. Если операция чтения выполняется перед операцией записи, читающий поток не увидит значение и, следовательно, не будет отношения произошло-до, и поэтому мы не сможем сделать заявление о других переменных.
Чтобы продолжить ваш пример с b
равным volatile
: это означает, что если читающий поток читает b
и читает значение 3
, и только тогда он гарантированно увидит значение 2
(или даже более позднее значение, если есть другие записи) для a
при последующих чтениях.
Таким образом, если JVM может доказать, что никогда не будет операции чтения для b
, увидев записанное значение, возможно, потому, что весь экземпляр, который мы модифицируем, никогда не будет виден другому потоку, не произойдет ничего, прежде чем связь будет когда-либо установлена, в Другими словами, b
в качестве volatile
не влияет на разрешенные преобразования кода в этом случае, т. е. он также может быть переупорядочен или даже вообще не записываться в кучу.
Таким образом, суть в том, что бесполезно смотреть на небольшой фрагмент кода и спрашивать, позволит ли он изменить порядок или будет ли он содержать барьер памяти. На это может даже не быть ответа, поскольку ответ может меняться в зависимости от того, как на самом деле используется код. Только если ваш обзор достаточно широк, чтобы увидеть, как потоки будут взаимодействовать при доступе к данным, и вы можете с уверенностью сделать вывод, будет ли установлена связь происходит до, вы можете начать делать выводы о правильной работе кода. . Как вы сами убедились, правильная работа не означает, что вы знаете, произойдет переупорядочение или нет на самом нижнем уровне.
person
Holger
schedule
13.08.2014