乐观锁及其实现方式详解
概述
乐观锁是一种并发控制机制,它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。
乐观锁的核心思想是:在提交时检查数据是否被其他事务修改,如果没有修改则提交成功,否则回滚或重试。
实现原理
乐观锁的实现主要基于以下两个核心概念:
- 版本控制:通过版本号或时间戳来标识数据的修改状态
- 原子操作:使用原子操作来确保检查和更新的原子性
主要实现方式
1. 版本号法
版本号法是最常见的乐观锁实现方式,通过在数据表中添加一个版本字段,每次更新时检查版本号是否一致。
实现原理
- 读取数据时同时读取版本号
- 更新数据时检查版本号是否与读取时一致
- 如果一致则更新成功并递增版本号
- 如果不一致则更新失败,需要重试
Go语言实现
go
package main
import (
"database/sql"
"fmt"
"log"
"time"
)
// User 用户结构体
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Balance int `json:"balance"`
Version int `json:"version"`
}
// UserService 用户服务
type UserService struct {
db *sql.DB
}
// NewUserService 创建用户服务实例
func NewUserService(db *sql.DB) *UserService {
return &UserService{db: db}
}
// UpdateUserBalance 更新用户余额(乐观锁版本号法)
func (s *UserService) UpdateUserBalance(userID, amount int) error {
maxRetries := 3
for i := 0; i < maxRetries; i++ {
// 1. 读取当前用户信息和版本号
user, err := s.getUserByID(userID)
if err != nil {
return fmt.Errorf("获取用户信息失败: %v", err)
}
// 2. 计算新余额
newBalance := user.Balance + amount
if newBalance < 0 {
return fmt.Errorf("余额不足")
}
// 3. 尝试更新(检查版本号)
result, err := s.db.Exec(
"UPDATE users SET balance = ?, version = version + 1 WHERE id = ? AND version = ?",
newBalance, userID, user.Version,
)
if err != nil {
return fmt.Errorf("更新失败: %v", err)
}
// 4. 检查是否更新成功
affected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("获取影响行数失败: %v", err)
}
if affected > 0 {
// 更新成功
log.Printf("用户 %d 余额更新成功,新余额: %d", userID, newBalance)
return nil
}
// 版本号不匹配,更新失败,准备重试
log.Printf("用户 %d 版本号不匹配,准备重试 (第 %d 次)", userID, i+1)
time.Sleep(100 * time.Millisecond) // 短暂延迟后重试
}
return fmt.Errorf("更新失败,已达到最大重试次数")
}
// getUserByID 根据ID获取用户信息
func (s *UserService) getUserByID(userID int) (*User, error) {
user := &User{}
err := s.db.QueryRow(
"SELECT id, name, balance, version FROM users WHERE id = ?",
userID,
).Scan(&user.ID, &user.Name, &user.Balance, &user.Version)
if err != nil {
return nil, err
}
return user, nil
}
Java语言实现
java
import java.sql.*;
import java.util.concurrent.TimeUnit;
public class OptimisticLockExample {
// 用户实体类
public static class User {
private Long id;
private String name;
private Integer balance;
private Integer version;
// 构造函数、getter和setter方法
public User(Long id, String name, Integer balance, Integer version) {
this.id = id;
this.name = name;
this.balance = balance;
this.version = version;
}
// getter和setter方法
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getBalance() { return balance; }
public void setBalance(Integer balance) { this.balance = balance; }
public Integer getVersion() { return version; }
public void setVersion(Integer version) { this.version = version; }
}
// 用户服务类
public static class UserService {
private Connection connection;
public UserService(Connection connection) {
this.connection = connection;
}
/**
* 使用乐观锁更新用户余额(版本号法)
* @param userId 用户ID
* @param amount 金额变化
* @return 是否更新成功
*/
public boolean updateUserBalance(Long userId, Integer amount) {
final int MAX_RETRIES = 3;
for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
// 1. 读取当前用户信息和版本号
User user = getUserById(userId);
if (user == null) {
throw new RuntimeException("用户不存在: " + userId);
}
// 2. 计算新余额
int newBalance = user.getBalance() + amount;
if (newBalance < 0) {
throw new RuntimeException("余额不足");
}
// 3. 尝试更新(检查版本号)
String updateSql =
"UPDATE users SET balance = ?, version = version + 1 " +
"WHERE id = ? AND version = ?";
PreparedStatement pstmt = connection.prepareStatement(updateSql);
pstmt.setInt(1, newBalance);
pstmt.setLong(2, userId);
pstmt.setInt(3, user.getVersion());
int affectedRows = pstmt.executeUpdate();
pstmt.close();
// 4. 检查是否更新成功
if (affectedRows > 0) {
System.out.println("用户 " + userId + " 余额更新成功,新余额: " + newBalance);
return true;
}
// 版本号不匹配,更新失败,准备重试
System.out.println("用户 " + userId + " 版本号不匹配,准备重试 (第 " + (attempt + 1) + " 次)");
if (attempt < MAX_RETRIES - 1) {
TimeUnit.MILLISECONDS.sleep(100); // 短暂延迟后重试
}
} catch (Exception e) {
System.err.println("更新过程中发生错误: " + e.getMessage());
return false;
}
}
System.err.println("更新失败,已达到最大重试次数");
return false;
}
/**
* 根据ID获取用户信息
*/
private User getUserById(Long userId) throws SQLException {
String sql = "SELECT id, name, balance, version FROM users WHERE id = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setLong(1, userId);
ResultSet rs = pstmt.executeQuery();
User user = null;
if (rs.next()) {
user = new User(
rs.getLong("id"),
rs.getString("name"),
rs.getInt("balance"),
rs.getInt("version")
);
}
rs.close();
pstmt.close();
return user;
}
}
}
2. CAS法(Compare-And-Swap)
CAS是一种原子操作,它比较内存中的值与期望值,如果相等则用新值替换,否则不做任何操作。
CAS原理解析
CAS操作包含三个操作数:
- 内存位置(V):要更新的变量
- 期望值(A):进行比较的值
- 新值(B):要写入的新值
CAS操作的伪代码:
ini
CAS(V, A, B) {
if (V == A) {
V = B;
return true;
} else {
return false;
}
}
CAS操作是原子的,这意味着整个比较和交换过程不会被其他线程中断。
Go语言实现
go
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
// UserBalance 用户余额(使用CAS实现乐观锁)
type UserBalance struct {
balance int64
mu sync.RWMutex
}
// NewUserBalance 创建新的用户余额实例
func NewUserBalance(initialBalance int64) *UserBalance {
return &UserBalance{balance: initialBalance}
}
// GetBalance 获取余额
func (ub *UserBalance) GetBalance() int64 {
ub.mu.RLock()
defer ub.mu.RUnlock()
return ub.balance
}
// UpdateBalanceCAS 使用CAS更新余额
func (ub *UserBalance) UpdateBalanceCAS(amount int64) bool {
for {
currentBalance := ub.GetBalance()
newBalance := currentBalance + amount
if newBalance < 0 {
return false // 余额不足
}
// 使用CAS操作
if atomic.CompareAndSwapInt64(&ub.balance, currentBalance, newBalance) {
return true // 更新成功
}
// CAS失败,说明有其他线程修改了余额,继续重试
time.Sleep(1 * time.Millisecond)
}
}
// UpdateBalanceWithRetry 带重试的余额更新
func (ub *UserBalance) UpdateBalanceWithRetry(amount int64, maxRetries int) bool {
for i := 0; i < maxRetries; i++ {
if ub.UpdateBalanceCAS(amount) {
return true
}
time.Sleep(10 * time.Millisecond)
}
return false
}
// 并发测试
func testConcurrentUpdates() {
balance := NewUserBalance(1000)
var wg sync.WaitGroup
numGoroutines := 100
updatesPerGoroutine := 10
fmt.Printf("初始余额: %d\n", balance.GetBalance())
// 启动多个goroutine并发更新
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < updatesPerGoroutine; j++ {
success := balance.UpdateBalanceWithRetry(10, 5)
if !success {
fmt.Printf("Goroutine %d 第 %d 次更新失败\n", id, j+1)
}
}
}(i)
}
wg.Wait()
fmt.Printf("最终余额: %d\n", balance.GetBalance())
fmt.Printf("预期余额: %d\n", 1000+int64(numGoroutines*updatesPerGoroutine*10))
}
Java语言实现
java
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CASOptimisticLockExample {
// 使用AtomicLong实现CAS操作
public static class UserBalance {
private AtomicLong balance;
public UserBalance(long initialBalance) {
this.balance = new AtomicLong(initialBalance);
}
public long getBalance() {
return balance.get();
}
/**
* 使用CAS更新余额
* @param amount 金额变化
* @return 是否更新成功
*/
public boolean updateBalanceCAS(long amount) {
while (true) {
long currentBalance = balance.get();
long newBalance = currentBalance + amount;
if (newBalance < 0) {
return false; // 余额不足
}
// 使用CAS操作
if (balance.compareAndSet(currentBalance, newBalance)) {
return true; // 更新成功
}
// CAS失败,说明有其他线程修改了余额,继续重试
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
/**
* 带重试的余额更新
* @param amount 金额变化
* @param maxRetries 最大重试次数
* @return 是否更新成功
*/
public boolean updateBalanceWithRetry(long amount, int maxRetries) {
for (int i = 0; i < maxRetries; i++) {
if (updateBalanceCAS(amount)) {
return true;
}
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
}
// 并发测试
public static void testConcurrentUpdates() throws InterruptedException {
UserBalance balance = new UserBalance(1000);
int numThreads = 100;
int updatesPerThread = 10;
CountDownLatch latch = new CountDownLatch(numThreads);
System.out.println("初始余额: " + balance.getBalance());
// 启动多个线程并发更新
for (int i = 0; i < numThreads; i++) {
final int threadId = i;
new Thread(() -> {
try {
for (int j = 0; j < updatesPerThread; j++) {
boolean success = balance.updateBalanceWithRetry(10, 5);
if (!success) {
System.out.println("Thread " + threadId + " 第 " + (j+1) + " 次更新失败");
}
}
} finally {
latch.countDown();
}
}).start();
}
// 等待所有线程完成
latch.await();
System.out.println("最终余额: " + balance.getBalance());
System.out.println("预期余额: " + (1000 + (long) numThreads * updatesPerThread * 10));
}
}
3. 时间戳法
时间戳法使用时间戳来标识数据的修改时间,通过比较时间戳来判断数据是否被其他事务修改。
Go语言实现
go
package main
import (
"database/sql"
"fmt"
"log"
"time"
)
// UserWithTimestamp 带时间戳的用户结构体
type UserWithTimestamp struct {
ID int `json:"id"`
Name string `json:"name"`
Balance int `json:"balance"`
UpdatedAt time.Time `json:"updated_at"`
}
// UserTimestampService 用户时间戳服务
type UserTimestampService struct {
db *sql.DB
}
// NewUserTimestampService 创建用户时间戳服务实例
func NewUserTimestampService(db *sql.DB) *UserTimestampService {
return &UserTimestampService{db: db}
}
// UpdateUserBalanceTimestamp 使用时间戳更新用户余额
func (s *UserTimestampService) UpdateUserBalanceTimestamp(userID, amount int) error {
maxRetries := 3
for i := 0; i < maxRetries; i++ {
// 1. 读取当前用户信息和时间戳
user, err := s.getUserByID(userID)
if err != nil {
return fmt.Errorf("获取用户信息失败: %v", err)
}
// 2. 计算新余额
newBalance := user.Balance + amount
if newBalance < 0 {
return fmt.Errorf("余额不足")
}
// 3. 尝试更新(检查时间戳)
result, err := s.db.Exec(
"UPDATE users SET balance = ?, updated_at = NOW() WHERE id = ? AND updated_at = ?",
newBalance, userID, user.UpdatedAt,
)
if err != nil {
return fmt.Errorf("更新失败: %v", err)
}
// 4. 检查是否更新成功
affected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("获取影响行数失败: %v", err)
}
if affected > 0 {
log.Printf("用户 %d 余额更新成功,新余额: %d", userID, newBalance)
return nil
}
log.Printf("用户 %d 时间戳不匹配,准备重试 (第 %d 次)", userID, i+1)
time.Sleep(100 * time.Millisecond)
}
return fmt.Errorf("更新失败,已达到最大重试次数")
}
// getUserByID 根据ID获取用户信息
func (s *UserTimestampService) getUserByID(userID int) (*UserWithTimestamp, error) {
user := &UserWithTimestamp{}
err := s.db.QueryRow(
"SELECT id, name, balance, updated_at FROM users WHERE id = ?",
userID,
).Scan(&user.ID, &user.Name, &user.Balance, &user.UpdatedAt)
if err != nil {
return nil, err
}
return user, nil
}
Java语言实现
java
import java.sql.*;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
public class TimestampOptimisticLockExample {
// 带时间戳的用户实体类
public static class UserWithTimestamp {
private Long id;
private String name;
private Integer balance;
private LocalDateTime updatedAt;
public UserWithTimestamp(Long id, String name, Integer balance, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.balance = balance;
this.updatedAt = updatedAt;
}
// getter和setter方法
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getBalance() { return balance; }
public void setBalance(Integer balance) { this.balance = balance; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}
// 用户时间戳服务类
public static class UserTimestampService {
private Connection connection;
public UserTimestampService(Connection connection) {
this.connection = connection;
}
/**
* 使用时间戳更新用户余额
* @param userId 用户ID
* @param amount 金额变化
* @return 是否更新成功
*/
public boolean updateUserBalanceTimestamp(Long userId, Integer amount) {
final int MAX_RETRIES = 3;
for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
// 1. 读取当前用户信息和时间戳
UserWithTimestamp user = getUserById(userId);
if (user == null) {
throw new RuntimeException("用户不存在: " + userId);
}
// 2. 计算新余额
int newBalance = user.getBalance() + amount;
if (newBalance < 0) {
throw new RuntimeException("余额不足");
}
// 3. 尝试更新(检查时间戳)
String updateSql =
"UPDATE users SET balance = ?, updated_at = NOW() " +
"WHERE id = ? AND updated_at = ?";
PreparedStatement pstmt = connection.prepareStatement(updateSql);
pstmt.setInt(1, newBalance);
pstmt.setLong(2, userId);
pstmt.setTimestamp(3, Timestamp.valueOf(user.getUpdatedAt()));
int affectedRows = pstmt.executeUpdate();
pstmt.close();
// 4. 检查是否更新成功
if (affectedRows > 0) {
System.out.println("用户 " + userId + " 余额更新成功,新余额: " + newBalance);
return true;
}
System.out.println("用户 " + userId + " 时间戳不匹配,准备重试 (第 " + (attempt + 1) + " 次)");
if (attempt < MAX_RETRIES - 1) {
TimeUnit.MILLISECONDS.sleep(100);
}
} catch (Exception e) {
System.err.println("更新过程中发生错误: " + e.getMessage());
return false;
}
}
System.err.println("更新失败,已达到最大重试次数");
return false;
}
/**
* 根据ID获取用户信息
*/
private UserWithTimestamp getUserById(Long userId) throws SQLException {
String sql = "SELECT id, name, balance, updated_at FROM users WHERE id = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setLong(1, userId);
ResultSet rs = pstmt.executeQuery();
UserWithTimestamp user = null;
if (rs.next()) {
user = new UserWithTimestamp(
rs.getLong("id"),
rs.getString("name"),
rs.getInt("balance"),
rs.getTimestamp("updated_at").toLocalDateTime()
);
}
rs.close();
pstmt.close();
return user;
}
}
}
乐观锁的潜在问题与解决方案
ABA问题详解
ABA问题是CAS操作中最经典的并发问题。想象这样一个场景:线程A读取到值A,准备将其更新为B,但在更新之前,线程B将值从A改为C,然后又改回A。当线程A执行CAS操作时,发现当前值仍然是A,误以为没有被修改过,但实际上值已经被修改过了。
ABA问题的危害
ABA问题在某些场景下可能导致严重的数据不一致。比如在链表操作中,如果节点被删除后重新分配,可能导致链表结构被破坏。
解决方案
1. 版本号标记法
go
// Go语言实现带版本号的CAS
type VersionedValue struct {
value int64
version int64
}
func (vv *VersionedValue) CompareAndSwap(expectedValue, newValue int64, expectedVersion int64) bool {
// 使用原子操作同时比较值和版本号
return atomic.CompareAndSwapInt64(&vv.value, expectedValue, newValue) &&
atomic.CompareAndSwapInt64(&vv.version, expectedVersion, expectedVersion+1)
}
2. 引用标记法
java
// Java实现带标记的引用
public class MarkedReference<T> {
private static class MarkedValue<T> {
final T value;
final int mark;
MarkedValue(T value, int mark) {
this.value = value;
this.mark = mark;
}
}
private final AtomicReference<MarkedValue<T>> ref;
public boolean compareAndSet(T expectedValue, T newValue, int expectedMark) {
MarkedValue<T> current = ref.get();
if (current.value == expectedValue && current.mark == expectedMark) {
return ref.compareAndSet(current, new MarkedValue<>(newValue, expectedMark + 1));
}
return false;
}
}
性能抖动问题
在高并发场景下,乐观锁可能出现性能抖动现象。当冲突率突然升高时,大量重试会导致系统性能急剧下降。
性能抖动的原因
- 冲突雪崩:某个热点数据被大量并发访问时,冲突率急剧上升
- 重试风暴:失败的操作集中重试,形成重试风暴
- 资源竞争:数据库连接池耗尽,导致更多失败
解决方案
1. 自适应重试策略
go
// 自适应重试策略实现
type AdaptiveRetryStrategy struct {
baseDelay time.Duration
maxDelay time.Duration
backoffFactor float64
maxRetries int
}
func (ars *AdaptiveRetryStrategy) Retry(operation func() error) error {
delay := ars.baseDelay
for i := 0; i < ars.maxRetries; i++ {
if err := operation(); err == nil {
return nil
}
// 指数退避 + 随机抖动
jitter := time.Duration(rand.Float64() * float64(delay) * 0.1)
sleepTime := delay + jitter
if sleepTime > ars.maxDelay {
sleepTime = ars.maxDelay
}
time.Sleep(sleepTime)
delay = time.Duration(float64(delay) * ars.backoffFactor)
}
return fmt.Errorf("达到最大重试次数")
}
2. 热点数据分离
java
// 热点数据分离策略
public class HotspotSeparationStrategy {
private final Map<String, AtomicInteger> conflictCounters = new ConcurrentHashMap<>();
private final int conflictThreshold = 10;
public <T> T executeWithSeparation(String key, Supplier<T> operation) {
AtomicInteger counter = conflictCounters.computeIfAbsent(key, k -> new AtomicInteger(0));
try {
T result = operation.get();
counter.set(0); // 成功时重置计数器
return result;
} catch (OptimisticLockException e) {
int conflicts = counter.incrementAndGet();
if (conflicts > conflictThreshold) {
// 切换到悲观锁或队列化处理
return executeWithPessimisticLock(key, operation);
}
throw e; // 继续使用乐观锁重试
}
}
}
业务逻辑复杂化问题
乐观锁的失败重试机制会让业务逻辑变得复杂,特别是在涉及多个数据操作的场景下。
复杂化表现
- 状态回滚困难:部分操作成功后,失败重试时需要回滚已成功的操作
- 事务边界模糊:重试逻辑可能跨越多个事务边界
- 异常处理复杂:需要区分不同类型的异常并采取不同的处理策略
解决方案
1. 幂等性设计
go
// 幂等性操作设计
type IdempotentOperation struct {
operationID string
status string
result interface{}
}
func (op *IdempotentOperation) Execute(operation func() error) error {
// 检查操作是否已经执行过
if op.status == "completed" {
return nil
}
// 标记操作开始
op.status = "processing"
if err := operation(); err != nil {
op.status = "failed"
return err
}
op.status = "completed"
return nil
}
2. 补偿机制
java
// 补偿机制实现
public class CompensationMechanism {
private final List<CompensationAction> actions = new ArrayList<>();
public void executeWithCompensation(Runnable operation, CompensationAction compensation) {
actions.add(compensation);
try {
operation.run();
} catch (Exception e) {
// 执行补偿操作
for (int i = actions.size() - 1; i >= 0; i--) {
try {
actions.get(i).compensate();
} catch (Exception ce) {
// 记录补偿失败,但不影响主流程
log.error("补偿操作失败", ce);
}
}
throw e;
}
}
}
调试困难问题
乐观锁的并发问题往往难以复现和调试,给问题排查带来很大挑战。
调试难点
- 问题不可重现:并发问题具有随机性,难以稳定复现
- 日志信息不足:缺乏足够的调试信息来定位问题
- 性能影响:添加调试信息可能影响性能
解决方案
1. 增强日志记录
go
// 增强的日志记录
type EnhancedLogger struct {
logger *log.Logger
traceID string
}
func (el *EnhancedLogger) LogOptimisticLockAttempt(operation string, key string, version int64) {
el.logger.Printf("[%s] 乐观锁尝试 - 操作: %s, 键: %s, 版本: %d, 时间: %s",
el.traceID, operation, key, version, time.Now().Format(time.RFC3339Nano))
}
func (el *EnhancedLogger) LogOptimisticLockConflict(operation string, key string, expectedVersion, actualVersion int64) {
el.logger.Printf("[%s] 乐观锁冲突 - 操作: %s, 键: %s, 期望版本: %d, 实际版本: %d, 时间: %s",
el.traceID, operation, key, expectedVersion, actualVersion, time.Now().Format(time.RFC3339Nano))
}
2. 分布式追踪
java
// 分布式追踪实现
public class DistributedTracing {
private static final ThreadLocal<String> traceId = new ThreadLocal<>();
public static void startTrace(String operation) {
String id = UUID.randomUUID().toString();
traceId.set(id);
// 记录操作开始
log.info("开始追踪 - 操作: {}, 追踪ID: {}", operation, id);
}
public static void recordOptimisticLockAttempt(String key, long version) {
String id = traceId.get();
log.info("乐观锁尝试 - 追踪ID: {}, 键: {}, 版本: {}", id, key, version);
}
public static void recordOptimisticLockConflict(String key, long expectedVersion, long actualVersion) {
String id = traceId.get();
log.warn("乐观锁冲突 - 追踪ID: {}, 键: {}, 期望版本: {}, 实际版本: {}",
id, key, expectedVersion, actualVersion);
}
}
资源浪费问题
乐观锁的重试机制会消耗额外的系统资源,包括CPU、内存、数据库连接等。
资源浪费表现
- CPU浪费:大量重试操作消耗CPU资源
- 数据库连接浪费:重试时占用数据库连接
- 内存浪费:重试过程中的临时对象占用内存
解决方案
1. 资源池化管理
go
// 资源池化管理
type ResourcePool struct {
dbPool *sql.DB
maxRetries int
retryDelay time.Duration
}
func (rp *ResourcePool) ExecuteWithResourceLimit(operation func() error) error {
for i := 0; i < rp.maxRetries; i++ {
// 检查资源使用情况
if rp.isResourceExhausted() {
return fmt.Errorf("资源耗尽,无法继续重试")
}
if err := operation(); err == nil {
return nil
}
// 指数退避
delay := time.Duration(float64(rp.retryDelay) * math.Pow(2, float64(i)))
time.Sleep(delay)
}
return fmt.Errorf("达到最大重试次数")
}
2. 智能重试策略
java
// 智能重试策略
public class SmartRetryStrategy {
private final CircuitBreaker circuitBreaker;
private final RateLimiter rateLimiter;
public <T> T executeWithSmartRetry(Supplier<T> operation) {
// 检查熔断器状态
if (circuitBreaker.isOpen()) {
throw new CircuitBreakerOpenException("熔断器已开启");
}
// 检查限流器
if (!rateLimiter.tryAcquire()) {
throw new RateLimitExceededException("超过限流阈值");
}
try {
T result = operation.get();
circuitBreaker.recordSuccess();
return result;
} catch (OptimisticLockException e) {
circuitBreaker.recordFailure();
throw e;
}
}
}
总结
乐观锁是一种高效的并发控制机制,通过版本号、CAS操作或时间戳等方式实现。它适合读多写少、冲突较少的场景,能够提供良好的并发性能。
然而,乐观锁并非万能的解决方案,它也存在ABA问题、性能抖动、业务逻辑复杂化、调试困难和资源浪费等诸多挑战。在实际应用中,我们需要深入理解这些问题的本质,并采用相应的解决方案来应对。
通过合理的实现方式选择、重试策略优化、异常处理完善、性能监控建立以及混合锁策略的应用,乐观锁能够为系统提供稳定可靠的并发控制能力。关键是要根据具体的业务场景和性能要求,选择最适合的并发控制方案,并在实践中不断优化和调整。
如果觉得有用就收藏点赞,骚话王会继续分享更多技术干货!