← Назад к вопросам
Какие знаешь методы борьбы с 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:
- Immutable Objects — лучший вариант
- Volatile — для флагов
- Atomic Classes — для счётчиков (предпочтительно)
- synchronized — классический способ
- ReentrantLock — больше контроля
- ReadWriteLock — оптимизация для читающих
- Thread-safe Collections — ConcurrentHashMap и т.д.
- Executor Service — правильное управление потоками
Основной принцип: Минимизируй mutable shared state (изменяемое общее состояние).