← Назад к вопросам

В чем разница между Race Condition и Data Race?

2.3 Middle🔥 151 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

В чем разница между Race Condition и Data Race

Эти два понятия часто путают, но они обозначают разные проблемы в многопоточном программировании. Давай разберёмся подробно.

Data Race (гонка данных)

Data Race — это техническая проблема на уровне памяти. Это когда два или более потока обращаются к одной и той же переменной одновременно, и хотя бы один из них пишет в эту переменную, при этом без синхронизации.

public class DataRaceExample {
    private static int counter = 0; // общая переменная БЕЗ синхронизации
    
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter++; // DATA RACE: чтение + запись без синхронизации
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter++; // DATA RACE: одновременное обращение
            }
        });
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        System.out.println(counter); // может быть что угодно: 1000, 1500, 2000
        // правильный ответ: 2000
    }
}

Характеристика Data Race:

  • Это нарушение Java Memory Model (JMM)
  • Компилятор и процессор могут оптимизировать код непредсказуемо
  • Результат undefined behavior — поведение не определено
  • Может привести к потере обновлений, видимости старых значений и т.д.

Race Condition (состояние гонки)

Race Condition — это логическая ошибка на уровне приложения. Это когда итоговый результат программы зависит от порядка выполнения операций в разных потоках, и этот порядок не гарантирован и не контролируется.

public class RaceConditionExample {
    private static int balance = 1000;
    
    // Синхронизированный доступ к переменной — нет Data Race
    public synchronized static void withdraw(int amount) {
        if (balance >= amount) {
            // RACE CONDITION: check-then-act (TOCTOU bug)
            // между проверкой и действием другой поток может изменить balance
            balance = balance - amount; // это уже может быть некорректно
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> withdraw(600)); // хочу вывести 600
        Thread t2 = new Thread(() -> withdraw(600)); // хочу вывести 600
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        System.out.println("Balance: " + balance);
        // Даже если метод synchronized, может быть balance = 400
        // Потому что обе операции выполнились, хотя денег не было
    }
}

Характеристика Race Condition:

  • Это логическая ошибка вашего кода, а не нарушение памяти
  • Синхронизация переменной не гарантирует отсутствие race condition
  • Нужна синхронизация не одной переменной, а целой последовательности операций (atomic action)
  • Это application-level проблема

Сравнение

КритерийData RaceRace Condition
УровеньПамять (JMM)Логика приложения
ПричинаОдновременный доступ без синхронизацииНедостаточная синхронизация операций
Решениеsynchronized, volatile, LockРасширить критическую секцию
ДетекторThreadSanitizer, static analysisCode review, тестирование
ПоведениеUndefined behaviorОпределено, но неправильно

Пример: исправления

public class CorrectedExample {
    private int balance = 1000;
    
    // Решение 1: синхронизировать весь atomic action
    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            balance = balance - amount;
        }
    }
    
    // Решение 2: использовать AtomicInteger (от Data Race)
    private AtomicInteger atomicBalance = new AtomicInteger(1000);
    
    // Но атомарность одной переменной всё равно не решает race condition
    // если нужно check-then-act
    public void withdrawAtomic(int amount) {
        // Даже здесь race condition
        if (atomicBalance.get() >= amount) {
            atomicBalance.addAndGet(-amount); // может быть некорректно
        }
    }
    
    // Решение 3: истинная защита от race condition
    public void withdrawSafe(int amount) {
        synchronized (this) {
            if (balance >= amount) {
                balance -= amount;
            }
        }
    }
}

Data Race + Race Condition в одном примере

public class BothProblems {
    private int counter = 0; // нет volatile/synchronized
    private static final int LIMIT = 100;
    
    public void increment() {
        if (counter < LIMIT) { // DATA RACE + RACE CONDITION
            counter++; // 1) нет синхронизации = Data Race
        } // 2) check-then-act = Race Condition
    }
    
    // Исправленная версия
    public synchronized void incrementSafe() {
        if (counter < LIMIT) {
            counter++;
        }
    }
}

Как избежать обеих проблем

public class SafeCounter {
    private final AtomicInteger counter = new AtomicInteger(0);
    private static final int LIMIT = 100;
    
    // Для простого увеличения
    public void increment() {
        counter.incrementAndGet(); // атомарно
    }
    
    // Для check-then-act
    public void incrementIfBelowLimit() {
        while (true) {
            int current = counter.get();
            if (current >= LIMIT) break;
            // compareAndSet = atomic check-then-act
            if (counter.compareAndSet(current, current + 1)) {
                break;
            }
            // иначе retry (другой поток изменил значение)
        }
    }
    
    // Или просто синхронизировать
    public synchronized void incrementIfBelowLimitSimple() {
        if (counter.get() < LIMIT) {
            counter.incrementAndGet();
        }
    }
}

Вывод

  • Data Race — техническая ошибка на уровне памяти, нарушение JMM. Избегай через synchronized/volatile/Lock.
  • Race Condition — логическая ошибка, когда результат зависит от timing. Избегай через синхронизацию целых atomic actions.
  • Оба могут присутствовать одновременно
  • Синхронизация одной переменной не защищает от race condition, если нужна последовательность операций