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

Какие знаешь проблемы при работе с многопоточностью?

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

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

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

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

Проблемы при работе с многопоточностью

Многопоточность - мощный инструмент, но и сложный в управлении. Вот основные проблемы, с которыми я регулярно сталкиваюсь:

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

Несколько потоков одновременно обращаются к одному ресурсу, и результат зависит от порядка выполнения.

// Проблема
public class Counter {
    private int count = 0;
    
    public void increment() {
        count++; // Эта операция НЕ атомарна!
        // 1. Прочитать count
        // 2. Увеличить на 1
        // 3. Записать обратно
    }
}

// Два потока вызывают increment() одновременно:
// Thread 1: читает 0, записывает 1
// Thread 2: читает 0, записывает 1
// Результат: 1, вместо 2

// Решение 1: synchronized
public synchronized void increment() {
    count++;
}

// Решение 2: AtomicInteger
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
    count.incrementAndGet();
}

2. Deadlock (Взаимная блокировка)

Два или более потока ждут друг друга и заблокированы навечно.

// Классический deadlock
public class BankAccount {
    private int balance = 0;
    
    public synchronized void transfer(BankAccount other, int amount) {
        this.balance -= amount;
        other.deposit(amount); // Здесь будет блокировка!
    }
    
    public synchronized void deposit(int amount) {
        this.balance += amount;
    }
}

// Проблема:
// Thread 1: блокирует account1, пытается заблокировать account2
// Thread 2: блокирует account2, пытается заблокировать account1
// Deadlock!

// Решение: порядок лок-объектов
public class BankAccount {
    private int balance = 0;
    private static final Object LOCK = new Object();
    
    public void transfer(BankAccount other, int amount) {
        // Всегда одинаковый порядок блокировки
        BankAccount first = this.balance < other.balance ? this : other;
        BankAccount second = this.balance < other.balance ? other : this;
        
        synchronized(first) {
            synchronized(second) {
                first.balance -= amount;
                second.balance += amount;
            }
        }
    }
}

3. Starvation (Голодание потока)

Поток не получает доступ к ресурсу и остаётся в очереди надолго.

// Проблема с приоритетами
for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        synchronized(resource) {
            doWork();
        }
    }, "Priority-High-" + i).start();
}

// Низкоприоритетные потоки могут никогда не выполниться
// Решение: ReentrantReadWriteLock, FairSync
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); // fair

4. Visibility Problem (Видимость памяти)

Один поток изменяет переменную, другой не видит изменения.

// Проблема
public class Flag {
    private boolean ready = false; // Может кэшироваться в register CPU
    
    public void set() {
        ready = true;
    }
    
    public void waitForReady() {
        while (!ready) { // Может никогда не завершиться!
            // Второй поток может видеть кэшированное значение false
        }
    }
}

// Решение 1: volatile
private volatile boolean ready = false;

// Решение 2: synchronized
private boolean ready = false;
public synchronized void set() {
    ready = true;
}
public synchronized boolean isReady() {
    return ready;
}

5. Livelock (Живая блокировка)

Потоки активны, но не совершают полезную работу.

// Пример: потоки постоянно откатывают друг друга
public void doWork() {
    while (true) {
        if (tryLock(resource1)) {
            if (tryLock(resource2)) {
                // Делаем работу
                break;
            } else {
                unlock(resource1); // Откатываем!
            }
        }
    }
}

// Решение: время ожидания, back-off стратегия
Thread.sleep(random.nextInt(100)); // random back-off

6. ConcurrentModificationException

Изменение коллекции во время итерации.

// Проблема
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");

for (String item : list) {
    if (item.equals("a")) {
        list.remove(item); // ConcurrentModificationException!
    }
}

// Решение
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    if (item.equals("a")) {
        it.remove(); // Безопасно
    }
}

7. Double-Checked Locking

Ошибка при ленивой инициализации.

// Небезопасно (даже с synchronized)
if (instance == null) { // Первая проверка
    synchronized(this) {
        if (instance == null) { // Вторая проверка
            instance = new Singleton();
        }
    }
}

// Правильно
private volatile Singleton instance; // volatile!
if (instance == null) {
    synchronized(this) {
        if (instance == null) {
            instance = new Singleton();
        }
    }
}

Инструменты для борьбы

  • synchronized — простая синхронизация
  • volatile — гарантирует видимость памяти
  • AtomicInteger/AtomicReference — атомарные операции без лока
  • ReentrantLock — более гибкая альтернатива synchronized
  • ReadWriteLock — раздельные блокировки для чтения/письма
  • CountDownLatch/Semaphore — синхронизация потоков
  • ThreadLocal — данные, изолированные по потокам

Best Practices

  1. Минимизируй критические секции
  2. Избегай nested locks
  3. Используй высокоуровневые инструменты (Stream, CompletableFuture)
  4. Тестируй многопоточный код тщательно
  5. Используй stress-тесты и ThreadSanitizer

Многопоточность требует глубокого понимания и аккуратного проектирования. Ошибки в многопоточном коде часто проявляются редко и сложно воспроизводятся.

Какие знаешь проблемы при работе с многопоточностью? | PrepBro