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

Какие знаешь методы борьбы с Race Condition?

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

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

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

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

Методы борьбы с Race Condition

Race Condition — это ошибка параллелизма, когда результат программы зависит от порядка выполнения потоков. Вот основные методы для её решения:

1. Synchronized (Блокировка)

Основной метод синхронизации на уровне JVM:

// Синхронизация метода
public class BankAccount {
    private double balance = 0;
    
    // ❌ БЕЗ synchronization - Race Condition
    public void withdraw(double amount) {
        if (balance >= amount) {
            balance = balance - amount;  // Два шага = race condition
        }
    }
    
    // ✅ С synchronization - безопасно
    public synchronized void withdrawSafe(double amount) {
        if (balance >= amount) {
            balance = balance - amount;  // Атомарно
        }
    }
    
    public synchronized double getBalance() {
        return balance;
    }
}

// Синхронизация блока кода
public class LockedResource {
    private double balance = 0;
    private Object lock = new Object();
    
    public void withdraw(double amount) {
        synchronized(lock) {  // Блокируем критическую секцию
            if (balance >= amount) {
                balance = balance - amount;
            }
        }
    }
    
    // Или синхронизируем на this
    public void deposit(double amount) {
        synchronized(this) {  // this — объект BankAccount
            balance = balance + amount;
        }
    }
}

// Проблема: мертвая блокировка (Deadlock)
public class DeadlockExample {
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    
    // ❌ Может привести к deadlock
    public void method1() {
        synchronized(lock1) {
            Thread.sleep(100);
            synchronized(lock2) {  // Ждёт lock2
                // Критическая секция
            }
        }
    }
    
    public void method2() {
        synchronized(lock2) {
            synchronized(lock1) {  // Ждёт lock1 — deadlock!
                // Критическая секция
            }
        }
    }
}

Проблемы synchronized:

  • Мертвые блокировки
  • Нет timeout'ов
  • Нет гибкости

2. Volatile

Для простых переменных, читаемых из разных потоков:

public class VolatileExample {
    // ❌ Может быть закеширована
    private boolean running = true;
    
    // ✅ Гарантирует видимость между потоками
    private volatile boolean runningSafe = true;
    
    public void run() {
        while (runningSafe) {
            // Работа
        }
    }
    
    public void stop() {
        runningSafe = false;  // Видимо для всех потоков
    }
}

// Ограничение: volatile работает только для простых присваиваний
public class VolatileLimitations {
    private volatile int counter = 0;
    
    // ❌ NOT атомарно!
    public void increment() {
        counter++;  // Три операции: читай, увеличь, пиши
    }
    
    // ✅ Атомарно
    public void incrementSafe() {
        synchronized(this) {
            counter++;
        }
    }
}

3. Atomic Classes

Легковесная альтернатива synchronized:

import java.util.concurrent.atomic.*;

public class AtomicExample {
    // ✅ Атомарная операция без блокировки
    private AtomicInteger counter = new AtomicInteger(0);
    private AtomicLong balance = new AtomicLong(0);
    private AtomicBoolean running = new AtomicBoolean(true);
    private AtomicReference<String> value = new AtomicReference<>();
    
    public void increment() {
        counter.incrementAndGet();  // Атомарно
    }
    
    public void transferMoney(long amount) {
        balance.addAndGet(amount);  // Атомарно
    }
    
    public void stop() {
        running.set(false);  // Видимо для всех потоков
    }
    
    // Compare-and-swap (CAS)
    public boolean withdraw(long amount) {
        while (true) {
            long current = balance.get();
            if (current < amount) return false;
            
            // Если значение не изменилось, обновить
            if (balance.compareAndSet(current, current - amount)) {
                return true;
            }
            // Иначе повторить
        }
    }
}

// Плюсы Atomic
// ✅ Нет блокировок (lock-free)
// ✅ Быстрее synchronized при низком contention
// ✅ Меньше deadlock'ов
// ❌ Сложнее для сложных операций

4. Reentrant Locks (ReentrantLock)

Больше контроля, чем synchronized:

import java.util.concurrent.locks.*;

public class ReentrantLockExample {
    private ReentrantLock lock = new ReentrantLock();
    private double balance = 0;
    
    // С timeout
    public boolean withdraw(double amount) throws InterruptedException {
        if (lock.tryLock(10, TimeUnit.SECONDS)) {
            try {
                if (balance >= amount) {
                    balance -= amount;
                    return true;
                }
                return false;
            } finally {
                lock.unlock();
            }
        }
        return false;  // Не получилось захватить lock
    }
    
    // С условными переменными (Condition Variables)
    private Condition sufficientFunds = lock.newCondition();
    
    public void withdrawWait(double amount) throws InterruptedException {
        lock.lock();
        try {
            while (balance < amount) {
                sufficientFunds.await();  // Ждём денег
            }
            balance -= amount;
        } finally {
            lock.unlock();
        }
    }
    
    public void deposit(double amount) {
        lock.lock();
        try {
            balance += amount;
            sufficientFunds.signalAll();  // Уведомляем ждущие потоки
        } finally {
            lock.unlock();
        }
    }
}

// Плюсы ReentrantLock
// ✅ Timeout'ы
// ✅ Condition variables
// ✅ Справедливая (fair) очередь
// ❌ Нужно явно unlock (могут быть ошибки)

