← Назад к вопросам
В чем разница между 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 Race | Race Condition |
|---|---|---|
| Уровень | Память (JMM) | Логика приложения |
| Причина | Одновременный доступ без синхронизации | Недостаточная синхронизация операций |
| Решение | synchronized, volatile, Lock | Расширить критическую секцию |
| Детектор | ThreadSanitizer, static analysis | Code 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, если нужна последовательность операций