Короткий ответ
Состояние гонки — это ситуация, когда результат многопоточной программы зависит от порядка и скорости выполнения потоков и становится непредсказуемым. Гонка возникает, когда несколько потоков без синхронизации обращаются к одним и тем же разделяемым данным и хотя бы один из них выполняет запись; итог зависит от того, какой поток «добежал» первым.
Как это работает подробнее
Типичный пример — несколько потоков увеличивают общий счётчик. Операция count++ состоит из трёх шагов: прочитать текущее значение, прибавить единицу, записать результат обратно. Два потока могут прочитать одно и то же значение, каждый вычислит следующее число, и в поле будет записано то же значение — одно увеличение потеряно (lost update).
Гонка коварна тем, что проявляется не сразу: она часто «выстреливает» только под нагрузкой или на другом железе, из-за чего баги нерегулярно воспроизводятся.
- Три основных способа борьбы: атомарные классы (
AtomicInteger) для одной составной операции;synchronizedилиLockдля более крупной критической секции; потокобезопасные коллекции для структур данных.
Пример кода
public class RaceConditionDemo {
private int count = 0;
public void increment() { count++; } // не потокобезопасно
public static void main(String[] args) throws InterruptedException {
RaceConditionDemo c = new RaceConditionDemo();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) c.increment();
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(c.count); // ожидаем 2000, реально меньше
}
}Что использовать на практике
- Защищай общие изменяемые данные: атомарные классы для счётчиков,
synchronizedилиLockдля критических секций. - Для структур данных используй потокобезопасные коллекции (ConcurrentHashMap и аналоги) вместо ручной синхронизации.
- Прежде чем опубликовать поле между потоками, реши, кто его меняет и как синхронизируется доступ.
Подводные камни
- Гонка может долго не проявляться и «выстрелить» только под нагрузкой или на другом железе — код «то работает, то нет».
- Даже простой
count++не атомарен: это три шага, а не одна операция. - Синхронизация нужна не только при записи, но и при чтении общих данных, иначе поток может увидеть устаревшее значение.
Как отвечать на собеседовании
- Дай определение: результат зависит от порядка и скорости выполнения потоков → приведи пример двух потоков и count++ с потерянным обновлением → объясни, почему операция не атомарна → перечисли способы борьбы: атомарные классы, synchronized/Lock, потокобезопасные коллекции → отметь нерегулярность воспроизведения.