Как реализовать пессимистическую блокировку в Java?

Ответ

Пессимистическая блокировка предполагает, что конфликт при совместном доступе к ресурсу вероятен, поэтому поток заранее захватывает эксклюзивную блокировку.

1. На уровне языка (синхронизация)

Использование ключевого слова synchronized или класса ReentrantLock из java.util.concurrent.locks.

Пример с ReentrantLock:

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

public class PessimisticCounter {
    private int value = 0;
    private final Lock lock = new ReentrantLock(); // Пессимистическая блокировка

    public void safeIncrement() {
        lock.lock(); // Захват блокировки. Поток будет ждать, если блокировка у другого.
        try {
            value++; // Критическая секция защищена
        } finally {
            lock.unlock(); // ОСВОБОЖДЕНИЕ в finally — ОБЯЗАТЕЛЬНО!
        }
    }

    public int getValue() {
        lock.lock();
        try {
            return value;
        } finally {
            lock.unlock();
        }
    }
}

2. На уровне базы данных (JDBC)

Использование SELECT ... FOR UPDATE для блокировки строк.

Пример с JDBC и транзакцией:

Connection conn = dataSource.getConnection();
try {
    conn.setAutoCommit(false); // Начало транзакции

    // 1. ПЕССИМИСТИЧЕСКАЯ БЛОКИРОВКА строки
    String selectSQL = "SELECT balance FROM accounts WHERE id = ? FOR UPDATE";
    PreparedStatement selectStmt = conn.prepareStatement(selectSQL);
    selectStmt.setInt(1, accountId);
    ResultSet rs = selectStmt.executeQuery();
    // Строка с accountId теперь заблокирована для других транзакций

    // 2. Чтение и модификация данных
    int currentBalance = 0;
    if (rs.next()) {
        currentBalance = rs.getInt("balance");
    }
    rs.close();
    selectStmt.close();

    int newBalance = currentBalance - 100;

    // 3. Обновление заблокированной строки
    String updateSQL = "UPDATE accounts SET balance = ? WHERE id = ?";
    PreparedStatement updateStmt = conn.prepareStatement(updateSQL);
    updateStmt.setInt(1, newBalance);
    updateStmt.setInt(2, accountId);
    updateStmt.executeUpdate();
    updateStmt.close();

    conn.commit(); // Фиксация транзакции и снятие блокировки
} catch (SQLException e) {
    conn.rollback(); // Откат в случае ошибки
    throw e;
} finally {
    conn.setAutoCommit(true);
    conn.close();
}
Сравнение с оптимистической блокировкой: Аспект Пессимистическая Оптимистическая
Философия Конфликты часты, блокируем заранее. Конфликты редки, проверяем при коммите.
Механизм synchronized, Lock, FOR UPDATE. Версионность (version column), CAS.
Производительность Может быть ниже из- ожидания блокировок. Выше при низкой конкуренции.
Применение Высокая конкуренция, критичные данные. Чтение >> записи, низкая конкуренция.

Ответ 18+ 🔞

А, слушай, пессимистическая блокировка — это когда ты такой заранее уверен, что все вокруг — мудаки, которые норовят тебе нагадить в общий ресурс. Ну, типа, конфликт — это не «если», а «когда». Поэтому подход простой, как три копейки: подошёл, схватил за горло этот ресурс, повесил на него здоровенный замок и кричишь «Моё! Пока я не отпущу — никому ни хуя!».

1. На уровне языка (синхронизация)

Тут всё просто: либо synchronized, либо этот серьёзный дядька — ReentrantLock из пакета java.util.concurrent.locks. Он как бугай с бейсбольной битой у входа в бар: «Проход закрыт, пока я не закончу свои дела».

Смотри, как это выглядит с ReentrantLock:

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

public class PessimisticCounter {
    private int value = 0;
    private final Lock lock = new ReentrantLock(); // Вот он, наш пессимист-бугай

    public void safeIncrement() {
        lock.lock(); // Захватываем блокировку. Если занято — сиди и жди, как лох у парадной.
        try {
            value++; // А вот тут наша священная критическая секция. Теперь она в безопасности.
        } finally {
            lock.unlock(); // ОСВОБОЖДАЕМ в finally — это святое, иначе все зависнут и будут тебя проклинать!
        }
    }

    public int getValue() {
        lock.lock();
        try {
            return value;
        } finally {
            lock.unlock();
        }
    }
}

2. На уровне базы данных (JDBC)

Тут уже настоящая магия, или, скорее, криминал. Используем SELECT ... FOR UPDATE. Эта штука говорит базе данных: «Слушай, сука, вот эту строчку я пометил. Пока я с ней не разберусь — все остальные пусть идут нахуй и ждут».

Вот тебе живой пример с JDBC:

Connection conn = dataSource.getConnection();
try {
    conn.setAutoCommit(false); // Поехали, начинаем транзакцию. Точка невозврата.

    // 1. ПЕССИМИСТИЧЕСКИЙ ЗАХВАТ строки, блядь!
    String selectSQL = "SELECT balance FROM accounts WHERE id = ? FOR UPDATE";
    PreparedStatement selectStmt = conn.prepareStatement(selectSQL);
    selectStmt.setInt(1, accountId);
    ResultSet rs = selectStmt.executeQuery();
    // Всё. Строка с accountId теперь в наших оковах. Остальные транзакции могут только плакать.

    // 2. Читаем и колдуем над данными
    int currentBalance = 0;
    if (rs.next()) {
        currentBalance = rs.getInt("balance");
    }
    rs.close();
    selectStmt.close();

    int newBalance = currentBalance - 100; // Списание, например.

    // 3. Апдейтим эту уже захваченную и беспомощную строку
    String updateSQL = "UPDATE accounts SET balance = ? WHERE id = ?";
    PreparedStatement updateStmt = conn.prepareStatement(updateSQL);
    updateStmt.setInt(1, newBalance);
    updateStmt.setInt(2, accountId);
    updateStmt.executeUpdate();
    updateStmt.close();

    conn.commit(); // Ура! Фиксируем изменения и снимаем блокировку. Все свободны.
} catch (SQLException e) {
    conn.rollback(); // Если что-то пошло не так — откатываемся, как будто ничего и не было. И блокировка тоже падает.
    throw e;
} finally {
    conn.setAutoCommit(true);
    conn.close();
}

А теперь, чтобы ты не запутался, смотри сравнение с его вечно улыбающимся оптимистичным братаном:

Аспект Пессимистическая (Наш суровый чел) Оптимистическая (Тот, кто верит в хорошее)
Философия Конфликты — это норма жизни. Хватай первым, потом разбирайся. Конфликты — редкость. Давай сначала поработаем, а в конце проверим, не обосрал ли кто наш результат.
Механизм synchronized, Lock, SELECT ... FOR UPDATE. Версионность (колонка version), Compare-and-Swap (CAS).
Производительность Может проседать, потому что потоки тупо стоят и ждут, пока один мудак со своей блокировкой разберётся. Летит как пуля, если все друг другу не мешают. Но если конфликт всё-таки случился — переделывай всё заново, ебать его в сраку.
Когда применять Когда драка за ресурс — это каждый день. Высокая конкуренция, данные критичные до нельзя. Когда все в основном читают, а пишут редко. Низкая конкуренция, все приличные люди.

Короче, выбирай по обстановке. Если вокруг одни хамы и дикари — вешай замки. Если цивилизация и порядок — можно попробовать верить в лучшее. Но помни, чувак: доверия ебать ноль, а подозрение ёбаное чувствую всегда.