CAS简解
CAS 名词解释
- Compare-And-Swap 比较并替换
- 它是一条CPU并发原语【原语:属于操作系统语言范畴,它的执行是连续的,不允许被中断的,也就是说CAS是CPU的一条原子指令,不会造成数据不一致的问题】
- 它的主要功能是判断主内存某个位置的值是否跟预期的值一样,相同就进行修改,否则一直重试,直到一直为止
- 主要用于实现无锁并发控制,确保在多线程环境对共享变量的操作是原子性的
底层操作逻辑
- 主要在于三个核心参数
- 内存位置(V) --- 指向要更新遍历的内存地址
- 预期原值(E) --- 更新前的预期值
- 新值(N) ----要更新的新值
- 逻辑
- 当且仅当内存位置的值与预期的值一致时,该位置的值会更新为新值,否则不进行任何操作
具体使用和示例
- Java中使用CAS的类:
java.util.concurrent.atomic
包提供了一系列原子类,如AtomicInteger
、AtomicLong
等。【注意:atomic类是通过 sun.misc.Unsafe
类的底层的CAS操作功能。如:compareAndSwapInt
和compareAndSwapLong
等方法可以直接在内存地址上执行CAS操作】 -
示例1:
-
package thread; import java.util.concurrent.atomic.AtomicInteger; public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(1); System.out.println(atomicInteger.compareAndSet(1, 2) + "\t 当前数据 值 : " + atomicInteger.get()); //修改失败 System.out.println(atomicInteger.compareAndSet(1, 3) + "\t 当前数据 值 : " + atomicInteger.get()); // 自增并返回当前的atomicInteger值 atomicInteger.getAndIncrement(); } }
底层原理
- UnSafe类(Native方法) + CAS思想
-
UnSafe类
- UnSafe类是CAS的核心类,由于Java方法是不能直接操作底层系统的,需要通过本地方法来访问,通过UnSafe类可以直接操作特定内存中的数据
- UnSafe类中变量使用volatile来修饰,保证了内存的可见性;Atomic的自增就是由于类中的值使用volatile修饰
-
CAS思想(自旋锁)
- 在使用CAS操作时,当CAS操作失败后,线程不直接阻塞等待,而是继续尝试执行,直到CAS操作成功为止
- 自旋锁的意思时程序使用循环来等待特定条件的实现方式,先比较传统的阻塞锁,自旋锁不会使线程进入阻塞状态,因此避免了线程上下切换带来的开销。通常当线程竞争的资源空闲等待的时间不长时,自旋锁时一种比较高效的同步机制
优点
- 无锁并发: CAS操作不需要显示的锁机制,减少了锁竞争的开销,提高了并发性能
缺点
- ABA问题:如果内存位置的值从A变成B又变回A,那么CAS无法检测出这种变化。可以通过使用带版本号的CAS操作或使用AtomicStampedReference类解决
- 活锁问题:在最差的情况下,如果比较不成功,线程会一直循环,可能导致性能问题,通常建议使用分段CAS和自动迁移机制来优化
- 只能保证一个共享变量的原子性操作
ABA问题
分段CAS
所谓CAS分段机制,其维护这一个base变量和一个cell数组,当多个线程操作一个变量的时候,先会在这个base变量上进行cas操作,当它发现线程增多的时候,就会使用cell数组。比如当base更新为3的时候发现线程增多(也就是casBase操作失败),那么它会自动使用cell数组,每一个线程对应于一个cell,在每一个线程中对该cell进行cas操作,这样就可以提高并发效率,分散并发压力
自动迁移机制
当线程增多,每个cell中分配的线程数也会增多,当其中一个线程操作失败的时候,它会自动迁移到下一个cell中进行操作,这也就解决了CAS空旋转,自旋不停等待的问题。这就是自动迁移机制
使用
- CAS操作本身是不保证内存可见性的。通常需要结合其他机制来保证内存可见性,如:volatitle关键字、synchronized关键字
- CAS是一种乐观锁机制。具体实现为Unsafe类+自旋,通过Unsafe类提供硬件级别的原子性操作保证了并发安全,加上自旋操作,解决了synchronized在多线程环境下出现的线程阻塞,唤醒切换,以及用户态内核态之间的切换带来的消耗
- CAS可以不加锁保证操作的原子性,Java标准库提供了Atomic+包装类,相关的组合类来实现原子操作,这些类都在java.util.concurrent.atomic 包底下