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、数据库排他锁 |
根据具体的业务场景选择合适的锁机制。

浙公网安备 33010602011771号