我们以这段代码为例进行讲解:
class MyData{
AtomicInteger atomicInteger = new AtomicInteger();
public void addMyAtomic(){
atomicInteger.getAndIncrement();
}
}
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
myData.addMyAtomic();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程全部计算完成后,再用main线程取得最终的结果看是多少
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type,finally number value:" + myData.atomicInteger);
}
}
上面得出来的结果是20000,那么 getAndIncrement()方法是如何保证原子性的呢?我们还是有些不太理解。
那么接下来我们就提出原语这个词
CAS的全称为Compare-And-Swap,它是一条CPU并发原语它的功能是判断内存某个位置是否为预期值,如果是则更新为新的值,这个过程是原子的。
CAS并发原语体现在Java语言中就是sun.misc.Unsafe类的各个方法,调用UnSafe中的CAS方法,JVM会帮我们实现出CAS汇编指令,这时一种完全依赖于硬件的功能,通过他实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
CAS并发原语的特点
我们先来看一下它的底层实现:
Unsafe类中的compareAndSwapInt,是一个本地方法,该方法的实现位于unsafe.cpp中,该方法中的Atomic::cmpxchg(x,addr,e) ==e;实现比较替换(参数x是即将更新的值,参数e是原内存的值)
缺点:
1.循环时间长开销大(多个线程一直循环CPU飙高)
2.只能保证一个共享变量的原子操作(this+偏移量指定的那个变量)
3.ABA问题
总结:CAS比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。