CAS与自旋锁
CAS
CAS –> Unsafe –> CAS底层思想 –> ABA问题 –> 原子引用更新 –> 如何规避ABA问题
CAS底层原理
- 简单来说就是Unsafe类+CAS思想(自旋)
1 | public class CASDemoClass { |
CAS(Compare and swap),即比较并交换。自旋锁或乐观锁,其中的核心操作实现就是CAS。
它的功能是判断内存中某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
从操作系统底层来说,CAS是一条CPU并发原语。
CAS并发原语体现在Java语言中sun.misc包下的Unsafe类,调用这个类的CAS方法,JVM会帮我们实现出CAS汇编指令。
这是一种完全依赖硬件的功能,通过这个实现了原子操作。由于CAS是一种系统原语,原语属于操作系统用语范畴,
是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被打断,
也就是说CAS是一条CPU的原子指令,不会出现数据不一致的问题。
- AtomicInteger.getAndIncrement()方法是如何实现在多线程情况下的自增函数的线程安全的?
原因就在getAndIncrement()方法的底层实际上是CAS思想,靠的是unSafe类CPU指定原语保证原子性
①变量valueoffset表示该变量值在内存中的偏移地址,Unsafe类可以通过该值获取对应地址数据
②变量value使用volatile修饰,保证了多线程之间的数据可见性
1 | public final int getAndIncrement() { |
1 | /* |
那么这个Unsafe类又是什么呢?
Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库
都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,
增强Java语言底层操作能力方面起了很大的作用。Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,
同时也带来了指针的问题。
- Unsafe类中的所有方法都是用native修饰的,也就是说Unsafe类中的方法都是直接调用操作系统底层资源执行任务。
CAS的缺点
1.循环时间长
因为getAndAddInt方法执行时,有个do whlie,
如果CAS失败会一直进行尝试,长时间不成功就会给CPU代来很大的开销
2.只能保证一个共享变量的原子操作
3.引出ABA问题
ABA问题
当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有
被其他线程访问过,又进行其的修改行为。看起来,这一切没有问题,实际上过程中是发生了很多变化,而在某些
场景下这样是存在错误风险的。
那么如何解决这个ABA问题呢,大多数情况下乐观锁的实现都会通过引入一个版本号标记这个对象,每次修改版本号
都会变化,比如使用时间戳作为版本号,这样就可以很好的解决ABA问题。
在JDK中提供了AtomicStampedReference类来解决这个问题,思路是一样的。这个类也维护了一个int类型的标记stamp,
每次更新数据的时候顺带更新一下stamp。
- 剩下内容有机会再补充,就先这样
自旋锁
自旋锁(SpinLock)是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁。
好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
- 自旋锁代码验证
1 | import java.util.concurrent.TimeUnit; |