Java параллельная энергозависимая i ++ - PullRequest
0 голосов
/ 20 февраля 2019

У меня есть глобальная переменная

volatile i = 0;

и два потока.Каждый делает следующее:

i++;
System.out.print(i);

Я получаю следующие комбинации.12, 21 и 22.

Я понимаю, почему я не получаю 11 (volatile запрещает кэширование i) и Я также понимаю 12 и 22.

ЧтоЯ не понимаю, как можно получить 21?

Единственный возможный способ, которым вы можете получить эту комбинацию, состоит в том, что печатаемая позже нить должна была быть первой, чтобы увеличить i с 0 до1, а затем кэшируется i==1.Затем другой поток увеличил i с 1 до 2, а затем напечатал 2. Затем первый поток печатает кэшированный i==1.Но я подумал, что volatile запрещает кэширование.

Редактировать: После выполнения кода 10000 раз я получил 11 раз.Добавление volatile к i не меняет возможных комбинаций вообще.

пробел является правильным: volatile запрещает кэширование i, но i++ не является атомарным.Это означает, что i все еще получает своего рода "кэшированные" в регистре во время приращения.

r1 = i
//if i changes here r1 does not change
r1 = r1 + 1 
i = r1

Это причина, почему 11 все еще возможно.21 вызван тем, что PrintStreams не синхронизированы (см. Ответ Кароля Доубеки)

Ответы [ 3 ]

0 голосов
/ 20 февраля 2019

К сожалению ++ не является атомарной операцией.Несмотря на то, что volatile не разрешает кэширование, JVM разрешено читать, увеличивать, а затем записывать как отдельные операции.Таким образом, концепция, которую вы пытаетесь реализовать, просто не работает.Вам нужно использовать synchronized для его мьютекса или что-то вроде AtomicInteger, которое обеспечивает атомарную операцию приращения.

0 голосов
/ 20 февраля 2019

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

Вы забываете о том, что делает System.out.print(i);: этот оператор вызывает метод System.out объекта *1006* с тем, что значение было сохранено в i в момент начала вызова.

Итак, вот один сценарий, который может произойти:

Thread A
increments i  (i now equals 1)
Starts to call `print(1)`  //Notice! that's the digit 1, not the letter i.
gets bogged down somewhere deep in the guts...

          Thread B
          increments i (i=2)
          Calls `print(2)`
          Gets lucky, and the call runs to completion.

Thread A
finishes its `print(1)` call.

Ни один из потоков не является кэширующим переменной i.Но функция System.out.print(...) ничего не знает о вашем volatile int i.Он знает только о значении (1 или 2), которое было ему передано.

0 голосов
/ 20 февраля 2019

Ваш код не гарантирует, какой поток будет вызывать System.out первым.

Приращение и чтение для i происходили в порядке из-за ключевого слова volatile, но печать не выполняласьт.

...