JUC下的atomic类都是通过CAS来实现的,比如AtomicInteger
AtomicInteger 类调用incrementAndGet()方法实现原子性的自增,内部调用Unsafe的getAndAddInt方法:
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
在Unsafe类的getAndAddInt方法中主要是看compareAndSwapInt方法:
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;
}
我们可以看到Unsafe类中的compareAndSwapInt是一个native本地方法:
public final native boolean compareAndSwapInt(Object var1, long var2,
int var4, int var5);
到具体实现的虚拟机(HotSpot)中去查看C++源码:
!!!!!我们可以查看Atomic::cmpxchg函数的具体实现:
(ASM是C++中的一个关键字,用于在C++源码中内嵌汇编语言)
不难发现,cmpxchg这条汇编语言可以直接操作内存进行数据交换,实现CAS最终目的。
(一条汇编指令对应一条CPU指令,是单步操作,自然是原子性的,因此谁CAS实现是硬件层面上的)
这里看到有一个LOCK_IF_MP,作用是如果是多处理器,在指令前加上LOCK前缀,因为在单处理器中,是不会存在缓存不一致的问题的,所有线程都在一个CPU上跑,使用同一个缓存区,也就不存在缓存与主内存不一致的问题,不会造成可见性问题。
(缓存在CPU上,主内存不在CPU上,CPU是通过缓存去读取主内存的,每个CPU对应一个缓存,不同缓存对应不同CPU,这里要结合前面的JMM模型和硬件架构理一理)
然而在多核处理器中,需要遵循缓存一致性协议通知其他处理器更新自己的缓存。
Lock在这里的作用:
-
在cmpxchg执行期间,锁住内存地址[edx],其他处理器不能访问该内存,保证原子性。
(这个就是保证CAS原子性的关键所在) -
写内存屏障,保证每个线程的本地空间与主存一致。
-
禁止cmpxchg与前后任何指令重排序,防止指令重排序。
CAS缺点
CAS虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方面:
1.自旋时间太长
如果CAS一直不成功呢?这种情况绝对有可能发生,如果自旋CAS长时间地不成功,则会给CPU带来非常大的开销。在JUC中有些地方就限制了CAS自旋的次数,例如BlockingQueue的SynchronousQueue。
2.只能保证一个共享变量原子操作
看了CAS的实现就知道这只能针对一个共享变量,如果是多个共享变量就只能使用锁了,当然如果你有办法把多个变量整成一个变量,利用CAS也不错。例如读写锁中state的高低位。
3.ABA问题
CAS需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情况:如果一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但是实质上它已经发生了改变,只是又回到了原来的值而已,这就是所谓的ABA问题。对于ABA问题其解决方案是加上版本号,即在每个变量都加上一个版本号,每次改变时加1,即A —> B —> A,变成1A —> 2B —> 3A,采用AtomicStampedRdference类可以实现这个方案。



