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

Что такое Lock у Mutex?

2.0 Middle🔥 91 комментариев
#Основы Java

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

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

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

Lock у Mutex: Взаимное исключение в многопоточности

Mutex (Mutual Exclusion — взаимное исключение) — это синхронизационный механизм в многопоточном программировании, который гарантирует, что только один поток может получить доступ к критичной секции кода одновременно. Lock — это акт захвата мьютекса потоком.

Базовые концепции

Что такое Mutex

Mutex — это объект, который может быть "захвачен" только одним потоком за раз:

Мьютекс: [ СВОБОДЕН ] <- Поток может захватить

После захвата потоком A:
Мьютекс: [ ЗАХВАЧЕН (Поток A) ] <- Другие потоки ждут

После освобождения потоком A:
Мьютекс: [ СВОБОДЕН ] <- Ждущие потоки могут захватить

Что такое Lock

Lock — это действие захвата мьютекса. Когда поток "получает lock", он получает исключительный доступ к защищаемому ресурсу.

Проблема без Mutex/Lock

// БЕЗ синхронизации: Race Condition
public class UnsafeCounter {
    private int count = 0;
    
    public void increment() {
        count++;  // НЕ атомарна! count++ = read, increment, write
    }
    
    public int getCount() {
        return count;
    }
}

// Проблема:
UnsafeCounter counter = new UnsafeCounter();

Thread t1 = new Thread(() -> {
    for (int i = 0; i < 1000; i++) {
        counter.increment();
    }
});

Thread t2 = new Thread(() -> {
    for (int i = 0; i < 1000; i++) {
        counter.increment();
    }
});

t1.start();
t2.start();
t1.join();
t2.join();

System.out.println(counter.getCount());  // Может быть < 2000!
// Ожидается 2000, но может быть 1500, 1800 или что угодно

Решение: Mutex/Lock

// С синхронизацией (implicit lock в Java)
public class SafeCounter {
    private int count = 0;
    private final Object lock = new Object();  // Мьютекс
    
    public void increment() {
        synchronized(lock) {  // Захватить lock
            count++;  // Теперь атомарна
        }  // Освободить lock
    }
    
    public int getCount() {
        synchronized(lock) {
            return count;
        }
    }
}

// Или на уровне метода
public class SafeCounterV2 {
    private int count = 0;
    
