什么是CAS
在并发的工作中,CAS做的工作就是保证一个值能够保证多个线程对他进行修改,并且能够修改正确,比如我们在并发的时候,常常会有多个线程对一个对象E进行修改赋值的操作,CAS所做的就是在修改值之前给出一个修改值和期望值,如果期望值等于我们修改后的值,那么他就会修改成功,如果不一致,就为修改失败。
如何解决
在多线程中,我们平时为了避免一个值会被多个值进行修改会对其修改的方法加锁,synchronized可以很好的解决,但是synchronize他是一个重量级的锁,每次都需要获取锁和释放锁,需要进行内核态和用户态的切换。并不是很推荐这样做。
所以为了解决这样的问题我们会使用到原子类,原子类的作用和锁有类似之处,是为了保证并发下线程安全。不过原子类相对于锁,有一定的优势。
- 原子变量可以吧竞争范围缩小到变量级别,通常情况下,锁的粒度都要大于原子变量的粒度。
- 除了高度竞争的情况之外,使用原子类的效率通常会比使用同步互斥锁的效率更高,因为原子类底层用了CAS操作,不会阻塞线程。
怎么使用CAS
原子类
类型 | 具体类 |
---|---|
Atomic* 基本类型原子类 | AtomicInteger、AtomicLong、AtomicBoolean |
Atomic*Array 数组类型原子类 | AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray |
Atomic*Reference 引用类型原子类 | AtomicReference、AtomicStampedReference、AtomicMarkableReference |
Atomic*FieldUpdater 升级类型原子类 | AtomicIntegerfieldupdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater |
Adder 累加器 | LongAdder、DoubleAdder |
Accumulator 积累器 | LongAccumulator、DoubleAccumulator |
AtomicInteger 类常用方法
public void Atomic(){
//获取当前的值
System.out.println(integer.get());
//获取当前的值,并设置新的值
System.out.println(integer.getAndSet(1));
//获取当前的值,并自增
integer.getAndIncrement();
//获取当前额值,并自减
System.out.println(integer.getAndDecrement());
//获取当前的值并加上预期的值
System.out.println(integer.getAndAdd(1));
//如果输入的数值等于预期值,则以原子方式将该值更新为输入值
integer.compareAndSet(1,1);
}
对于int类型,相应的我们可以使用AtomicInteger来实现
AtomicInteger底层是通过unsafe类来操作数据的,其内部维护了一个Integer类型的属性value
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
1、这段代码时获取到value这个属性相对于AtomicInteger对象的内存偏移量,方便后续直接通过unsafe来修改内存当中的值
对于上面我们的n++操作,对应到AtomicInteger类当中
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
通过对象和对象中属性的偏移量,来计算出当前偏移量下,存储的旧的数值,然后在旧值上增加新值,这一步需要注意的是,通过do-while这种方式,直到修改成功
现在再来看 while 的退出条件,也就是 compareAndSwapInt 这个方法,它一共传入了 4 个参数,这 4 个参数是 var1、var2、var5、var5 + var4,为了方便理解,我们给它们取了新了变量名,分别 object、offset、expectedValue、newValue,具体含义如下:
第一个参数 object 就是将要操作的对象,传入的是 this,也就是 atomicInteger 这个对象本身;
第二个参数是 offset,也就是偏移量,借助它就可以获取到 value 的数值;
第三个参数 expectedValue,代表“期望值”,传入的是刚才获取到的 var5;
而最后一个参数 newValue 是希望修改的数值 ,等于之前取到的数值 var5 再加上 var4,而 var4 就是我们之前所传入的 delta,delta 就是我们希望原子类所改变的数值,比如可以传入 +1,也可以传入 -1。
最终调用的就是unsafe当中的compareAndSwapInt
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
其参数就是当前对象,var2:属性相对于当前对象的偏移量,var4 :期待值,var5:修改的新值
说到底,Unsafe 的 getAndAddInt 方法是通过循环 + CAS 的方式来实现的,在此过程中,它会通过 compareAndSwapInt 方法来尝试更新 value 的值,如果更新失败就重新获取,然后再次尝试更新,直到更新成功。
上面简单的介绍了AtomicInteger能够保证原子性的原理,其他的就不多说了,下面就简单介绍一下。
Array 数组类型原子类
- AtomicIntegerArray:整形数组原子类;
- AtomicLongArray:长整形数组原子类;
- AtomicReferenceArray :引用类型数组原子类。
Atomic\Reference 引用类型原子类
- AtomicStampedReference:它是对 AtomicReference 的升级,在此基础上还加了时间戳,用于解决 CAS 的 ABA 问题。
- AtomicMarkableReference:和 AtomicReference 类似,多了一个绑定的布尔值,可以用于表示该对象已删除等场景。
Atomic\FieldUpdater 原子更新器
- AtomicIntegerFieldUpdater:原子更新整形的更新器;
- AtomicLongFieldUpdater:原子更新长整形的更新器;
- AtomicReferenceFieldUpdater:原子更新引用的更新器。
原子更新器的主要作用就是把不具备原子属性的变量变成可以拥有CAS操作属性的变量,他存在的目的其实是为了解决我们平时存在的历史遗留问题,比如之前有个int类型的数据,不具备原子属性,但是突然在并发环境下运行,所以就存在需要具备CAS操作,这时,我们就可以使用原子更新器来使用了。另外除了这种场景,在有些并发的环境下,我们如果操作的原子属性过多的情况下,可以为了节省内存而使用int属性保存数据,CAS操作却使用FieldUpdate来完成,这样可以有效的节省不少的内存。
Adder 加法器
它里面有两种加法器,分别叫作 LongAdder 和 DoubleAdder。
Accumulator 积累器
最后一种叫 Accumulator 积累器,分别是 LongAccumulator 和 DoubleAccumulator。