Я думаю, что пример volatile в спецификации Java немного неверен.
В 8.3.1.4. летучие поля, это говорит
class Test {
static int i = 0, j = 0;
static void one() { i++; j++; }
static void two() {
System.out.println("i = " + i + " j = " + j);
}
}
...then method two could occasionally print a value for j that is greater than the value of i, because the example includes no synchronization and, under the rules explained in§17.4, the shared values of i and j might be updated out of order.
Я думаю, даже если эти обновления в порядке, второй метод все еще может видеть j больше, чем i, поскольку System.out.println("i = " + i + " j = " + j) не является атомарным, а i читается перед j.
Второй метод такой же, как
read i
read j
Так что возможно, что
read i
i++
j++
read j
В этом случае второй метод видит значение j больше, чем i, однако обновления НЕ выходят из строя.
Так что не по порядку - не единственная причина видеть j > i
Должно быть System.out.println("j = " + j + " i = " + i);?
На этот раз не по порядку - единственная причина видеть j > i
Не понимаю вашего вопроса, но объяснение JLS кажется мне правильным.
@RobbyCornelissen обновил мой вопрос
@Vipul, дело не в изменчивости...
Непоследовательность относится к нескольким потокам, чередующим вызовы. Цитата из JLS в вашем вопросе относится к разделу §17.4. См. «§17.4.3 Программы и порядок программ» для значения «не по порядку».
@Vipul Что вы подразумеваете под «процессами»?
@curiousguy процесс здесь темы
@Tim Ответ ниже также объясняет изменчивость. Если вам нужен параллелизм, переменные должны быть изменчивыми.




Примеры более чем «немного неправильные».
Во-первых, вы правы в том, что даже без переупорядочения j может оказаться больше, чем i в этом примере. Это даже подтверждается позже в тот же пример:
Another approach would be to declare
iandjto bevolatile:class Test { static volatile int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println("i = " + i + " j = " + j); } }This allows method
oneand methodtwoto be executed concurrently, but guarantees that accesses to the shared values foriandjoccur exactly as many times, and in exactly the same order, as they appear to occur during execution of the program text by each thread. Therefore, the shared value forjis never greater than that fori, because each update toimust be reflected in the shared value foribefore the update tojoccurs. It is possible, however, that any given invocation of methodtwomight observe a value forjthat is much greater than the value observed fori, because methodonemight be executed many times between the moment when methodtwofetches the value ofiand the moment when methodtwofetches the value ofj.
Конечно, заумно говорить «общее значение для j никогда не бывает больше, чем для i», лишь бы прямо в следующем предложении сказать «Возможно… [наблюдать] значение для j, которое намного больше, чем значение, наблюдаемое для i».
Итак, j никогда не больше i, за исключением случаев, когда наблюдается, что много больше i? Можно ли сказать, что «немного больше» невозможно?
Конечно, нет. Это утверждение не имеет смысла и кажется результатом попытки отделить некоторую объективную истину, такую как «общая ценность», от «наблюдаемой ценности», тогда как на самом деле в программе есть только наблюдаемое поведение.
Это иллюстрируется неправильным предложением:
This allows method one and method two to be executed concurrently, but guarantees that accesses to the shared values for
iandjoccur exactly as many times, and in exactly the same order, as they appear to occur during execution of the program text by each thread.
Даже с переменными volatile такой гарантии нет. Все, что JVM должна гарантировать, это то, что наблюдаемое поведение не противоречит спецификации, поэтому, например, когда вы вызываете one() тысячу раз в цикле, оптимизатор все равно может заменить его атомарным приращением на тысячу, если это может предотвратить возможность другого потока, свидетельствующего о наличии такой оптимизации (кроме вывода из более высокой скорости).
Или, другими словами, сколько раз к переменной (соответственно к ее расположению в памяти) обращаются фактически, не наблюдается и, следовательно, не указывается. В любом случае это не имеет значения. Все, что имеет значение для разработчика приложений, это то, что j может быть больше, чем i, независимо от того, объявлены переменные volatile или нет.
Замена порядка чтения i и j внутри two() может сделать его лучшим примером, но я думаю, было бы лучше, если бы JLS §8.3.1.2 не пытался объяснить значение volatile в разговорной речи, а просто заявил, что это накладывает специальную семантику в соответствии с модель памяти и предоставил JMM возможность объяснить ее формально корректным образом.
Программисты не должны освоить параллелизм, просто прочитав 8.3.1.4., поэтому пример здесь бесполезен (в лучшем случае; в худшем случае создастся впечатление, что этого примера достаточно для понимания вопроса).
Я удивлен, что JLS может иметь такие вводящие в заблуждение предложения
@user7 еще много чего нуждается в доработке. JLS был написан людьми…
То, что Хольгер говорит в своем ответе, абсолютно правильно (прочитайте это опять таки и примите это), я просто хочу добавить, что с помощью jcstress это даже как бы легко доказать. Сам тест — это всего лишь небольшой рефакторинг Образец согласованности (который превосходен! IMO):
import org.openjdk.jcstress.annotations.Actor;
import org.openjdk.jcstress.annotations.Expect;
import org.openjdk.jcstress.annotations.JCStressTest;
import org.openjdk.jcstress.annotations.Outcome;
import org.openjdk.jcstress.annotations.State;
import org.openjdk.jcstress.infra.results.II_Result;
@JCStressTest
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "only j updated")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "only i updated")
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "both updates lost")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "both updated")
@State
public class SOExample {
private final Holder h1 = new Holder();
private final Holder h2 = h1;
@Actor
public void writeActor() {
++h1.i;
++h1.j;
}
@Actor
public void readActor(II_Result result) {
Holder h1 = this.h1;
Holder h2 = this.h2;
h1.trap = 0;
h2.trap = 0;
result.r1 = h1.i;
result.r2 = h2.j;
}
static class Holder {
int i = 0;
int j = 0;
int trap;
}
}
Даже если вы не понимаете код, дело в том, что его выполнение покажет ACCEPTABLE_INTERESTING как абсолютно возможные результаты; будь то с volatile int i = 0; volatile int j = 0; или без этого volatile.
где ваши изменчивые переменные? Также volatile - это тот, который хранится в основной памяти, и все процессы обращаются к ним напрямую. для статики есть копия, которая используется всеми процессами