CAS(Compare And Swap)
是非阻塞同步的实现原理,它是CPU硬件层面的一种指令;
CAS制定操作包含三个参数
- 内存值(内存地址)v
- 预期值E
- 新增值N
当CAS指令执行时,当且仅当预期值E和内存值V相同时,才更新内存值为N,否则不更新;
上述的处理过程是一个原子操作;
CAS 可以看作是它们合并后的整体一个不可分割的原子操作,并且其原子性是直接在硬件层面得到保障的。
CAS的应用场景
再juc的atomic相关类,AQS,currentHashMap等是线上有广泛的应用
在并发编程中最容易出现的是线程安全的问题,i++在多线程的时候,就无法得到正确的值;而使用synchronized又不能高性能的解决问题;
actomic包提供了一组原子操作类
- 基本类型:AtomicInteger,AtomicLong,AtomicBoolean
- 引用类型:AtomicReferenc、AtomicStampedReference
- 数组类型:AtomicIntegerArray
- 原子累加器LongAdder,Striped64
原子更新基本类型
以AtomicInteger为例总结常用的方法
//以原子的方式将实例中的原值加1,返回的是自增前的旧值;
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);}//getAndSet(int newValue):将实例中的值更新为新值,并返回旧值;public final boolean getAndSet(boolean newValue) {boolean prev;do {prev
= get();} while (!compareAndSet(prev, newValue));return prev;}//incrementAndGet() :以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果;public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}//addAndGet(int delta) :以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果;public final int addAndGet(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
通过CAS自增实现,如果CAS失败,自旋直到成功+1。
LongAdder
是LongAdder引入的初衷——解决高并发环境下AtomicInteger,AtomicLong的自旋瓶颈问题。
设计思路
AtomicLong中有个内部变量value保存着实际long值,所有的操作都是针对该变量进行。也就是说高并发的环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率小很多。如果要获取真正的long值,只要将各个槽点的变量值累加返回;
LongAdder的内部结构
LongAdder内部有一个base变量,一个Cell[]数组:
base变量:非竞态条件下,直接累加到该变量上
Cell[]数组:竞态条件下,累加个各个线程自己的槽Cell[i]中
CAS缺陷
CAS 虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方面:
- 自旋 CAS 长时间不成功,则会给 CPU 带来非常大的开销
- 只能保证一个共享变量原子操作
- ABA 问题
ABA问题的描述和解决方案
描述 :当有多个线程对一个原子类进行操作的时候,某个线程在短时间内将原子类的值A修改为B,又马上将其修改为A,此时其他线程不感知,还是会修改成功。
ABA问题的解决方案
数据库有个锁称为乐观锁,是一种基于数据版本实现数据同步的机制,每次修改一次数据,版本就会进行累加。
同样,Java也提供了相应的原子引用类AtomicStampedReference, reference即我们实际存储的变量,stamp是版本,每次修改可以通过+1保证版本唯一性。这样就可以保证每次修改后的版本也会往上递增