Комментарии (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); // Атомарно
Лучшие практики
- Используйте synchronized для простых случаев
- Используйте ReentrantLock для гибкости
- Минимизируйте время в критичной секции
- Избегайте вложенных locks
- Всегда освобождайте lock в finally
- Профилируйте contention (конкуренцию)
- Рассмотрите atomic классы для счетчиков
- Предпочитайте immutable объекты чтобы избежать синхронизации
Lock и Mutex — это фундаментальные механизмы для безопасной многопоточности в Java.