目录
- 乐观锁
- 悲观锁
乐观锁
乐观锁是一种乐观的锁机制,它假设并发冲突不会频繁发生,因此在数据处理过程中不会直接锁定数据,而是在更新数据时,会检查在此期间有没有其他用户修改过这个数据。如果有其他用户修改了数据,则操作失败并回滚。否则,操作成功。
- 实现方式
乐观锁的实现方式有多种。其中一种是使用版本号,另外一种是 CAS 。
版本号方式
每次读取数据时,都会获取一个版本号,然后在更新时,会检查版本号是否一致。如果版本号不一致,则操作失败。这种方式的好处是不会有死锁的情况发生,因为不会直接锁定数据。但是需要注意的是,如果并发冲突非常频繁,乐观锁可能会引起较多的失败回滚,影响性能。
CAS(Compare and Swap)
CAS 是一种无锁的线程安全实现方式。它是一个原子操作,用于在多线程环境下管理对共享数据的并发访问。CAS操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置 V 的值与预期原值 A 相匹配,那么将内存位置 V 的值修改为B;否则不做任何操作。
然而,CAS 也存在一些限制和问题。比如 ABA 问题,循环时间长开销大和只能保证一个共享变量的原子操作。
ABA 问题
CAS 需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把变更版本号。检查的是变量值和版本号的组合,这样就可以避免ABA问题。
自旋重试
在高并发环境下,CAS 操作可能会出现自旋重试的情况,即多次尝试比较并交换操作,直到成功为止,自旋重试会增加 CPU 的开销。为了减少自旋重试的开销,可以采用一些优化策略。例如,可以在自旋重试时引入适当的延迟,避免过于频繁地进行 CAS 操作。另外,可以设置自旋重试的次数上限,超过次数后转为使用其他机制,如锁。
原子性
CAS 只能保证对单个共享变量的原子操作,对于多个共享变量的复合操作,CAS 无法保证其原子性。针对这种情况,可以使用更高级别的并发原语,如锁或并发容器,来保证复合操作的原子性。
- 乐观锁优缺点
优点
- 不会阻塞其他事务的读操作,提高了系统的并发性能;
- 适用于读操作频繁,写操作较少的场景;
缺点
- 如果冲突较多,导致版本号频繁更新,性能可能下降;
- 不适用于写操作频繁的高并发场景;
悲观锁
悲观锁是一种悲观的锁机制,它假设并发冲突会频繁发生,因此在数据处理过程中会直接锁定数据,防止其他用户修改数据。在锁定期间,其他用户无法访问被锁定的数据。这种方式的好处是避免了失败回滚的情况,因为其他用户无法修改数据。但是需要注意的是,如果锁定时间过长,会影响其他用户的访问效率,甚至可能导致死锁。
悲观锁的实现方式也有多种,其中一种是使用SQL语句中的FOR UPDATE语句。该语句会在查询数据时锁定数据,直到事务结束时才释放锁。这种方式的好处是避免了失败回滚的情况,但是需要注意避免长时间锁定数据和死锁的情况。
- 悲观锁优缺点
优点
- 确保事务之间数据的一致性,避免了脏读、不可重复读等问题;
- 适用于写操作频繁的高并发场景;
缺点
- 阻塞其他事务的读写操作,降低了系统的并发性能;
- 可能导致死锁问题,需要谨慎使用;