Java 原子变量分析

 

在编码过程中, 我们难免会遇见对共享变量的读取或者更新操作, 而这些共享变量是可以被多个线程同时访问, 我们可以使用锁来保证这些操作的有序地进行串行的访问, 但是这样性能和可伸缩性将大大降低, 这就需要我们今天的主角, 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给无状态更新带来性能负担.