Является ли id = 1 - id атомарным?


со страницы 291 OCP Java SE 6 программист практика экзаменов, вопрос 25:

public class Stone implements Runnable {
    static int id = 1;

    public void run() {
        id = 1 - id;
        if (id == 0) 
            pick(); 
        else 
            release();
    }

    private static synchronized void pick() {
        System.out.print("P ");
        System.out.print("Q ");
    }

    private synchronized void release() {
        System.out.print("R ");
        System.out.print("S ");
    }

    public static void main(String[] args) {
        Stone st = new Stone();
        new Thread(st).start();
        new Thread(st).start();
    }
}

один из ответов:

выход может быть P Q P Q

я отметил этот ответ как правильный. Мои рассуждения:

  1. мы начинаем два потока.
  2. первый входит run().
  3. по данным JLS 15.26.1, он сначала оценивает 1 - id. Результат 0. Это хранится в стеке потока. Мы как раз собираемся сохранить это 0 статический id, но...
  4. бум, планировщик выбирает поток для выполнения.
  5. Итак, второй поток входит run(). Статика id по-прежнему 1, поэтому он выполняет метод pick(). P Q печати.
  6. планировщик выбирает поток для выполнения. Это займет 0 из стека и сохраняет в статический id. Итак, первый поток также выполняет pick() и печатает P Q.

однако, в книге написано, что этот ответ неверный:

это неверно, потому что строка id = 1 - id меняет значениеid между 0 и 1. Один и тот же метод не может быть выполнен дважды.

я не согласен. Я думаю, что есть некоторый шанс для сценария, который я представил выше. Такой обмен не является атомарным. Я ошибаюсь?

2 73

2 ответа:

я ошибаюсь?

нет, вы абсолютно правы - как и ваш пример временной шкалы.

в дополнение к тому, что он не является атомарным, не гарантируется, что запись в id будет подобран другой поток в любом случае, учитывая, что нет синхронизации и поле не является изменчивым.

это несколько смущает для справочного материала, как это, чтобы быть неправильным : (

на мой взгляд, ответ в практике экзаменов является правильным. В этом коде вы выполняете два потока, которые имеют доступ к одной и той же статической переменной id. Статические переменные хранятся в куче в java, а не в стеке. Порядок выполнения runnables непредсказуем.

однако, чтобы изменить значение id каждого потока:

  1. делает локальную копию значения, хранящегося в адресе памяти id в реестр CPU;
  2. выполняет операция 1 - id. Строго говоря, здесь выполняются две операции (-id and +1);
  3. перемещает результат обратно в область памяти id в куче.

это означает, что, хотя значение id может быть изменено одновременно любым из двух потоков, изменяются только начальные и конечные значения. Промежуточные значения не будут изменены друг другом.

кроме того, анализ кода, может показать, что в любой момент времени, ID может быть только 0 или 1.

доказательство:

  • начальное значение id = 1; Один поток изменит его на 0 (id = 1 - id). И другой поток вернет его к 1.

  • начальное значение id = 0; Один поток изменит его на 1 (id = 1 - id). И другой поток вернет его к 0.

таким образом, значение состояния идентификатор дискретные значения 0 или 1.

конец Доказательство.

для этого кода могут быть две возможности:

  • возможность 1. Поток один сначала обращается к идентификатору переменной. Тогда значение id (id = 1 - id изменяется на 0. После этого, только метод pick () будет выполнено, печать P Q. Поток два, будет оценивать id в то время id = 0; способ release() затем будет выполнена печать R S. В результате,P Q R S будут напечатаны.

  • возможность 2. Нитка два сначала обращается к переменной id. Тогда значение id (id = 1 - id изменяется на 0. После этого, только метод pick () будет выполнено, печать P Q. Поток один, будет оценивать id в то время id = 0; способ release() затем будет выполнена печать R S. В результате,P Q R S будут напечатаны.

других возможностей нет. Однако, следует отметить, что варианты P Q R S например P R Q S или R P Q S и т. д. может быть напечатан из-за pick() будучи статическим методом и поэтому делится между двумя потоками. Это приводит к одновременному выполнению этого метода, что может привести к печати букв в другом порядке в зависимости от вашей платформы.

однако в любом случае, не метод pick() или release () быть выполнены в два раза, как они взаимоисключающие. Поэтому P Q P Q не будет выхода.