在编码过程中, 我们难免会遇见对共享变量的读取或者更新操作, 而这些共享变量是可以被多个线程同时访问, 我们可以使用锁来保证这些操作的有序地进行串行的访问, 但是这样性能和可伸缩性将大大降低, 这就需要我们今天的主角, Java 中的原子变量.
Java原子变量的主要机制
- volatile保证其内部字段的可见性, 有序性.
- 通过CAS机器级指令实现原子性的更新操作. 如果你对CAS机制不了解, 推荐看下这篇文章Java CAS原理分析
分析源码 (AtomicInteger)
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
valueOffset是value字段的偏移量, 这里的offset,处于不同的操作系统是有差别的, 不是value的准确内存地址, 首先它是一个静态变量就确定它不可能是准确地址, 其次static的执行位序也确定了他不会和实例的地址有关. 这里我们找一个方法来看下.
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
这里通过this传入了实例的引用, 与偏移量配合, 完成这个CAS操作. 至于具体的unsafe的代码, 这里的compareAndSwapInt()是一个JNI方法, 我在其他文章说过.
其他原子变量
至于其他原子变量的更新操作, 和AtomicInteger的处理思路是近乎一致的, 通过Unsafe直接或者间接提供的CAS操作来完成 AtomicReference的一个举例
public final void set(V newValue) {
value = newValue;
}
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
这里不免有一个疑问, 可能会觉得有了set(), 还要compareAndSet()方法有什么用呢? 我的理解是:set()方法是无状态的, 由于 value本身就是volitile, 保证了每次更新的可见性, 但是仅仅拥有可见性是不行的, 想一下, 其实跟volitile i进行i++会造成错误一样, 如果你的逻辑操作是有状态依赖的话, 就要用compareAndSet(), 如果没有状态依赖, 那么直接set()即可
set()与lazySet()
拿AtomicInteger的源码来分析
/**
* Sets to the given value.
*
* @param newValue the new value
*/
public final void set(int newValue) {
value = newValue;
}
/**
* Eventually sets to the given value.
*
* @param newValue the new value
* @since 1.6
*/
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
关于lazySet(), javadoc上并没有详细描述, 跟注释上一样, 也是 Eventually sets to the given value. 也就是最终保证该字段会设置成该值, 但具体的时间却不确定, 有点future的感觉.
我在stackoverflow 找到一个解答, atomicinteger-lazyset-vs-set, lazySet()的内存语义是保证写入不会与任何先前的写入重排序, 但可以与后续的操作重排序, 重排序之后, 执行之前对于其他线程是不可见的.
回答中主要的应用场景是垃圾收集场景, 设置null, 保证该操作最终将会被设置为null, 然后被垃圾回收机制回收. 因为volatile-wirte成本相对较高, 所以对于一些非及时操作, lazySet()方法可以更高效的执行. 最终最终的目的, 还是落实到了性能上, 避免了volatile-write给无状态更新带来性能负担.