Примеры более чем «немного ошибочны».
Во-первых, вы правы, что даже без переупорядочения j
может выглядеть больше i
в этом примере. Это даже подтверждается позже в того же примера :
Другим подходом было бы объявить i
и j
равными volatile
:
class Test {
static volatile int i = 0, j = 0;
static void one() { i++; j++; }
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
Это позволяет одновременно выполнять метод one
и метод two
, но гарантирует, что доступ к общим значениям для i
и j
будет происходить ровно столько же раз и в том же порядке, в котором они появляются во время исполнения текста программы каждым потоком. Поэтому общее значение для j
никогда не превышает значение для i
, поскольку каждое обновление до i
должно отражаться в общем значении для i
до того, как произойдет обновление до j
. Возможно, однако, что любой данный вызов метода two
мог бы наблюдать значение для j
, которое намного больше, чем значение, наблюдаемое для i
, потому что метод one
мог бы выполняться много раз между моментом, когда метод two
извлекает значение i
и момент, когда метод two
извлекает значение j
.
Конечно, трудно сказать « общее значение для j
никогда не больше, чем для i
», просто сказать прямо в следующем предложении « Это возможно… [to] наблюдать значение для j
, которое намного больше, чем значение, наблюдаемое для i
”.
Итак, j
никогда не больше i
, кроме случаев, когда наблюдается, что оно намного больше i
? Предполагается ли сказать, что «немного больше» невозможно?
Конечно нет. Это утверждение не имеет смысла и, по-видимому, является результатом попытки отделить некоторую объективную истину, такую как «общая ценность», от «наблюдаемой ценности», тогда как на самом деле в программе наблюдается только наблюдаемое поведение.
Это иллюстрируется неправильным предложением:
Это позволяет одновременно выполнять метод один и метод два, но гарантирует, что доступ к общим значениям для i
и j
происходит ровно столько раз, и в том же порядке, в котором они появляются во время выполнение текста программы каждым потоком.
Даже с переменными volatile
такой гарантии нет. Все, что JVM должна гарантировать, - это то, что наблюдаемое поведение не противоречит спецификации, поэтому, например, когда вы вызываете one()
тысячу раз в цикле, оптимизатор все равно может заменить его атомарным увеличение на тысячу, если это может исключить возможность того, что другой поток станет свидетелем наличия такой оптимизации (кроме выведения из более высокой скорости).
Или, другими словами, сколько раз переменная (соответственно ее местоположение в памяти) действительно доступна, не наблюдаема и, следовательно, не указана. В любом случае это не имеет значения. Все, что имеет значение для программиста приложения, - это то, что j
может быть больше, чем i
, независимо от того, объявлены ли переменные volatile
или нет.
Изменение порядка чтения i
и j
в пределах two()
может сделать его лучшим примером, но я думаю, было бы лучше, если бы JLS §8.3.1.2 не пытался объяснить значение volatile
в разговорной речи, но только что заявил, что он налагает особую семантику в соответствии с моделью памяти и оставил это JMM, чтобы объяснить это формально правильно.
Программисты не должны осваивать параллелизм, просто читая 8.3.1.4., Поэтому пример здесь не имеет смысла (в лучшем случае; в худшем случае создается впечатление, что этого примера достаточно для понимания вопроса).