在多线程下,i++;操作不具有原子性,反汇编后i++由三步组成:取值,加操作,赋值.多线程造成累加不准确.
JUC包中原子类如何保证多线程下数据累加的准确性呢?
原子类原子操作的原理
- 首先,原子类中value属性通过volatile修饰,保证多线程间value的变化对其他线程是可见的;
- 其次,原子类中通过unsafe类获取value属性对应的内存偏移地址,即valueOffset
- 以AtomicInteger类的getAndSet方法举例,底层调用
unsafe.getAndSetInt()
- 获取传入的实例this在valueOffset地址对应的值,即value属性的值,使用var5暂存,调用本地方法
compareAndSwapInt()
,通过this + valueOffset始终可以获取最新的value属性值,将value与var5对比,一致则将value值设置为 newValue,不一致则重新获取 - 原子类通过上述步骤避免多线程下数据不一致的问题.
源码说明
public class AtomicInteger extends Number implements java.io.Serializable {
// ...
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
// 获取value的内存地址,即valueOffset
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// volatile修饰value
private volatile int value;
public final int getAndSet(int newValue) {
// this 原子类实例,
// value 属性的内存偏移地址,
// newValue 待更新的值
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
// 获取实例的value属性值
var5 = this.getIntVolatile(var1, var2);
// v1,v2始终可以获取最新的value属性值v_new,并传入当前value值v5用于对比,
// v_new==v5,则将v4设为最新值,并返回true,跳出循环,否则重复
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
// compareAndSwapInt为native本地方法
// CPU层面调用CPU指令cmpxchg实现
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
小结
CAS可以理解成是一种思想或算法,即
比较与交换
.而JUC包下的原子类通过结合使用volatile
,unsafe类
以及CAS
(通过do while循环不断比较,与自旋锁的实现类似)来解决多线程下数据安全问题.