https://blog.youkuaiyun.com/bluetjs/article/details/52261490?locationNum=15&fps=1
1.什么是cas?
cas是一种无锁算法(非阻塞算法:一个线程的失败或者挂起不应该影响其他线程的失败或者),是compare and swap的缩写,表示为比较并交换的意思
2.cas算法:
cas有三个操作数,v内存值,旧的预算值A,需要修改的值B,当且预期值A和内存值V相等时,将内存值修改为B,否则什么都不做、
3.++i操作
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
这里采用了CAS操作,每次从中读取数据都会将此数据和+1后的结果进行CAS操作,如果成功则返回结果否则重试到成功为止,而这里的compareAndSet利用JNT( JNI:Java Native Interface为JAVA本地调用,允许java调用其他语言。)来完成CPU的指令操作
compareAndSet的代码
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
compareAndSwapInt类似这样的逻辑:
if(this==except){
this=update
return true;
}else{
return false;
}
4.CAS原理:
CAS是通过调用JNT代码实现的,而compareAndSwapInt就是就是借用CAS来调用CPU底层指令实现的下面通过(inel x86)来解释CAS的实现原理
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
可以看到这是个本地方法调用。这个本地方法在openjdk中依次调用的c++代码为:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp。这个本地方法的最终实现在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011\openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp(对应于windows操作系统,X86处理器)。下面是对应于intel x86处理器的源代码的片段:
// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0 \
__asm je L0 \
__asm _emit 0xF0 \
__asm L0:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
如上面源代码所示,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。
5.CAS的缺点:
CAS虽然能高效的保证原子操作,但CAS还是存在三大问题,ABA问题,循环时间长开销大,只能保证一个共享变量的原子操作
1.ABA问题:因为CAS需要在操作时检查值有没有发生变化,,如果没有发生变化就更新,但是如果一个值原来是A变成了B,再变成A,那么CAS检查不到他的值发生变化,而实际上是变化了的,A->B->A,也就是说,CAS不能发现数值是否改变的过程,他只会看到结果,
问题实例化:假如银行扣费,有两个线程操作,正确应该是一个扣费成功,一个失败,而我应少50元,假如第一个线程扣费50元后,第二个线程检查之前,我收到转账50元,那么线程2还会对我进行扣费,所以我被扣100元
解决方法:添加版本号,1A->2B->3A
2.循环时间长:开销大:如果CAS长时间不成功,则会带来非常大的开销,如果,jvm支持pause指令,效率会有一定的提升,pause指令有两个作用,1、延迟流水线执行指令,使cpu不会消耗过多执行资源2、避免退出循环的时候因内存顺序冲突而引起cpu流水线被清空,从提高CPU的执行效率
3.只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环的方式来保证操作的原子性,这时候就要用锁
6.CAS的优化
java8对CAS的优化:为了防止CAS不断自旋,消耗大量资源,于是java8推出了一个LongAdder,他使用分段CAS和自动分段迁移的方式来大幅度提升多线程高并发执行CAS操作0性
在LongAdder的底层实现中,首先有一个base值,刚开始多线程不停地累加数值,都是对base进行累加的,比如刚开始累加成base=5;接着,如果发现并发更新的线程数过多,就开始实行分CAS机制也就是内部会创建一个Cell数组,没个数组是一个分段值,它内部也实现了自动分段迁移的机制,就是如果一个Cell的value执行CAS失败了,那么他就会自动寻找另外一个Cell分段内的value值进操作
优点是把CAS的计算压力分散到不同的Cell中了,解决了线程空自旋、自旋不等待CAS操作的问题,让一个CAS来执行时可能尽快的完成这个操作