乐观锁(CAS)
什么是CAS
-
CAS(compare and swap) 比较并替换,比较和替换是线程并发算法时用到的一种技术
-
CAS是原子操作,保证并发安全,而不是保证并发同步
-
CAS是CPU的一个指令
-
CAS是非阻塞的、轻量级的乐观锁
为什么说CAS是乐观锁
乐观锁,严格来说并不是锁,通过原子性来保证数据的同步,比如说数据库的乐观锁,通过版本控制来实现等,所以CAS不会保证线程同步。乐观的认为在数据更新期间没有其他线程影响
CAS原理
CAS(compare and swap) 比较并替换,就是将内存值更新为需要的值,但是有个条件,内存值必须与期望值相同。举个例子,期望值 E、内存值M、更新值U,当E == M的时候将M更新为U。
CAS优缺点
-
优点
- 非阻塞的轻量级的乐观锁,通过CPU指令实现,在资源竞争不激烈的情况下性能高,相比synchronized重量锁,synchronized会进行比较复杂的加锁,解锁和唤醒操作。
-
缺点
-
ABA问题 线程C、D,线程D将A修改为B后又修改为A,此时C线程以为A没有改变过,java的原子类AtomicStampedReference,通过控制变量值的版本来保证CAS的正确性。
-
自旋时间过长,消耗CPU资源, 如果资源竞争激烈,多线程自旋长时间消耗资源。
-
CAS例子
不加锁
代码:
|
|
结果:
p 134066
加上乐观锁
代码
|
|
结果
p 200000
乐观锁与悲观锁对比
-
乐观锁没有加锁和解除锁的步骤,直觉上会快一些;但是乐观锁这么做的前提是总认为不会发生并发,如果并发发生的概率很大,重试的次数会增加,这种情况下乐观锁的性能就差很多了。
-
悲观锁有加锁和解除锁的步骤,直觉上会慢一些;但是当有很多进程或者线程对同一个数值进行修改时,能避免大量的重试过程,这种情况下悲观锁的性能相对就很高了。
Golang中的乐观锁与悲观锁
sync/atomic
Golang中有一个 atomic 包,可以在不形成临界区和创建互斥量的情况下完成并发安全的值替换操作,这个包应用的便是乐观锁的原理。
不过这个包只支持int32/int64/uint32/uint64/uintptr这几种数据类型的一些基础操作(增减、交换、载入、存储等)
sync
Golang中的sync包,提供了各种锁,如果使用了这个包,基本上就以悲观锁的工作模式了。
CAS总结
CAS不仅是乐观锁,是种思想,我们也可以在日常项目中通过类似CAS的操作保证数据安全,但并不是所有场合都适合,曾看过帖子说,能用synchronized就不要用CAS,除非遇到性能瓶颈,因为CAS会让代码可读性变差,这句话看大家怎么理解了。
参考文章
深入分析CAS(乐观锁):https://juejin.im/post/5a803e61f265da4e914b5b63
乐观锁与悲观锁与Golang:https://jingwei.link/2018/07/07/optimistic-perssimistic-locking-and-golang.html
- 原文作者:Daryl
- 原文链接:https://siskinc.github.io/post/%E4%B9%90%E8%A7%82%E9%94%81cas/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。