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

Для чего нужен Mutex?

1.3 Junior🔥 141 комментариев
#Многопоточность

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

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

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

Для чего нужен Mutex

Mutex (mutual exclusion) — это один из самых фундаментальных концептов многопоточного программирования. За 10+ лет я видел системы, которые тормозили или крашились из-за race conditions. Давайте разберёмся почему Mutex критичный.

Проблема: Race Condition

Представьте счётный банков с балансом 1000 рублей. Два потока (два клиента) одновременно снимают по 500 рублей:

public class BankAccount {
    private int balance = 1000;
    
    // БЕЗ Mutex (race condition!)
    public void withdraw(int amount) {
        int temp = balance;           // Шаг 1
        temp = temp - amount;         // Шаг 2
        balance = temp;               // Шаг 3
    }
}

// Timeline:
Thread 1: balance = 1000, читает: temp1 = 1000
Thread 2: balance = 1000, читает: temp2 = 1000
Thread 1: вычисляет: temp1 = 1000 - 500 = 500
Thread 2: вычисляет: temp2 = 1000 - 500 = 500
Thread 1: пишет: balance = 500
Thread 2: пишет: balance = 500  ← НЕПРАВИЛЬНО! Должно быть 0

Результат: оба снимали по 500, но баланс = 500 (вместо 0)
Банк потерял 500 рублей!

Это race condition. Результат зависит от timing, что делает bug непредсказуемым.

Решение: Mutex

Mutex гарантирует, что только один поток может выполнять код одновременно:

public class BankAccount {
    private int balance = 1000;
    private final Object lock = new Object();  // Mutex
    
    // С Mutex (безопасно!)
    public void withdraw(int amount) {
        synchronized (lock) {  // Захватываем mutex
            int temp = balance;     // Шаг 1
            temp = temp - amount;   // Шаг 2
            balance = temp;         // Шаг 3
        }  // Освобождаем mutex
    }
}

// Timeline:
Thread 1: захватывает lock
Thread 1: balance = 1000, читает: temp1 = 1000
Thread 1: вычисляет: temp1 = 1000 - 500 = 500
Thread 1: пишет: balance = 500
Thread 1: освобождает lock

Thread 2: захватывает lock (ждал, пока Thread 1 кончит)
Thread 2: balance = 500, читает: temp2 = 500
Thread 2: вычисляет: temp2 = 500 - 500 = 0
Thread 2: пишет: balance = 0
Thread 2: освобождает lock

Результат: balance = 0 ✅ Правильно!

Как работает Mutex в Java

1. Synchronized блок

private final Object lock = new Object();

public void criticalSection() {
    synchronized (lock) {
        // Только один поток может быть здесь одновременно
        // Остальные потоки ждут
        // ...
    }  // Автоматически освобождаем lock
}

2. Synchronized метод

public class SafeCounter {
    private int count = 0;
    
    // Эквивалентно: synchronized(this)
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

// Для static методов: synchronized(ClassName.class)
public static synchronized void staticMethod() {
    // Lock на класс, не на instance
}

3. ReentrantLock (современный подход)

public class BankAccount {
    private int balance = 1000;
    private final Lock lock = new ReentrantLock();
    
    public void withdraw(int amount) {
        lock.lock();  // Захватываем lock
        try {
            int temp = balance;
            temp = temp - amount;
            balance = temp;
        } finally {
            lock.unlock();  // Всегда освобождаем, даже если exception
        }
    }
}

Реальные примеры из моего опыта

Пример 1: Payment processing

public class PaymentProcessor {
    private final Lock transactionLock = new ReentrantLock();
    private Account fromAccount;
    private Account toAccount;
    
    public void transfer(BigDecimal amount) throws InsufficientFundsException {
        transactionLock.lock();
        try {
            // Без lock: race condition!
            // A может видеть balance от B пока B пишет
            
            if (fromAccount.getBalance().compareTo(amount) < 0) {
                throw new InsufficientFundsException();
            }
            
            fromAccount.debit(amount);
            toAccount.credit(amount);
            
            // Database запрос
            transactionRepository.save(transaction);
        } finally {
            transactionLock.unlock();
        }
    }
}

Без Mutex:

  • Thread A checks: balance = 1000 > 500? Yes
  • Thread B checks: balance = 1000 > 500? Yes
  • Thread A debits 500: balance = 500
  • Thread B debits 500: balance = -500 ← NEGATIVE!

С Mutex: Только один поток в раз может проверить и debited.

Пример 2: Inventory management

public class Inventory {
    private Map<String, Integer> stock = new HashMap<>();
    private final Lock inventoryLock = new ReentrantLock();
    
