AtomicInteger是通过unsafe类来变更数据的。
原理是:(1) 通过unsafe获取到字段的内存偏移量。 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value"));
(2)unsafe直接操作valueOffset内存偏移对应的值+1。 return U.getAndAddInt(this, VALUE, 1); CAS的操作是乐观锁思想的应用。 所以能保证原子性。
volatile的作用:保证内存可见性、防止指令重排
volatile如何保持内存可见性?
volatile的特殊规则就是:
read、load、used必须连续出现
assign、store、write必须连续出现
所以volatile能保证:
每次“读取前”,必须先从主内存刷新最新的值 每次“写入后”,必须立即同步到主内存中 也就是说,volatile关键字修饰的变量看到的随时是自己的最新值。
线程1中对变量v的最新修改,对线程2是可见的。 volatile如何防止指令重排 打个比方,写一个单例:
class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance() {
if ( instance == null ) { //当instance不为null时,仍可能指向一个“被部分初始化的对象”
synchronized (Singleton.class) {
if ( instance == null ) {
instance = new Singleton();
}
}
}
return instance;
}
}
复制代码**这个单利程序有一个指令重拍的问题: **
instance = new Singleton();这句,其实做了如下操作:
memory = allocate(); //1:分配对象的内存空间
initInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
重排后:
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:先设置instance指向刚分配的内存地址
initInstance(memory); //2:再初始化对象
即引用instance指向内存memory时,这段崭新的内存还没有初始化——即,引用instance指向了一个"被部分初始化的对象"。此时,如果另一个线程调用getInstance方法,由于instance已经指向了一块内存空间,从而if条件判为false,方法返回instance引用,用户得到了没有完成初始化的“半个”单例。
解决这个该问题,只需要将instance声明为volatile变量:
private static volatile Singleton instance;
下面是基于保守策略的JMM内存屏障插入策略:
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。
上面简单讲解了volatile关键字的作用和原理,但对volatile的使用过程中很容易出现的一个问题是:
错把volatile变量当做原子变量。
出现这种误解的原因,主要是volatile关键字使变量的读、写具有了“原子性”。然而这种原子性仅限于变量(包括引用)的读和写,无法涵盖变量上的任何操作,即:
基本类型的自增(如count++)等操作不是原子的。
对象的任何非原子成员调用(包括成员变量和成员方法)不是原子的。
如果希望上述操作也具有原子性,那么只能采取锁、原子变量更多的措施。
总结原子性和可见性的区别:
可见性是指“可见”的变量的读操作会立刻从内存刷新最新的值到副本,写操作会立刻把用户内存的副本刷新到主内存。 而原子性指的是多条语句的操作在同一个线程的连续性。