Вот моя тема:
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-программе операция добавления кажется атомной, даже когда я не делал блокировки или синхронизации?
Вы просто не пробовали достаточно времени или не смотрели на ваши результаты достаточно близко. Этот фрагмент кода
private void add()
{
num = num + 1;
}
никоим образом не является безопасным. Вы настраиваете себя на потерянные обновления. Оба потока будут читать одно и то же значение num
и каждый из них обновит его, поэтому один прирост будет потерян.
Вполне возможно, что первый поток заканчивает свою (довольно короткую) петлю перед тем, как второй поток начинает свой собственный цикл, и поэтому они, кажется, не мешают друг другу.
Вы увидите ожидаемое поведение, если вы попробуете более длинные циклы или добавьте задержки в цикле.
Просто чтобы быть более ясным с понятием состояния расы в случае, который вы представили; проблема, конечно же, в области
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-й раз, не означает, что этого не произойдет.