    public synchronized void increment() {  // lock на this
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

Механизм работы Lock

Время | Поток A              | Поток B              | count
------|----------------------|----------------------|--------
1     | increment() BEGIN    |                      | 0
2     | LOCK (получил)       |                      | 0
3     | count++              |                      | 1
4     |                      | increment() BEGIN    | 1
5     |                      | LOCK (ждет) WAIT     | 1
6     | UNLOCK               |                      | 1
7     |                      | LOCK (получил)       | 1
8     |                      | count++              | 2
9     |                      | UNLOCK               | 2

java.util.concurrent.locks в Java

ReentrantLock (явная синхронизация)

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private int count = 0;
    private final Lock lock = new ReentrantLock();  // Мьютекс
    
    public void increment() {
        lock.lock();  // Захватить lock
        try {
            count++;  // Критичная секция
        } finally {
            lock.unlock();  // Всегда освободить lock
        }
    }
    
    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

ReadWriteLock (разделенные блокировки)

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private String value = "initial";
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    
    // Много потоков могут читать одновременно
    public String getValue() {
        rwLock.readLock().lock();
        try {
            return value;  // Несколько читателей одновременно OK
        } finally {
            rwLock.readLock().unlock();
        }
    }
    
    // Только один поток может писать (и читать другие не могут)
    public void setValue(String newValue) {
        rwLock.writeLock().lock();
        try {
            this.value = newValue;  // Исключительный доступ
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

StampedLock (оптимистичные lock)

import java.util.concurrent.locks.StampedLock;

public class StampedLockExample {
    private double x = 0.0;
    private double y = 0.0;
    private final StampedLock lock = new StampedLock();
    
    // Оптимистичное чтение: попытаться без lock
    public double getDistance() {
        long stamp = lock.tryOptimisticRead();  // Марка времени
        double currentX = x;
        double currentY = y;
        
        if (!lock.validate(stamp)) {  // Проверить, изменилось ли
            stamp = lock.readLock();  // Перейти на реальный read lock
            try {
                currentX = x;
                currentY = y;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
    
    // Запись требует эксклюзивного lock
    public void move(double dx, double dy) {
        long stamp = lock.writeLock();  // Захватить эксклюзивный lock
        try {
            x += dx;
            y += dy;
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

Семантика Lock

1. Acquire (Захват)

lock.lock();  // Попытаться захватить
// Если мьютекс занят, поток ЖДЕТ
// Если свободен, потокполучает доступ

2. Hold (Удержание)

lock.lock();
try {
    // Критичная секция
    // Только этот поток может выполнять код здесь
    resource.doSomething();
} finally {
    lock.unlock();
}

3. Release (Освобождение)

lock.unlock();  // Освободить lock
// Один из ждущих потоков получит доступ

Типы Lock в Java

ТипИспользованиеОсобенности
synchronizedметоды, блокиВстроена в язык, не переэнтрантна
ReentrantLockявные критичные секцииЯвное acquire/release, переэнтрантна
ReadWriteLockмного читателей, мало писателейРазделенные блокировки
StampedLockвысокая производительностьОптимистичные reads, сложнее
Semaphoreуправление пулом ресурсовСчитающий семафор

Deadlock: опасность неправильного использования Lock

// ОПАСНО: Deadlock!
public class DeadlockExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    // Поток A
    public void methodA() {
        synchronized(lock1) {
            System.out.println("A has lock1");
            try { Thread.sleep(100); } catch (Exception e) {}
            synchronized(lock2) {
                System.out.println("A has lock2");  // Может не достичь
            }
        }
    }
    
    // Поток B
    public void methodB() {
        synchronized(lock2) {
            System.out.println("B has lock2");
            try { Thread.sleep(100); } catch (Exception e) {}
            synchronized(lock1) {
                System.out.println("B has lock1");  // Может не достичь
            }
        }
    }
}

// Сценарий Deadlock:
// T1: acquires lock1
// T2: acquires lock2
// T1: waits for lock2 (held by T2)
// T2: waits for lock1 (held by T1)
// DEADLOCK! Оба потока ждут друг друга

Правильное использование Lock: Best Practices

1. Всегда освобождайте Lock

// ПРАВИЛЬНО
Lock lock = new ReentrantLock();
lock.lock();
try {
    // критичная секция
} finally {
    lock.unlock();  // Выполнится даже при исключении
}

// НЕПРАВИЛЬНО
lock.lock();
// критичная секция
lock.unlock();  // Может не выполниться при исключении

2. Используйте try-with-resources (если возможно)

class LockResourceCloseable implements AutoCloseable {
    private Lock lock;
    
    public LockResourceCloseable(Lock lock) {
        this.lock = lock;
        lock.lock();
    }
    
    @Override
    public void close() {
        lock.unlock();
    }
}

// Использование
try (LockResourceCloseable resource = 
    new LockResourceCloseable(lock)) {
    // критичная секция
}  // Автоматически unlock

3. Минимизируйте время в критичной секции

// ПЛОХО: долго в lock
lock.lock();
try {
    // тяжелое вычисление
    int result = slowCalculation();  // 1 секунда
    
    // обновление
    data.setValue(result);  // 1 миллисекунда
} finally {
    lock.unlock();
}

// ХОРОШО: вычисление вне lock
int result = slowCalculation();  // Без lock

lock.lock();
try {
    data.setValue(result);  // В lock только необходимое
} finally {
    lock.unlock();
}

4. Избегайте nested locks (если возможно)

// ОПАСНО: вложенные locks
lock1.lock();
try {
    lock2.lock();  // Риск deadlock
    try {
        // код
    } finally {
        lock2.unlock();
    }
} finally {
    lock1.unlock();
}

// ЛУЧШЕ: один lock
lock1.lock();
try {
    // код
} finally {
    lock1.unlock();
}

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

import java.util.concurrent.locks.ReentrantLock;

public class ThreadSafeCounter {
    private long count = 0;
    private final ReentrantLock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
    
    public void decrement() {
        lock.lock();
        try {
            count--;
        } finally {
            lock.unlock();
        }
    }
    
    public long getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
    
    // Использование
    public static void main(String[] args) throws InterruptedException {
        ThreadSafeCounter counter = new ThreadSafeCounter();
        
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                counter.increment();
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                counter.increment();
            }
        });
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        System.out.println("Final count: " + counter.getCount());  // 20000
    }
}

Альтернативы Lock в Java

1. AtomicInteger (без явного lock)

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();  // Атомарна, без явного lock
    }
    
    public int getCount() {
        return count.get();
    }
}

2. ConcurrentHashMap (синхронизированная коллекция)

import java.util.concurrent.ConcurrentHashMap;

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
map.computeIfPresent("key", (k, v) -> v + 1);  // Атомарно

Лучшие практики

  1. Используйте synchronized для простых случаев
  2. Используйте ReentrantLock для гибкости
  3. Минимизируйте время в критичной секции
  4. Избегайте вложенных locks
  5. Всегда освобождайте lock в finally
  6. Профилируйте contention (конкуренцию)
  7. Рассмотрите atomic классы для счетчиков
  8. Предпочитайте immutable объекты чтобы избежать синхронизации

Lock и Mutex — это фундаментальные механизмы для безопасной многопоточности в Java.

Что такое Lock у Mutex? | PrepBro