5. ReadWriteLock

Много читателей, один писатель:

import java.util.concurrent.locks.*;

public class ReadWriteLockExample {
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Map<String, String> cache = new HashMap<>();
    
    // Много потоков могут читать одновременно
    public String get(String key) {
        readWriteLock.readLock().lock();
        try {
            return cache.get(key);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
    
    // Только один поток может писать
    public void put(String key, String value) {
        readWriteLock.writeLock().lock();
        try {
            cache.put(key, value);
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
}

// Сценарий: 100 читателей, 1 писатель
// synchronized: один поток одновременно
// ReadWriteLock: 100 читателей одновременно

6. Immutable Objects

Объекты, которые не меняются = нет Race Condition:

// ✅ Immutable
public final class ImmutableUser {
    private final Long id;
    private final String name;
    private final String email;
    
    public ImmutableUser(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
    
    public Long getId() { return id; }
    public String getName() { return name; }
    public String getEmail() { return email; }
    
    // Нет setters!
}

// ❌ Mutable
public class MutableUser {
    private Long id;
    private String name;
    
    public void setName(String name) {
        this.name = name;  // Race condition возможен
    }
}

// Пример: неизменяемая коллекция
List<String> immutableList = Collections.unmodifiableList(
    Arrays.asList("a", "b", "c")
);  // Thread-safe

7. Thread-safe Collections

Коллекции, которые уже защищены:

import java.util.concurrent.*;

public class ThreadSafeCollectionsExample {
    // Collections утилиты
    List<String> syncList = Collections.synchronizedList(
        new ArrayList<>()
    );
    Map<String, String> syncMap = Collections.synchronizedMap(
        new HashMap<>()
    );
    
    // Concurrent коллекции (лучший выбор)
    ConcurrentHashMap<String, String> concurrentMap = 
        new ConcurrentHashMap<>();  // Segment locking
    
    CopyOnWriteArrayList<String> copyOnWriteList = 
        new CopyOnWriteArrayList<>();  // Для частого чтения
    
    ConcurrentLinkedQueue<String> queue = 
        new ConcurrentLinkedQueue<>();  // Lock-free queue
    
    // Блокирующие очереди
    BlockingQueue<Task> blockingQueue = 
        new LinkedBlockingQueue<>();
}

// Производительность
// ConcurrentHashMap >> Collections.synchronizedMap
// Потому что ConcurrentHashMap использует bucket-level locking

8. Executor Service

Управление потоками безопасно:

public class ExecutorServiceExample {
    public void processData() {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        // Безопасная отправка задач
        for (int i = 0; i < 100; i++) {
            executor.submit(() -> {
                // Каждая задача в своем потоке
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
    }
}

9. Happens-before

Гарантии упорядочивания памяти:

public class HappensBeforeExample {
    private boolean ready = false;
    private int result = 0;
    
    public void process() {
        // Writer thread
        result = 10;
        ready = true;  // Volatile write создаёт happens-before
    }
    
    public void readData() {
        // Reader thread
        if (ready) {  // Volatile read
            // Гарантировано, что result = 10
            System.out.println(result);  // Всегда 10, никогда 0
        }
    }
}

// Happens-before правила
// 1. synchronized/lock: unlock happens-before следующий lock
// 2. volatile write happens-before volatile read
// 3. start() happens-before код в потоке
// 4. код в потоке happens-before join()

10. Best Practices для борьбы с Race Conditions

public class RaceConditionBestPractices {
    // ✅ Используй immutable объекты где возможно
    // ✅ Используй volatile для простых флагов
    // ✅ Используй Atomic* для счётчиков
    // ✅ Используй ConcurrentHashMap вместо synchronized HashMap
    // ✅ Используй ReentrantLock для сложного синхронизма
    // ✅ Используй ReadWriteLock для много-читателей
    // ✅ Избегай synchronized где возможно (выбирай Atomic)
    // ❌ Никогда не используй sleep() для синхронизации
    // ❌ Избегай nested locks (deadlock риск)
    // ✅ Всегда используй finally для unlock'ов
    
    // Правильный порядок попыток:
    // 1. Immutable objects
    // 2. Atomic classes
    // 3. Volatile
    // 4. ReentrantLock
    // 5. synchronized (только если ничего не подходит)
}

Пример: Безопасный счётчик

// ❌ Небезопасно
public class UnsafeCounter {
    private int count = 0;
    
    public void increment() {
        count++;  // Race condition!
    }
    
    public int getCount() {
        return count;
    }
}

// ✅ С synchronized
public class SynchronizedCounter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

// ✅✅ С AtomicInteger (лучший выбор)
public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();
    }
    
    public int getCount() {
        return count.get();
    }
}

Вывод

Основные методы борьбы с Race Condition:

  1. Immutable Objects — лучший вариант
  2. Volatile — для флагов
  3. Atomic Classes — для счётчиков (предпочтительно)
  4. synchronized — классический способ
  5. ReentrantLock — больше контроля
  6. ReadWriteLock — оптимизация для читающих
  7. Thread-safe Collections — ConcurrentHashMap и т.д.
  8. Executor Service — правильное управление потоками

Основной принцип: Минимизируй mutable shared state (изменяемое общее состояние).

Какие знаешь методы борьбы с Race Condition? | PrepBro