核心概念总结 在深入细节之前,让我们先理解两种锁的核心思想:
悲观锁(Pessimistic Locking) :假设并发冲突一定会发生,在操作数据前先加锁,其他线程/事务必须等待。适合高并发冲突场景,通过加锁保证安全。
乐观锁(Optimistic Locking) :假设并发冲突很少发生,不加锁直接操作,提交时检测是否被别人改过。适合读多写少场景,通过版本检验或CAS保证一致性。
这两种策略的选择,本质上是在性能 和安全性 之间的权衡。
悲观锁(Pessimistic Locking) 核心思想 悲观锁采用”先下手为强”的策略:假设并发冲突一定会发生,因此在操作数据前先加锁,其他线程/事务必须等待锁释放后才能操作 。
这种策略类似于现实生活中的”先到先得”:当你需要操作某个资源时,先把它锁起来,确保在你操作完成之前,其他人无法修改它。
工作原理 1 2 线程A: 获取锁 → 操作数据 → 提交事务 → 释放锁 线程B: 等待... → 等待... → 获取锁 → 操作数据 → 提交事务 → 释放锁
常见实现方式 1. 数据库层面的悲观锁 MySQL/PostgreSQL :
1 2 3 4 5 6 7 8 SELECT * FROM account WHERE id = 1 FOR UPDATE ;BEGIN ;SELECT * FROM account WHERE id = 1 FOR UPDATE ;UPDATE account SET balance = balance - 100 WHERE id = 1 ;COMMIT ;
SQL Server :
1 2 SELECT * FROM account WITH (UPDLOCK) WHERE id = 1 ;
2. Java语言层面的悲观锁 synchronized关键字 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Account { private int balance = 1000 ; public synchronized void withdraw (int amount) { if (balance >= amount) { balance -= amount; } } public void transfer (Account target, int amount) { synchronized (this ) { synchronized (target) { if (this .balance >= amount) { this .balance -= amount; target.balance += amount; } } } } }
ReentrantLock(可重入锁) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.util.concurrent.locks.ReentrantLock;public class Account { private int balance = 1000 ; private final ReentrantLock lock = new ReentrantLock (); public void withdraw (int amount) { lock.lock(); try { if (balance >= amount) { balance -= amount; } } finally { lock.unlock(); } } }
3. 其他语言的实现 Python :
1 2 3 4 5 6 7 8 9 10 11 import threadingclass Account : def __init__ (self ): self .balance = 1000 self .lock = threading.Lock() def withdraw (self, amount ): with self .lock: if self .balance >= amount: self .balance -= amount
详细示例:银行转账 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 BEGIN ;SELECT * FROM account WHERE id = 1 FOR UPDATE ; SELECT * FROM account WHERE id = 2 FOR UPDATE ; UPDATE account SET balance = balance - 100 WHERE id = 1 ;UPDATE account SET balance = balance + 100 WHERE id = 2 ;COMMIT ;BEGIN ;SELECT * FROM account WHERE id = 1 FOR UPDATE ; COMMIT ;
优缺点分析 ✅ 优点
简单直接 :实现逻辑清晰,容易理解
数据一致性强 :通过锁机制保证数据不会被并发修改
适合高冲突场景 :当并发冲突频繁时,悲观锁能有效避免数据不一致
避免重试开销 :不需要像乐观锁那样可能反复重试
❌ 缺点
性能较差 :线程阻塞导致吞吐量下降
容易产生死锁 :多个锁的获取顺序不当可能导致死锁
扩展性不好 :在高并发场景下,大量线程等待会严重影响性能
资源占用 :锁会占用系统资源,降低系统整体性能
死锁问题 悲观锁容易产生死锁,需要特别注意:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public void transfer (Account from, Account to, int amount) { Account first = from.id < to.id ? from : to; Account second = from.id < to.id ? to : from; synchronized (first) { synchronized (second) { } } } ReentrantLock lock = new ReentrantLock ();if (lock.tryLock(5 , TimeUnit.SECONDS)) { try { } finally { lock.unlock(); } }
适用场景 1. 银行转账系统 1 2 3 4 5 BEGIN ;SELECT * FROM account WHERE id = ? FOR UPDATE ;COMMIT ;
2. 库存强一致性要求 1 2 3 4 5 6 BEGIN ;SELECT stock FROM product WHERE id = ? FOR UPDATE ;UPDATE product SET stock = stock - 1 WHERE id = ? AND stock > 0 ;COMMIT ;
3. 并发冲突非常频繁的场景
抢票系统
秒杀系统(如果冲突率极高)
账户余额操作
4. 数据完整性要求极高的场景
乐观锁(Optimistic Locking) 核心思想 乐观锁采用”先操作,后检查”的策略:假设并发冲突很少发生,因此不加锁直接操作数据,在提交时检测数据是否被别人修改过 。
这种策略类似于”先到先得”的另一种理解:相信大多数情况下不会有人同时修改,先进行操作,如果发现冲突再处理。
工作原理 1 2 线程A: 读取数据(version=1) → 修改数据 → 提交时检查version → version仍为1 → 提交成功 线程B: 读取数据(version=1) → 修改数据 → 提交时检查version → version已变为2 → 提交失败 → 重试
常见实现方式 1. 版本号机制(Version Number) 这是最常见的乐观锁实现方式,通过在数据表中增加一个版本号字段来实现。
表结构设计 :
1 2 3 4 5 CREATE TABLE account ( id INT PRIMARY KEY , balance DECIMAL (10 , 2 ), version INT DEFAULT 0 );
更新操作 :
1 2 3 4 5 6 7 8 9 10 11 SELECT id, balance, version FROM account WHERE id = 1 ;UPDATE accountSET balance = balance - 100 , version = version + 1 WHERE id = 1 AND version = 3 ;
完整示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 BEGIN ;SELECT balance, version FROM account WHERE id = 1 ;BEGIN ;SELECT balance, version FROM account WHERE id = 1 ;UPDATE account SET balance = 900 , version = 2 WHERE id = 1 AND version = 1 ; COMMIT ;UPDATE account SET balance = 900 , version = 2 WHERE id = 1 AND version = 1 ; ROLLBACK ;
2. CAS机制(Compare And Swap) CAS是CPU级别的原子操作,Java的Atomic类就是基于CAS实现的。
Java中的CAS :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.util.concurrent.atomic.AtomicInteger;public class Counter { private AtomicInteger count = new AtomicInteger (0 ); public void increment () { int current; int next; do { current = count.get(); next = current + 1 ; } while (!count.compareAndSet(current, next)); } public int get () { return count.get(); } }
CAS的工作原理 :
1 2 3 4 5 6 7 8 public boolean compareAndSet (int expected, int update) { if (this .value == expected) { this .value = update; return true ; } return false ; }
更多Atomic类的使用 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 AtomicInteger atomicInt = new AtomicInteger (0 );atomicInt.compareAndSet(0 , 1 ); AtomicReference<String> atomicRef = new AtomicReference <>("initial" ); atomicRef.compareAndSet("initial" , "updated" ); AtomicStampedReference<String> atomicStamped = new AtomicStampedReference <>("value" , 1 ); int [] stampHolder = new int [1 ];String current = atomicStamped.get(stampHolder);atomicStamped.compareAndSet(current, "new" , stampHolder[0 ], stampHolder[0 ] + 1 );
3. 时间戳机制 使用时间戳代替版本号(较少使用,因为时间戳可能不准确):
1 2 3 4 5 6 7 8 9 CREATE TABLE account ( id INT PRIMARY KEY , balance DECIMAL (10 , 2 ), update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); UPDATE accountSET balance = balance - 100 WHERE id = 1 AND update_time = '2026-01-12 10:00:00' ;
完整示例:版本号实现 Java + MyBatis实现 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class Account { private Long id; private BigDecimal balance; private Integer version; } public interface AccountMapper { Account selectById (Long id) ; int updateWithVersion (Account account) ; } <!-- 更新时检查版本号 --> <update id="updateWithVersion" > UPDATE account SET balance = #{balance}, version = version + 1 WHERE id = #{id} AND version = #{version} </update> @Service public class AccountService { @Autowired private AccountMapper accountMapper; public boolean transfer (Long fromId, Long toId, BigDecimal amount) { int maxRetries = 3 ; int retries = 0 ; while (retries < maxRetries) { try { Account from = accountMapper.selectById(fromId); Account to = accountMapper.selectById(toId); if (from.getBalance().compareTo(amount) < 0 ) { return false ; } from.setBalance(from.getBalance().subtract(amount)); to.setBalance(to.getBalance().add(amount)); int rows1 = accountMapper.updateWithVersion(from); int rows2 = accountMapper.updateWithVersion(to); if (rows1 > 0 && rows2 > 0 ) { return true ; } retries++; Thread.sleep(10 ); } catch (Exception e) { retries++; if (retries >= maxRetries) { throw new RuntimeException ("转账失败,重试次数超限" , e); } } } return false ; } }
ABA问题及解决方案 ABA问题 :值从A变成B,再变回A,CAS会认为值没有变化。
解决方案1:版本号/时间戳
1 2 3 4 5 6 7 AtomicStampedReference<Integer> atomic = new AtomicStampedReference <>(100 , 1 ); int [] stamp = new int [1 ];int value = atomic.get(stamp); atomic.compareAndSet(100 , 200 , 1 , 2 );
解决方案2:使用版本号字段
1 2 3 4 UPDATE account SET balance = 200 , version = version + 1 WHERE id = 1 AND version = 1 ;
优缺点分析 ✅ 优点
不阻塞线程 :读取和修改操作不需要加锁,提高了并发性能
性能好 :在低冲突场景下,性能远优于悲观锁
并发能力强 :多个线程可以同时读取,提高了系统吞吐量
避免死锁 :不需要获取锁,自然避免了死锁问题
适合分布式系统 :在分布式环境下更容易实现
❌ 缺点
可能反复重试 :如果冲突频繁,可能需要多次重试才能成功
实现复杂 :需要处理重试逻辑、ABA问题等
冲突高时反而更慢 :在高冲突场景下,反复重试的开销可能超过悲观锁
数据可能被丢弃 :如果重试次数达到上限仍失败,操作可能无法完成
适用场景 1. 读多写少的场景 1 2 3 4 5 6 7 8 9 10 11 12 13 public void incrementViewCount (Long articleId) { int maxRetries = 5 ; for (int i = 0 ; i < maxRetries; i++) { Article article = articleMapper.selectById(articleId); article.setViewCount(article.getViewCount() + 1 ); if (articleMapper.updateWithVersion(article) > 0 ) { return ; } } }
2. 秒杀库存(配合重试机制) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public boolean seckill (Long productId, Integer quantity) { int maxRetries = 10 ; for (int i = 0 ; i < maxRetries; i++) { Product product = productMapper.selectById(productId); if (product.getStock() < quantity) { return false ; } product.setStock(product.getStock() - quantity); if (productMapper.updateWithVersion(product) > 0 ) { return true ; } try { Thread.sleep(10 + i * 5 ); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false ; } } return false ; }
3. 分布式系统 1 2 3 4 5 6 7 8 9 @Service public class DistributedAccountService { public boolean updateBalance (String accountId, BigDecimal amount) { } }
4. 缓存更新 1 2 3 4 5 6 7 8 9 10 public void updateCache (String key, Object value) { CacheEntry entry = cache.get(key); if (entry != null ) { while (!cache.compareAndSet(key, entry, new CacheEntry (value, entry.version + 1 ))) { entry = cache.get(key); } } }
乐观锁 vs 悲观锁:全面对比 对比表格
对比项
乐观锁
悲观锁
核心思想
假设冲突少,先操作后检查
假设冲突多,先加锁后操作
是否加锁
不加锁
加锁
冲突处理
提交时校验,失败则重试
操作前阻塞,等待锁释放
性能
高(低冲突场景)
低(线程阻塞)
并发能力
强(多读并发)
弱(串行化执行)
实现复杂度
高(需处理重试、ABA问题)
低(直接加锁)
数据一致性
最终一致(可能重试)
强一致(立即保证)
死锁风险
无
有
适用场景
读多写少、冲突率低
写多冲突高、强一致性要求
资源消耗
低(无锁等待)
高(线程阻塞)
扩展性
好(适合分布式)
差(需要分布式锁)
性能对比分析 低冲突场景(冲突率 < 10%) 1 2 3 4 乐观锁:读取 → 修改 → 检查 → 提交(通常一次成功) 悲观锁:获取锁 → 等待 → 修改 → 提交 → 释放锁 结果:乐观锁性能明显优于悲观锁
高冲突场景(冲突率 > 50%) 1 2 3 4 乐观锁:读取 → 修改 → 检查失败 → 重试 → 检查失败 → 重试...(多次重试) 悲观锁:获取锁 → 等待 → 修改 → 提交 → 释放锁 结果:悲观锁性能可能优于乐观锁(避免重试开销)
选择决策树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 需要并发控制? │ ├─ 冲突率如何? │ │ │ ├─ 冲突率低(< 20%)→ 乐观锁 │ │ │ │ │ └─ 读多写少? → 乐观锁(推荐) │ │ │ └─ 冲突率高(> 50%)→ 悲观锁 │ │ │ └─ 强一致性要求? → 悲观锁(推荐) │ ├─ 性能要求? │ │ │ ├─ 高吞吐量 → 乐观锁 │ │ │ └─ 低延迟、强一致 → 悲观锁 │ └─ 系统架构? │ ├─ 分布式系统 → 乐观锁(更容易实现) │ └─ 单机系统 → 根据冲突率选择
混合使用策略 在实际项目中,可以根据不同场景混合使用两种锁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Service public class OrderService { public void updateOrderStatus (Long orderId, String status) { int maxRetries = 3 ; for (int i = 0 ; i < maxRetries; i++) { Order order = orderMapper.selectById(orderId); order.setStatus(status); if (orderMapper.updateWithVersion(order) > 0 ) { return ; } } throw new OptimisticLockException ("更新失败,请重试" ); } @Transactional public void deductInventory (Long productId, Integer quantity) { Product product = productMapper.selectByIdForUpdate(productId); if (product.getStock() >= quantity) { product.setStock(product.getStock() - quantity); productMapper.update(product); } else { throw new InsufficientStockException ("库存不足" ); } } }
实际应用案例 案例1:电商库存管理 场景分析 :
商品浏览(读操作)远多于购买(写操作)
热门商品购买时冲突率较高
需要保证库存准确性
解决方案 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Service public class InventoryService { public boolean purchaseNormalProduct (Long productId, Integer quantity) { int maxRetries = 5 ; for (int i = 0 ; i < maxRetries; i++) { Product product = productMapper.selectById(productId); if (product.getStock() < quantity) { return false ; } product.setStock(product.getStock() - quantity); if (productMapper.updateWithVersion(product) > 0 ) { return true ; } } return false ; } @Transactional public boolean purchaseSeckillProduct (Long productId, Integer quantity) { Product product = productMapper.selectByIdForUpdate(productId); if (product.getStock() >= quantity) { product.setStock(product.getStock() - quantity); productMapper.update(product); return true ; } return false ; } }
案例2:账户余额管理 场景分析 :
金融系统,强一致性要求
转账操作冲突率中等
不允许数据不一致
解决方案 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Service public class AccountService { @Transactional public boolean transfer (Long fromId, Long toId, BigDecimal amount) { Account from = fromId < toId ? accountMapper.selectByIdForUpdate(fromId) : accountMapper.selectByIdForUpdate(fromId); Account to = fromId < toId ? accountMapper.selectByIdForUpdate(toId) : accountMapper.selectByIdForUpdate(toId); if (from.getBalance().compareTo(amount) >= 0 ) { from.setBalance(from.getBalance().subtract(amount)); to.setBalance(to.getBalance().add(amount)); accountMapper.update(from); accountMapper.update(to); return true ; } return false ; } }
案例3:文章点赞系统 场景分析 :
读操作(查看文章)远多于写操作(点赞)
点赞冲突率低
可以接受偶尔的点赞失败
解决方案 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Service public class ArticleService { public boolean likeArticle (Long articleId, Long userId) { int maxRetries = 3 ; for (int i = 0 ; i < maxRetries; i++) { Article article = articleMapper.selectById(articleId); if (likeMapper.exists(articleId, userId)) { return false ; } article.setLikeCount(article.getLikeCount() + 1 ); if (articleMapper.updateWithVersion(article) > 0 ) { likeMapper.insert(articleId, userId); return true ; } } return false ; } }
最佳实践 1. 选择合适的锁策略
读多写少 → 乐观锁
写多冲突高 → 悲观锁
强一致性要求 → 悲观锁
高并发、低冲突 → 乐观锁
2. 乐观锁实现建议
设置合理的重试次数 :避免无限重试
使用递增等待时间 :减少冲突
处理ABA问题 :使用版本号或时间戳
记录失败日志 :监控冲突率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public boolean updateWithRetry (Long id, Function<Entity, Entity> updater) { int maxRetries = 5 ; for (int i = 0 ; i < maxRetries; i++) { try { Entity entity = mapper.selectById(id); Entity updated = updater.apply(entity); if (mapper.updateWithVersion(updated) > 0 ) { return true ; } Thread.sleep(10 * (i + 1 )); } catch (Exception e) { logger.warn("更新失败,重试中..." , e); } } return false ; }
3. 悲观锁实现建议
统一锁的获取顺序 :避免死锁
使用超时锁 :避免长时间等待
尽快释放锁 :减少锁持有时间
避免嵌套锁 :降低死锁风险
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public void transfer (Account a, Account b, BigDecimal amount) { Account first = a.getId() < b.getId() ? a : b; Account second = a.getId() < b.getId() ? b : a; synchronized (first) { synchronized (second) { } } } ReentrantLock lock = new ReentrantLock ();try { if (lock.tryLock(5 , TimeUnit.SECONDS)) { try { } finally { lock.unlock(); } } else { throw new TimeoutException ("获取锁超时" ); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
4. 监控和优化
监控锁冲突率 :决定是否需要调整策略
监控重试次数 :优化乐观锁的重试逻辑
监控死锁 :及时发现和解决死锁问题
性能测试 :在不同场景下测试两种锁的性能