← Назад к вопросам
Приведи пример оптимистической блокировки в Java
1.8 Middle🔥 131 комментариев
#Docker, Kubernetes и DevOps
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Оптимистическая блокировка в Java
Оптимистическая блокировка — это стратегия управления конкурентностью, при которой мы не блокируем ресурс при чтении. Вместо этого, перед обновлением проверяем, изменилась ли версия данных со времени нашего последнего чтения. Это особенно полезно в многопоточных приложениях с низкой контention-ностью.
Основной принцип
- Читаем данные вместе с версией (версионный номер или timestamp)
- Выполняем логику без блокировок
- При попытке записи проверяем: версия всё ещё та же?
- Если версия совпадает — обновляем данные и версию
- Если версия изменилась — повторяем операцию
Пример с использованием 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
- ✗ Требует повторных попыток при конфликте версии
- ✗ Сложнее в отладке, чем пессимистическая блокировка