Что нужно учитывать при использовании пессимистичных блокировок?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Пессимистичные блокировки в многопоточной среде
Пессимистичная блокировка (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();
}
}
Чеклист при использовании пессимистичных блокировок
- Deadlock prevention: соблюдай единообразный порядок захвата блокировок
- Minimizing lock time: минимизируй время удержания блокировки
- Timeout handling: используй
tryLock()с timeout вместоlock() - Lock hierarchy: документируй и соблюдай иерархию блокировок
- Read-Write locks: используй для множественного чтения
- Condition variables: для синхронизации между потоками
- Monitoring: отслеживай contentious locks через мониторинг
- Fallback strategy: имей план действий при timeout
Вывод: пессимистичные блокировки требуют глубокого понимания многопоточности. Всегда рассмотри альтернативы (ConcurrentHashMap, CompletableFuture, reactive programmming) перед использованием явного синхронизацией.