Java中的乐观锁和悲观锁
在多线程编程中,锁机制是确保数据一致性和线程安全的关键技术。悲观锁和乐观锁是两种常见的锁机制,它们在不同的场景下有着各自的优势和适用范围。
悲观锁和乐观锁的概念
悲观锁(Pessimistic Locking)假设在并发环境中会发生冲突,因此在访问共享资源时总是先加锁,确保在事务期间没有其他线程可以修改该资源。悲观锁在事务开始时就获取锁,直到事务结束时才释放锁。
乐观锁
乐观锁(Optimistic Locking)假设在并发环境中很少发生冲突,因此在访问共享资源时不立即加锁,而是等到真正需要修改资源时再检查是否有冲突。如果发现冲突,则采取补偿措施(如重试或回滚)。
概念
乐观锁假设并发冲突很少发生,因此在操作数据时不会先加锁,而是通过某种机制(如版本号)来检测是否存在冲突。只有在提交时发现冲突才会进行重试。
实现原理
-
版本号机制: 每条记录附加一个版本号字段,每次更新时检查和更新版本号。
-
读取数据时,获取当前版本号。
-
更新数据时,检查数据库中当前版本号是否与读取时一致。
-
如果一致,则更新数据并将版本号加 1;如果不一致,则说明有冲突,操作失败或重试。
-
-
CAS (Compare and Swap): Java 的
java.util.concurrent
包中大量使用了 CAS 操作,例如AtomicInteger
、AtomicLong
。-
CAS 通过比较内存中的值和预期值,如果一致则更新,否则重试。
-
优缺点
-
优点:
-
不需要实际加锁,因此开销小,性能较高。
-
适用于读多写少的场景。
-
-
缺点:
-
在高并发下,频繁重试可能影响性能。
-
不适合写多读少的场景。
-
代码示例
版本号机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 模拟版本号机制 class Data { private int value; private int version; public synchronized boolean updateValue( int newValue, int expectedVersion) { if ( this .version == expectedVersion) { this .value = newValue; this .version++; return true ; } return false ; } } |
CAS 操作(CAS 通过比较内存中的值和预期值,如果一致则更新,否则重试。)
1 2 3 4 5 6 7 8 9 10 | import java.util.concurrent.atomic.AtomicInteger; public class OptimisticLockExample { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger( 0 ); boolean success = atomicInteger.compareAndSet( 0 , 1 ); System.out.println( "Operation success: " + success + ", New value: " + atomicInteger.get()); } } |
2. 悲观锁 (Pessimistic Locking)
概念
悲观锁假设并发冲突会频繁发生,因此在操作数据时,会先加锁来阻止其他线程对数据的访问。只有当前操作完成后,其他线程才能访问。
实现原理
-
排他锁:
-
在操作数据前,先获取锁(独占访问)。
-
其他线程尝试访问时,会被阻塞直到锁被释放。
-
-
数据库层面: 使用
SELECT ... FOR UPDATE
的方式锁定数据行。 -
Java 中的锁机制: 悲观锁通常使用
synchronized
或java.util.concurrent.locks.Lock
来实现。
优缺点
-
优点:
-
能有效避免数据冲突。
-
对于写多读少的场景更适合。
-
-
缺点:
-
开销较大(需要维护锁)。
-
在高并发场景下可能导致线程阻塞,影响性能。
-
代码示例
使用 synchronized
1 2 3 4 5 6 7 8 9 10 11 | class Counter { private int count = 0 ; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } |
使用 Lock
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 | import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Counter { private int count = 0 ; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } } |
对比总结
特性 | 乐观锁 | 悲观锁 |
---|---|---|
并发控制方式 | 无锁(基于版本号或 CAS) | 加锁(阻塞其他线程) |
性能 | 高(冲突少时) | 低(锁管理开销大) |
适用场景 | 读多写少 | 写多读少 |
冲突处理 | 依赖重试机制 | 阻止冲突发生 |
实现方式 | 版本号、CAS 操作 | synchronized、Lock、数据库排他锁 |
根据具体的业务场景选择合适的锁机制。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)