CAS原理
原子类 线程安全 非阻塞,底层没有锁,底层实现原理CAS无锁技术,就是比较再交换:compare and swap。
CAS包含三个参数(V,E,N)V表示要更新的变量,E表示预期值,N表示新值。
仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。
V=需要去更新的变量(主内存的共享变量)
E=预期值(本地内存的副本)
N=新值
如果V=E(主内存的值与本地内存的值一致,说明没人修改),将V的值设置为N。
如果V不等于E(主内存的值跟本地内存的不一致),已经被修改。这样的情况下,就将E的值设置成V的值,重新刷新,再比较。看下源码,无非就是一个无限刷新,知道V和E的值一致。
在原子类中大量使用到CAS
AtomicInteger的实现
通过上一篇中volatile的自增的例子,我们知道要想实现这种自赠的效果就需要加锁,为了提高效率,这种场景下原子类型就可以胜任。
AtomicIntegerai =new AtomicInteger(1);
ai.incrementAndGet();
查看实现代码:
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
根据incrementAndGet()方法了解到AtomicInteger是对U的一个封装,U就是Unsafe类。
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private static final long VALUE;
static {
try {
VALUE = U.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
private volatile int value;
这段代码首先获得Unsafe对象,先声明一下Unsafe是个单例,Unsafe里面基本都是native方法。
static代码块里面初始化了VALUE这个值,static修饰的类加载的时候就会被初始化,并且引用是放到 Jvm的方法区的属于类的数据。
继续VALUE是什么呢?查看U.objectFieldOffset()方法:
/**
* Gets the raw byte offset from the start of an object's memory to
* the memory used to store the indicated instance field.
*
* @param field non-null; the field in question, which must be an
* instance field
* @return the offset to the field
*/
public long objectFieldOffset(Field field) {
return field.getOffset();
}
看方法注释:从对象的内存处开始,获得原始字节偏移量,用于存储实力对象的内存。
偏移量
在这里偏移量的意思就像我们 new 一个数组,数组的地址就是数组地一个元素的地址,假如数组地址是 a,第二个元素就是a+1,其中+1就是偏移量。对应的对象的一个属性的偏移量就是其对象的地址开始增加,增加的数就是这个filed的偏移量。
对于VALUE这个值我们知道了,他是AtomicInteger中的value属性对应的偏移量,就是对象地址+VALUE = value的地址
继续看代码:
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
/**
* Atomically adds the given value to the current value of a field
* or array element within the given object {@code o}
* at the given {@code offset}.
*
* @param o object/array to update the field/element in
* @param offset field/element offset
* @param delta the value to add
* @return the previous value
* @since 1.8
*/
// @HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
知道了offset值的意义之后,继续向下就是 v = getIntVolatile(o, offset);
这段代码,这个代码含义其实就是根据object和属性在object中的偏移地址,拿到 v(对应的共享内存中的 value 值,通过volatile控制值的可见性)。
compareAndSwapInt(o, offset, v, v + delta)
这个就是CAS(CompareAndSwap)机制 : 先拿着 v(预期的值)和 共享内存的值 做比较 如果其他线程没有修改过就替换掉,否则就一直自旋判断直到成功。
如果在比较过程中不成功,也就是值被其他线程修改了,这时候CAS机制是一直循环的,这样无非也会消耗大量CPU。
CAS是如何保证原子性的呢?
看了CAS的java代码并没提到他是通过什么方式保证原子性的,CAS是通过Unsafe类调用C然后调用处理器的指令,大部分处理器都实现了CAS的原子性,对于多核处理器在运行到CAS指令的时候会标记一个lock,当处理器运行到lock这个标记时,其他处理器就处于等待状态,单核处理器按步骤进行不会影响。另外一种保证原子性的处理器是通过保证在同一时间内当前处理器访问的共享内存地址不被其他处理器访问,新的方式提高了效率。
CAS缺点(ABA问题)
当一个线程在对共享变量进行操作时,读取到的值为A,如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。
解决办法:java并发包中提供了一个带有标记的原子引用类AtomicStampedReference
,它可以通过控制变量值的版本来保证CAS的正确性,相当于给主内存的变量的值给了个标记。