这里可以把悲观锁看作悲观的人,啥事都往最坏的方向想。乐观锁看作乐观的人,啥事都往最好的方向想。
首先,说一下悲观锁。
悲观锁就是假设并发情况下一定会有其他线程来修改数据,因此在处理数据之前,先将数据锁住,确保其他线程不能进行修改
。感觉像一个过于悲观的做法,想象一下,所有人去上街,你一定要把自己房门锁好才出门,这样就保证自己的房间不会被人闯入了。但是这种做法存在缺点,
比如频繁的加锁、解锁会占用大量的系统资源,降低了并发性能
,因此在高并发场景使用悲观锁是比较麻烦的,并且还可能出现死锁问题,影响代码的运行。
像 Java 中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
public void performSynchronisedTask() {
synchronized (this) {
// 需要同步的操作
}
}
private Lock lock = new ReentrantLock();
lock.lock();
try {
// 需要同步的操作
} finally {
lock.unlock();
}
接下来是乐观锁,
在多线程并发情况下,不加锁也可以保证数据的正确性。它假设数据不会被其他线程修改,只有在更新时才进行数据冲突的检测,一旦发现冲突则进行回滚或者重试
。这种做法就像大家去上街,对于自己的房间不再“悲观地”加锁,但是在回家,会发现门打开了,还有可能看到留在家中物品被人碰过或拿走了,于是就重新处理一下回家的情况。
乐观锁一般会使用版本号机制或 CAS 算法实现
像 Java 中java.util.concurrent.atomic包下面的原子变量类(比如AtomicInteger、LongAdder)就是使用了乐观锁的一种实现方式 CAS 实现的。
// LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好
// 代价就是会消耗更多的内存空间(空间换时间)
LongAdder longAdder = new LongAdder();
// 自增
longAdder.increment();
// 获取结果
longAdder.sum();
悲观锁和乐观锁虽然思想大相径庭,但是都能达到保证数据一致性的目的。在应用场景上要选对合适的锁机制,不然会浪费时间和资源哦。
悲观锁是一种更加保守的策略,适用于竞争激烈的场景,而乐观锁是一种更加乐观的策略,适用于竞争不激烈的场景。它们各有优点和局限性,需要根据具体情况来选择使用。(这里的竞争激烈指的是多写场景)