Почему в этом коде Java нет условий гонки?

1

Вот моя тема:

public class MyRunnable implements Runnable
{
  public static int num = 0;

  private void add()
  {
    num = num + 1;
  }

  @Override
  public void run()
  {
    for (int i=0;i<10000;i++)
    {
      add();
      System.out.println(num);
    }
  }
}

И здесь моя главная:

public class MultiThread
{
  public static void main(String[] argv)
  {
    Thread mt1 = new Thread(new MyRunnable(), "A");
    Thread mt2 = new Thread(new MyRunnable(), "B");

    mt1.start();
    mt2.start();
  }

}

Я ожидаю увидеть условия гонки там, и поэтому выход должен быть меньше 20000. Однако фактический результат я получил:

19975
19976
19977
19978
19979
19980
19981
19982
19983
19984
19985
19986
19987
19988
19989
19990
19991
19992
19993
19994
19995
19996
19997
19998
19999
20000

Process finished with exit code 0

Может ли кто-нибудь объяснить мне, почему в этой java-программе операция добавления кажется атомной, даже когда я не делал блокировки или синхронизации?

  • 5
    Отсутствие ошибки в вашем результате не означает отсутствие (возможного) состояния гонки.
  • 0
    Я запускал этот фрагмент кода много раз и никогда не получал условия гонки, но я думаю, что мне не так повезло, чтобы не получить условие гонки за более чем сотни тысяч итераций.
Показать ещё 2 комментария
Теги:
multithreading
parallel-processing

3 ответа

3
Лучший ответ

Вы просто не пробовали достаточно времени или не смотрели на ваши результаты достаточно близко. Этот фрагмент кода

private void add()
{
    num = num + 1;
}

никоим образом не является безопасным. Вы настраиваете себя на потерянные обновления. Оба потока будут читать одно и то же значение num и каждый из них обновит его, поэтому один прирост будет потерян.

  • 3
    Скорее всего, каждый поток тратит гораздо больше времени на выполнение операций ввода-вывода, поэтому им не приходится наступать друг на друга в течение относительно небольшого промежутка времени, необходимого для выполнения одного load-modify-store.
  • 0
    @GregHewgill я вижу. Да, условие гонки появляется после того, как я уберу заявление на печать.
Показать ещё 1 комментарий
1

Вполне возможно, что первый поток заканчивает свою (довольно короткую) петлю перед тем, как второй поток начинает свой собственный цикл, и поэтому они, кажется, не мешают друг другу.

Вы увидите ожидаемое поведение, если вы попробуете более длинные циклы или добавьте задержки в цикле.

0

Просто чтобы быть более ясным с понятием состояния расы в случае, который вы представили; проблема, конечно же, в области

num = num + 1;

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

рассмотрите следующую сборочную линию, которая

LOAD    @i, r0    ;load the value of 'i' into a register from memory
ADD     r0, 1     ;increment the value in the register
STORE   r0, @i    ;write the updated value back to memory

или простыми словами:

Fetch i into a register
Increment the register
Write it back to i

Условие гонки возникает, когда поток A извлекает я в регистр, увеличивая его, а затем ПЕРЕД записью - поток B входит и делает то же самое - fetch & increment (oh no). Это может быть в любом месте в 3 строках кода

Таким образом, этот код считается небезопасным и может привести к состоянию гонки. Тот факт, что это не произойдет в 1000-й раз, не означает, что этого не произойдет.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню