Java并发系列:CAS操作

86 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

CAS和JUC下的原子类关系十分密切,在本篇博客中可能需要一些原子类的知识。

1、引言

在多线程环境下,i++语句是线程不安全的。我们可以使用synchronized关键字保证线程安全,但是synchronized有一些缺陷:锁太重,影响系统的性能。 在使用原子类后,即可解决上述存在的问题,即保证原子性,又不需要加重锁。

2、CAS概述

2.1 基本概念

在这里插入图片描述 在这里插入图片描述 Java中JUC下原子类的应用,底层就是CAS这种思想来保证。 在这里插入图片描述

2.2 硬件角度

在这里插入图片描述

  • 在硬件层面,在多线程情况下,只有一个线程能给总线加锁,也就是说这是硬件层面的加锁。

==说白了就是:在开发中使用JUC中的原子类,原子类是由CAS保证,Unsafe类实现了CAS思想。CAS是靠硬件实现,在硬件层面提升效率,交给了硬件来保证原子性和可见性==。

3、Unsafe类

Unsafe类是汇编原语级别的操作,我们只需要学习其原理,在工作开发中慎用。

在这里插入图片描述 在这里插入图片描述

3.1 unsafe保证i++线程安全的源码分析:

在这里插入图片描述 在这里插入图片描述

  • var5是根据偏移量在内存中得到的值,预期值和新值一样,则更新var5+var4,如果不一样则返回是false,取反后为true进行do while自旋。 在这里插入图片描述 对于i++,有两个线程进行操作: 在这里插入图片描述

4、原子引用

AtomicReference:原子包装类 目的:不仅可以有AtomicInteger,也可以有自己的包装类,例如AtomicOrder... 简单例子: 在这里插入图片描述

5、CAS缺点

优点:避开synchronized这种大重量级加锁,从底层硬件上实现轻量级的加锁。 但是CAS有三个主要缺点: 1、自旋时间长导致开销大 CAS是非阻塞同步,线程不会被挂起,自旋就会占用CPU资源。

2、ABA问题 因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,但是实际上的确发生了变化。解决方案可以沿袭数据库中常用的乐观锁方式,添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3C。java这么优秀的语言,当然在java 1.5后的atomic包中提供了AtomicStampedReference来解决ABA问题,解决思路就是这样的。

3、只能保证一个共享变量的原子操作 当对一个共享变量执行操作时CAS能保证其原子性,如果对多个共享变量进行操作,CAS就不能保证其原子性。有一个解决方案是利用对象整合多个共享变量,即一个类中的成员变量就是这几个共享变量。然后将这个对象做CAS操作就可以保证其原子性。atomic中提供了AtomicReference来保证引用对象之间的原子性。

参考资料

  • 大厂学苑 JUC