Unsafe类+CAS思想(自旋)
CAS:比较并交换compare and swap,是一条CPU并发原语。原语属于操作系统用语范畴,是不可修改、不可中断的连续指令,不会造成数据不一致问题。
1.AtomicInteger.compareAndSet( int expect,int update),符合期望才允许更新
2.AtomicInteger.getAndIncrement
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
valueOffset:内存偏移量
1.unsafe是java基础包sun.misc中的方法,是CAS的核心类,由于java无法直接访问操作系统底层,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。原理是类似于C一样用指针操作内存。
**unsafe类中的所有方法都是用native修饰的,也就是说unsafe中所有方法都可以直接调用操作系统底层资源执行相应的任务
**原子类型在多线程环境中不用加锁是因为直接使用unsafe类操作内存,故不存在
CAS底层实现:
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;
}
底层汇编
总结:
比较工作内存和主内存中的值,一样时才允许执行规定操作,否则继续比较,直到两者值一致为止;
CAS有3个操作数,旧的预期值A,内存值V,更新值C。当A和V相等时,将V改为B。
CAS缺点:
1.do while自旋 时间长会使CPU开销很大
2.只能保证一个共享变量的原子操作
3.ABA问题
CAS —> Unsafe —> CAS底层思想 —> ABA —> 原子更新引用 —> 如何规避ABA问题?
ABA问题,CAS算法实现的一个重要前提是取出内存某时刻中的数据并在当下时刻进行比较,那么在这个时间差中可能会导致数据产生变化。比如两个线程1,2对变量X进行处理,X初始值为A。由于时间戳,可能在1进行cas校验前,2已经将X修改过并且修改回来,然而线程1并不知道,所以通过了cas校验。
换言之,CAS算法只管结果,不管过程,所以中间可能被其他线程动过手脚,这就是ABA问题产生的原因。因此,不在乎过程的话CAS算法没问题。
如何解决ABA问题?
使用原子引用AtomicRefence + 版本号(类似时间戳)=AtomicStampedRefence<>
//原子引用
User user=new User;
AtomicRefence<User> atoUser=new AtomicREfence<>();
atoUser.set(user);
atoUser.compareAndSet(100,101);
//加版本原子引用
User user=new User;
AtomicStampedRefence<User> atoUser=new AtomicStampedRefence<>();
atoUser.set(user);
atoUser.compareAndSet(100,101,atoUser.getStamp,atoUser.getStamp+1);