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

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

1.8 Middle🔥 131 комментариев
#Docker, Kubernetes и DevOps

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

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

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

Оптимистическая блокировка в Java

Оптимистическая блокировка — это стратегия управления конкурентностью, при которой мы не блокируем ресурс при чтении. Вместо этого, перед обновлением проверяем, изменилась ли версия данных со времени нашего последнего чтения. Это особенно полезно в многопоточных приложениях с низкой контention-ностью.

Основной принцип

  1. Читаем данные вместе с версией (версионный номер или timestamp)
  2. Выполняем логику без блокировок
  3. При попытке записи проверяем: версия всё ещё та же?
  4. Если версия совпадает — обновляем данные и версию
  5. Если версия изменилась — повторяем операцию

Пример с использованием Hibernate/JPA

// Модель с версионированием
@Entity
@Table(name = "accounts")
public class Account {
    @Id
    private Long id;
    
    private BigDecimal balance;
    
    // Ключевое поле для оптимистической блокировки
    @Version
    private Long version;
    
    // getters/setters
}

Вот как это работает в коде:

// Сервис с оптимистической блокировкой
@Service
public class AccountService {
    @Autowired
    private AccountRepository accountRepository;
    
    @Transactional
    public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
        // Читаем счёт БЕЗ блокировки (версия присутствует в объекте)
        Account fromAccount = accountRepository.findById(fromAccountId)
            .orElseThrow(() -> new RuntimeException("Account not found"));
        Account toAccount = accountRepository.findById(toAccountId)
            .orElseThrow(() -> new RuntimeException("Account not found"));
        
        // Выполняем бизнес-логику
        fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
        toAccount.setBalance(toAccount.getBalance().add(amount));
        
        // При сохранении Hibernate проверит версию
        // Если версия изменилась с момента чтения — выбросит OptimisticLockingFailureException
        accountRepository.save(fromAccount);
        accountRepository.save(toAccount);
    }
}

Обработка конфликтов

@Service
public class AccountServiceWithRetry {
    @Autowired
    private AccountRepository accountRepository;
    
    @Transactional(rollbackFor = OptimisticLockingFailureException.class)
    public void transferMoneyWithRetry(Long fromAccountId, Long toAccountId, BigDecimal amount) {
        int retryCount = 0;
        final int MAX_RETRIES = 3;
        
        while (retryCount < MAX_RETRIES) {
            try {
                transferMoney(fromAccountId, toAccountId, amount);
                return; // Успешно
            } catch (OptimisticLockingFailureException e) {
                retryCount++;
                if (retryCount >= MAX_RETRIES) {
                    throw new RuntimeException("Transaction failed after " + MAX_RETRIES + " retries", e);
                }
                // Пауза перед повтором
                Thread.sleep(100 * retryCount);
            }
        }
    }
    
    @Transactional
    private void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
        Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow();
        Account toAccount = accountRepository.findById(toAccountId).orElseThrow();
        
        fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
        toAccount.setBalance(toAccount.getBalance().add(amount));
        
        accountRepository.save(fromAccount);
        accountRepository.save(toAccount);
    }
}

Реализация без ORM (вручную)

public class OptimisticLockingExample {
    // Таблица: id, balance, version
    // version увеличивается при каждом обновлении
    
    public boolean updateBalance(Long accountId, BigDecimal newBalance, Long currentVersion) {
        String sql = "UPDATE accounts SET balance = ?, version = version + 1 " +
                     "WHERE id = ? AND version = ?";
        
        try (Connection conn = getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            
            stmt.setBigDecimal(1, newBalance);
            stmt.setLong(2, accountId);
            stmt.setLong(3, currentVersion);
            
            int affectedRows = stmt.executeUpdate();
            
            // Если affectedRows == 0, значит версия изменилась
            if (affectedRows == 0) {
                throw new OptimisticLockException("Version conflict. Retry operation.");
            }
            
            return true;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

Преимущества и недостатки

Преимущества:

  • ✓ Нет блокировок, высокая пропускная способность
  • ✓ Нет риска deadlock'ов
  • ✓ Хорошо работает при низкой контention-ности

Недостатки:

  • ✗ Если часто происходят конфликты, много retries
  • ✗ Требует повторных попыток при конфликте версии
  • ✗ Сложнее в отладке, чем пессимистическая блокировка