    public boolean reserveItem(String itemId) {
        inventoryLock.lock();
        try {
            Integer available = stock.getOrDefault(itemId, 0);
            
            if (available > 0) {
                stock.put(itemId, available - 1);
                return true;  // Reserved
            }
            return false;  // Out of stock
        } finally {
            inventoryLock.unlock();
        }
    }
}

Без Mutex:

  • Customer A checks: item available? Yes (stock = 5)
  • Customer B checks: item available? Yes (stock = 5)
  • Customer A reserves: stock = 4
  • Customer B reserves: stock = 4
  • But both got reserved from stock of 5! One oversold!

Tipos Mutex'a в Java

1. Synchronized (встроенный)

public synchronized void method() { }  // На instance
public static synchronized void method() { }  // На класс
synchronized(lock) { }  // На объект

Плюсы: Встроенный, автоматический unlock Минусы: Нельзя interruptible, нельзя timeout, простой

2. ReentrantLock

Lock lock = new ReentrantLock();
lock.lock();
try {
    // critical section
} finally {
    lock.unlock();
}

Плюсы: Гибкий, interruptible, can set timeout, fairness Минусы: Нужно помнить unlock, более многословный

3. ReadWriteLock (когда много читают, мало пишут)

ReadWriteLock rwLock = new ReentrantReadWriteLock();

public int read() {
    rwLock.readLock().lock();  // Много читателей могут быть одновременно
    try {
        return value;
    } finally {
        rwLock.readLock().unlock();
    }
}

public void write(int v) {
    rwLock.writeLock().lock();  // Только один писатель
    try {
        value = v;
    } finally {
        rwLock.writeLock().unlock();
    }
}

4. Semaphore (когда нужно N потоков максимум)

Semaphore semaphore = new Semaphore(3);  // Max 3 потока

public void limitedResource() throws InterruptedException {
    semaphore.acquire();  // Захватываем permit
    try {
        // Max 3 потока здесь одновременно
    } finally {
        semaphore.release();
    }
}

Deadlock (опасность Mutex'а)

// ❌ DEADLOCK!
public class Account {
    private int balance;
    private final Lock lock = new ReentrantLock();
    
    public void transfer(Account other, int amount) {
        lock.lock();
        try {
            other.lock.lock();  // ← DEADLOCK если other тоже пытается transfer в нас
            try {
                this.balance -= amount;
                other.balance += amount;
            } finally {
                other.lock.unlock();
            }
        } finally {
            lock.unlock();
        }
    }
}

// Сценарий deadlock:
Thread 1: transfer(account1 → account2, 100):
  1. Захватывает lock на account1 ✓
  2. Пытается захватить lock на account2 ← ЖДЁТ
  
Thread 2: transfer(account2 → account1, 200):
  1. Захватывает lock на account2 ✓
  2. Пытается захватить lock на account1 ← ЖДЁТ
  
// Они друг друга ждут! DEADLOCK!

Решение: Ordered locking

public void transfer(Account other, int amount) {
    // Всегда захватываем в одинаковом порядке (по ID)
    Account first = this.id < other.id ? this : other;
    Account second = this.id < other.id ? other : this;
    
    first.lock.lock();
    try {
        second.lock.lock();
        try {
            // Transfer logic
        } finally {
            second.lock.unlock();
        }
    } finally {
        first.lock.unlock();
    }
}

Performance consideration

Mutex имеет overhead:

// Synchronized быстрее для простых операций
for (int i = 0; i < 1000000; i++) {
    counter++;  // 1-10ns per operation
}

// Lock/unlock может быть 50-100ns (в зависимости от contention)
synchronized(lock) {
    counter++;
}

Оптимизация:

  1. Minimize critical section (короче lock'а = быстрее)
  2. Reduce contention (меньше потоков = меньше конфликтов)
  3. Use ReadWriteLock если много читают
  4. Use StampedLock (Java 8+) для очень высокой performance

Best practices

  1. Всегда используйте try-finally (или try-with-resources)
lock.lock();
try {
    // critical section
} finally {
    lock.unlock();  // Даже если exception!
}
  1. Минимизируйте время в critical section
// ❌ Плохо: I/O inside lock
synchronized(lock) {
    httpClient.get(url);  // SLOW!
}

// ✅ Хорошо: только самое necessary
synchronized(lock) {
    if (needsRefresh) {
        shouldFetch = true;
    }
}
if (shouldFetch) {
    data = httpClient.get(url);  // Outside lock
}
  1. Не вызывайте unknown методы в critical section
// ❌ Опасно: что если callback захватит другой lock?
synchronized(lock) {
    listener.onUpdate();  // Unknown code!
}

Вывод

Mutex нужен для:

  • Защиты shared state
  • Prevent race conditions
  • Гарантирования consistent data
  • Correct результатов в многопоточных приложениях

Без Mutex в многопоточной системе: хаос, data corruption, недетерминированное поведение.

С Mutex: безопасность, но нужно быть careful про deadlocks и performance.