Is id = 1 - id атомный?
Со страницы 291 экзамена на экзамен по программированию на 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
Я правильно ответил на этот ответ. Мои рассуждения:
- Мы начинаем два потока.
- Сначала вводится
run()
.
- В соответствии с JLS 15.26.1 он сначала оценивает
1 - id
. Результат 0
. Он хранится в стеке потоков. Мы собираемся сохранить это 0
в static id
, но...
- Бум, планировщик выбирает второй поток для запуска.
- Итак, второй поток входит в
run()
. Статический id
по-прежнему 1
, поэтому он выполняет метод pick()
. P Q
.
- Планировщик выбирает первый поток для запуска. Он берет
0
из своего стека и сохраняет до статического id
. Итак, первый поток также выполняет pick()
и печатает P Q
.
Однако в книге написано, что этот ответ неверен:
Это неверно, потому что строка id = 1 - id
меняет значение id
между 0
и 1
. Нет возможности для того, чтобы один и тот же метод выполнялся дважды.
Я не согласен. Я думаю, что есть шанс для сценария, который я представил выше. Такой обмен не является атомарным. Я не прав?
Ответы
Ответ 1
Я не прав?
Нет, вы абсолютно правы - как ваша временная шкала.
В дополнение к этому он не является атомарным, он не гарантирует, что запись в id
будет в любом случае подхвачена другим потоком, учитывая, что синхронизации нет, и поле не является изменчивым.
Это несколько сбивает с толку, поскольку ссылочный материал как это неверен: (
Ответ 2
По моему мнению, ответ на Практических экзаменах правильный. В этом коде вы выполняете два потока, которые имеют доступ к одному и тому же идентификатору статической переменной. Статические переменные хранятся в куче в java, а не в стеке. Порядок выполнения runnables непредсказуем.
Однако, чтобы изменить значение id каждого потока:
- делает локальную копию значения, хранящегося в адресе памяти id, в реестр CPU;
- выполняет операцию
1 - id
. Строго говоря, здесь выполняются две операции (-id and +1)
;
- возвращает результат обратно в область памяти
id
в куче.
Это означает, что хотя значение id может быть изменено одновременно одним из двух потоков, только начальное и конечное значения изменяемы. Промежуточные значения не будут изменены друг другом.
Более того, анализ кода может показать, что в любой момент времени id может быть только 0 или 1.
Доказательство:
-
Начальное значение id = 1;
Один поток изменит его на 0 (id = 1 - id
). И другой поток вернет его к 1.
-
Начальное значение id = 0;
Один поток изменит его на 1 (id = 1 - id
). И другой поток вернет его к 0.
Следовательно, состояние значения id дискретно либо 0, либо 1.
Конец доказательства.
Для этого кода могут быть две возможности:
-
Возможность 1. В потоке один сначала обращается к идентификатору переменной. Тогда значение id (id = 1 - id
изменится на 0. После этого будет выполнен только метод pick ()
, напечатав P Q
. Поток второй, будет оценивать id в это время id = 0
, тогда метод release()
будет выполненная печать R S. В результате будет напечатано P Q R S
.
-
Возможность 2. В потоке два сначала обращаются к идентификатору переменной. Тогда значение id (id = 1 - id
изменится на 0. После этого будет выполнен только метод pick ()
, напечатав P Q
. В первом случае будет оцениваться идентификатор в это время 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
не будет выводиться.