Как понять изменчивый пример в спецификации языка Java?

Я думаю, что пример 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

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

Vipul Pandey 24.05.2019 06:06

Не понимаю вашего вопроса, но объяснение JLS кажется мне правильным.

Robby Cornelissen 24.05.2019 06:07

@RobbyCornelissen обновил мой вопрос

Tim 24.05.2019 07:10

@Vipul, дело не в изменчивости...

Tim 24.05.2019 07:11

Непоследовательность относится к нескольким потокам, чередующим вызовы. Цитата из JLS в вашем вопросе относится к разделу §17.4. См. «§17.4.3 Программы и порядок программ» для значения «не по порядку».

Robby Cornelissen 24.05.2019 07:39

@Vipul Что вы подразумеваете под «процессами»?

curiousguy 24.05.2019 19:33

@curiousguy процесс здесь темы

Vipul Pandey 25.05.2019 13:06

@Tim Ответ ниже также объясняет изменчивость. Если вам нужен параллелизм, переменные должны быть изменчивыми.

Vipul Pandey 25.05.2019 13:09
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
7
8
235
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Примеры более чем «немного неправильные».

Во-первых, вы правы в том, что даже без переупорядочения j может оказаться больше, чем i в этом примере. Это даже подтверждается позже в тот же пример:

Another approach would be to declare i and j to be 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);
    }
}

This allows method one and method two to be executed concurrently, but guarantees that accesses to the shared values for i and j occur 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 for j is never greater than that for i, because each update to i must be reflected in the shared value for i before the update to j occurs. It is possible, however, that any given invocation of method two might observe a value for j that is much greater than the value observed for i, because method one might be executed many times between the moment when method two fetches the value of i and the moment when method two fetches the value of j.

Конечно, заумно говорить «общее значение для 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 i and j occur 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 30.07.2019 06:05

@user7 еще много чего нуждается в доработке. JLS был написан людьми…

Holger 05.08.2019 09:34

То, что Хольгер говорит в своем ответе, абсолютно правильно (прочитайте это опять таки и примите это), я просто хочу добавить, что с помощью 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.

Другие вопросы по теме