Java中的乐观锁和悲观锁
在多线程编程中,锁机制是确保数据一致性和线程安全的关键技术。悲观锁和乐观锁是两种常见的锁机制,它们在不同的场景下有着各自的优势和适用范围。
悲观锁和乐观锁的概念
悲观锁(Pessimistic Locking)假设在并发环境中会发生冲突,因此在访问共享资源时总是先加锁,确保在事务期间没有其他线程可以修改该资源。悲观锁在事务开始时就获取锁,直到事务结束时才释放锁。
乐观锁
乐观锁(Optimistic Locking)假设在并发环境中很少发生冲突,因此在访问共享资源时不立即加锁,而是等到真正需要修改资源时再检查是否有冲突。如果发现冲突,则采取补偿措施(如重试或回滚)。
概念
乐观锁假设并发冲突很少发生,因此在操作数据时不会先加锁,而是通过某种机制(如版本号)来检测是否存在冲突。只有在提交时发现冲突才会进行重试。
实现原理
-
版本号机制: 每条记录附加一个版本号字段,每次更新时检查和更新版本号。
-
读取数据时,获取当前版本号。
-
更新数据时,检查数据库中当前版本号是否与读取时一致。
-
如果一致,则更新数据并将版本号加 1;如果不一致,则说明有冲突,操作失败或重试。
-
-
CAS (Compare and Swap): Java 的
java.util.concurrent
包中大量使用了 CAS 操作,例如AtomicInteger
、AtomicLong
。-
CAS 通过比较内存中的值和预期值,如果一致则更新,否则重试。
-
优缺点
-
优点:
-
不需要实际加锁,因此开销小,性能较高。
-
适用于读多写少的场景。
-
-
缺点:
-
在高并发下,频繁重试可能影响性能。
-
不适合写多读少的场景。
-
代码示例
版本号机制
// 模拟版本号机制 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 通过比较内存中的值和预期值,如果一致则更新,否则重试。)
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
class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } }
使用 Lock
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、数据库排他锁 |
根据具体的业务场景选择合适的锁机制。