Состояние гонки в Java
Гонка возникает, когда исход программы зависит от случайного чередования потоков, а общие данные не защищены. Один поток читает устаревшее значение, другой успевает записать своё, и часть обновлений пропадает.
Даже простое counter++ на уровне байткода
распадается на загрузку, прибавление и сохранение.
Между этими шагами соседний поток может выполнить
ту же тройку действий с тем же стартовым числом.
Наглядный счетчик в объекте и два worker-потока:
class Tally {
int value = 0;
void bump() {
value++;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Tally tally = new Tally();
Runnable work = () -> {
for (int i = 0; i < 500; i++) {
tally.bump();
}
};
Thread a = new Thread(work);
Thread b = new Thread(work);
a.start();
b.start();
a.join();
b.join();
System.out.println(tally.value);
}
}
Ожидали бы 1000, но печать часто показывает
меньше. Чтобы убрать гонку, критический участок
нужно выполнять так, будто внутри него работает
только один поток.
Тот же сценарий с паузой между чтением и записью усиливает эффект:
class SlowTally {
int value = 0;
void bump() throws InterruptedException {
int seen = value;
Thread.sleep(1);
value = seen + 1;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
SlowTally tally = new SlowTally();
Runnable work = () -> {
for (int i = 0; i < 100; i++) {
try {
tally.bump();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
};
Thread a = new Thread(work);
Thread b = new Thread(work);
a.start();
b.start();
a.join();
b.join();
System.out.println(tally.value);
}
}
Класс Score с полем points и
методом addOne() без синхронизации.
Три потока по 200 вызовов. После
join сравните результат с 600.