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

Что нужно учитывать при использовании пессимистичных блокировок?

3.0 Senior🔥 121 комментариев
#ORM и Hibernate

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

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

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

Пессимистичные блокировки в многопоточной среде

Пессимистичная блокировка (pessimistic locking) — это механизм контроля одновременного доступа, когда ресурс заблокирован перед началом операции с предположением, что конфликты вероятны. Это требует внимательного подхода к разработке.

Основные проблемы и что учитывать

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

Deadlock — самая опасная проблема пессимистичных блокировок. Возникает, когда два или более потока ждут друг друга.

// ❌ Проблемный код — может привести к deadlock
public class Account {
    private long balance;
    private final Object lock = new Object();
    
    public void transfer(Account from, Account to, long amount) {
        synchronized (from.lock) {  // Поток А берёт lock от from
            synchronized (to.lock) {   // Ждёт lock от to...
                if (from.balance >= amount) {
                    from.balance -= amount;
                    to.balance += amount;
                }
            }
        }
    }
}

// Deadlock сценарий:
// Поток 1: transfer(account1, account2)
// Поток 2: transfer(account2, account1)
// Поток 1 ждёт lock2, а Поток 2 ждёт lock1 → DEADLOCK!

Решение — единообразный порядок блокировок:

// ✓ Правильно — всегда блокируем в одинаковом порядке
public void transfer(Account from, Account to, long amount) {
    Account first = from.id < to.id ? from : to;
    Account second = from.id < to.id ? to : from;
    
    synchronized (first.lock) {
        synchronized (second.lock) {
            if (from.balance >= amount) {
                from.balance -= amount;
                to.balance += amount;
            }
        }
    }
}

2. Timeout при получении блокировки

import java.util.concurrent.locks.ReentrantLock;

ReentrantLock lock = new ReentrantLock();

// ❌ Неправильно — бесконечное ожидание
lock.lock();
try {
    // Критическая секция
} finally {
    lock.unlock();
}

// ✓ Правильно — с timeout
boolean acquired = lock.tryLock(5, TimeUnit.SECONDS);
if (acquired) {
    try {
        // Критическая секция
    } finally {
        lock.unlock();
    }
} else {
    System.out.println("Не удалось получить блокировку");
}

3. Время удержания блокировки (Lock Holding Time)

Критическая проблема: чем дольше поток держит блокировку, тем больше других потоков ждут.

// ❌ Неправильно — долгое удержание блокировки
synchronized (lock) {
    // Быстрые операции
    data.modify();
    
    // Медленная операция (I/O, сетевой запрос) — ПЛОХО!
    database.save(data);  // 500ms!
    
    // Все остальные потоки ждали эти 500ms
}

// ✓ Правильно — минимальное время блокировки
synchronized (lock) {
    data.modify();
    Data snapshot = data.copy();  // Копируем данные
}
// Выпускаем блокировку
database.save(snapshot);  // Выполняем вне блокировки

4. Иерархия блокировок (Lock Ordering)

Если нужны несколько блокировок, используй консистентный порядок:

// ✓ Правильно — документируем иерархию
public class LockHierarchy {
    // Уровень 1: глобальная блокировка
    private final Object globalLock = new Object();
    
    // Уровень 2: блокировки ресурсов
    private final Object resource1Lock = new Object();
    private final Object resource2Lock = new Object();
    
    // ВСЕГДА соблюдаем порядок: global → resource1 → resource2
    public void criticalOperation() {
        synchronized (globalLock) {
            synchronized (resource1Lock) {
                synchronized (resource2Lock) {
                    // Операция
                }
            }
        }
    }
}

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

// ❌ Проблемный код — некоторые потоки могут не получить доступ
public class PoorLocking {
    private int counter = 0;
    
    // Читающие потоки берут блокировку даже для чтения
    public synchronized int read() {
        return counter;
    }
    
    public synchronized void write(int value) {
        counter = value;
    }
}

// ✓ Правильно — используем ReadWriteLock
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class BetterLocking {
    private int counter = 0;
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    
    // Множество потоков могут читать одновременно
    public int read() {
        rwLock.readLock().lock();
        try {
            return counter;
        } finally {
            rwLock.readLock().unlock();
        }
    }
    
    // Писать может только один поток
    public void write(int value) {
        rwLock.writeLock().lock();
        try {
            counter = value;
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

6. Live Lock (Живая блокировка)

Потоки активны, но прогресса нет:

// ❌ Проблемный код
public void operation() {
    while (true) {
        if (lock.tryLock()) {
            try {
                // Операция
                break;
            } finally {
                lock.unlock();
            }
        }
        // Бесконечно пытаемся — CPU в 100%!
    }
}

// ✓ Правильно — используем exponential backoff
public void operation() throws InterruptedException {
    int backoffMs = 1;
    while (true) {
        if (lock.tryLock(backoffMs, TimeUnit.MILLISECONDS)) {
            try {
                // Операция
                break;
            } finally {
                lock.unlock();
            }
        }
        backoffMs = Math.min(backoffMs * 2, 1000);  // Растём до 1 секунды
    }
}

7. Производительность (Performance Impact)

// ✓ Используй более гранулярные блокировки
public class FineGrainedLocking {
    private final List<Object> locks = new ArrayList<>();
    private final List<String> data = new ArrayList<>();
    
    // Каждому элементу свой lock — высокий параллелизм
    public void update(int index, String value) {
        synchronized (locks.get(index)) {
            data.set(index, value);
        }
    }
}

8. Условные переменные (Condition Variables)

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

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

// Производитель
public void produce(String item) throws InterruptedException {
    lock.lock();
    try {
        queue.add(item);
        condition.signalAll();  // Уведомляем потребителей
    } finally {
        lock.unlock();
    }
}

// Потребитель
public String consume() throws InterruptedException {
    lock.lock();
    try {
        while (queue.isEmpty()) {
            condition.await();  // Ждём сигнала
        }
        return queue.poll();
    } finally {
        lock.unlock();
    }
}

Чеклист при использовании пессимистичных блокировок

  1. Deadlock prevention: соблюдай единообразный порядок захвата блокировок
  2. Minimizing lock time: минимизируй время удержания блокировки
  3. Timeout handling: используй tryLock() с timeout вместо lock()
  4. Lock hierarchy: документируй и соблюдай иерархию блокировок
  5. Read-Write locks: используй для множественного чтения
  6. Condition variables: для синхронизации между потоками
  7. Monitoring: отслеживай contentious locks через мониторинг
  8. Fallback strategy: имей план действий при timeout

Вывод: пессимистичные блокировки требуют глубокого понимания многопоточности. Всегда рассмотри альтернативы (ConcurrentHashMap, CompletableFuture, reactive programmming) перед использованием явного синхронизацией.