Java原子类之AtomicInteger源码分析

一、简介

AtomicInteger应该是atomic框架中用得最多的原子类了。顾名思义,AtomicIntegerInteger类型的线程安全原子类,可以在应用程序中以原子的方式更新int值。

AtomicInteger的本质:自旋锁 + CAS原子操作。原子操作是多个线程同时执行,确保其是安全的,且并不需要synchronized关键字。

本质上,原子操作严重依赖于CAS,它是由多数现代CPU直接支持的原子指令。这些指令通常比同步块要快。所以在只需要并发修改单个可变变量的情况下,建议优先使用原子类,而不是使用锁机制实现。

二、案例

a++说起为什么使用AtomicIntegerjava并发机制中主要有三个特性需要去考虑,原子性、可见性和有序性。synchronized关键字可以保证可见性和有序性却无法保证原子性。而这个AtomicInteger的作用就是为了保证原子性。我们先看一个例子。

public class TestAtomicInteger {
    //定义一个变量
    private static volatile int a = 0;

    public static void integerAdd() {
        Thread[] threads = new Thread[5];
        //定义5个线程池,每个线程增加10
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(() -> {
                try {
                    for (int j = 0; j < 10; j++) {
                        System.out.println("线程:" + Thread.currentThread().getName()
                                                 + ",结果:" + a++);
                        Thread.sleep(500);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            threads[i].start();
        }
    }

    public static void main(String[] args) {
        integerAdd();
    }
}

在上面的例子中,定义了一个变量a。并且使用了5个线程分别去增加。为了保证可见性和有序性我们使用了volatile关键字对a进行修饰。如果我们第一次接触的话肯定会觉得5个线程,每个线程加10,最后结果一定是50呀。运行结果如下:

...
线程:Thread-0,结果:42
线程:Thread-2,结果:44
线程:Thread-4,结果:45
线程:Thread-1,结果:46

很明显,可能跟你想象的不一样。为什么会出现这个问题呢?这是因为变量a虽然保证了可见性和有序性,但是缺没有保证原子性。其原因我们可以来分析一下。

对于a++的操作,其实可以分解为3个步骤。

(1)从主存中读取a的值
(2)对a进行加1操作
(3)把a重新刷新到主存

volatile可以保证可见性,但是无法保证原子性。volatile修饰的变量a做++操作时,实际的指令包括三个(得到a的值,a+1,将a+1赋值给a),因此可能会出现多个线程交叉执行的结果。有三种方法可以解决:

  1. 使用synchronize修饰a++代码块
// 修饰的变量为a
synchronize(TestAtomicInteger.class) {
    a++;
}

具体请参考Synchronized实现原理

  1. 使用锁ReentrantLock
ReentrantLock reentrantLock = new ReentrantLock();
private volatile a = 0;

public void addA() {
    // 上锁
    reentrantLock.lock();
    try {
        a++;
    } finally {
        // 保证锁的释放
        reentrantLock.unlock();
    }
}
  1. 使用JUC包下的原子类AtomicInteger
public class TestAtomicInteger {

    private static AtomicInteger b = new AtomicInteger();

    public static void AtomicInteger() {
        Thread[] threads = new Thread[5];
        //定义5个线程池,每个线程增加10
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(() -> {
                try {
                    for (int j = 0; j < 10; j++) {
                        System.out.println("线程:" + Thread.currentThread().getName()
                                                   + ",结果:" + b.incrementAndGet());
                        Thread.sleep(500);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            threads[i].start();
        }
    }
}

三、源码分析

方法声明 描述
int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) 使用IntBinaryOperator 对当前值和x进行计算,
并更新当前值,返回计算后的新值
int addAndGet(int delta) 以原子方式将给定值与当前值相加,返回相加
后的新值
boolean compareAndSet(int expect, int update) 如果当前值 == expect,则以原子方式将该值设置
为给定的更新值(update)
int decrementAndGet() 以原子方式将当前值减 1,返回新值
int get() 获取当前值
int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) 使用IntBinaryOperator 对当前值和x进行计算,
并更新当前值,返回计算前的旧值
int getAndAdd(int delta) 以原子方式将给定值与当前值相加,返回旧值
int getAndDecrement() 以原子方式将当前值减 1,返回旧值
int getAndIncrement() 以原子方式将当前值加 1,返回旧值
int getAndSet(int newValue) 以原子方式设置为给定值,并返回旧值
int getAndUpdate(IntUnaryOperator updateFunction) 使用IntBinaryOperator 对当前值进行计算,
并更新当前值,返回计算前的旧值
int incrementAndGet() 以原子方式将当前值加 1,返回新值
void lazySet(int newValue) 设置为给定值,但不保证值的改变被其他线程立即看到
void set(int newValue) 设置为给定值
int updateAndGet(IntUnaryOperator updateFunction) 使用IntBinaryOperator 对当前值进行计算,
并更新当前值,返回计算后的新值
boolean weakCompareAndSet(int expect, int update) 无法保证除操作目标外的其他变量的执行顺序(编译器
和处理器为了优化程序性能而对指令序列进行重新排序),
同时也无法保证这些变量的可见性。

3.1 属性

ActomicInteger的内部属性可以看到,它是依赖Unsafe的一些底层能力,进行底层操作,以volatilevalue字段,记录数值,以保证可见性。以下是AtomicInteger的部分源码

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    // 获取Unsafe的实例
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // 标识value字段的偏移量
    private static final long valueOffset;

    // 静态代码块,通过unsafe获取value的偏移量
    static {
      try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }

    // 存储int类型值的地方,使用volatile修饰
    private volatile int value;

    /**
     * 获取value
     *
     * @return 当前的value
     */
    public final int get() {
        return value;
    }

    /**
     * 立即修改或者设置value
     * set操作能够保证可见性,避免指令重排
     *
     * @param newValue 设置的新值
     */
    public final void set(int newValue) {
        value = newValue;
    }
    
    //...
}

Unsafe会利用value字段的内存地址偏移,直接完成操作。

3.2 构造方法

AtomicInteger提供了两个构造器,使用默认构造器时,内部int类型的value值为0

/**
 * 创建一个AtomicInteger,初始值value为initialValue
 */
public AtomicInteger(int initialValue) {
    value = initialValue;
}

/**
 * 创建一个AtomicInteger,初始值value为0
 */
public AtomicInteger() {
}

AtomicInteger类的内部并不复杂,所有的操作都针对内部的int值——value,并通过Unsafe类来实现线程安全的CAS操作。

3.3 compareAndSet方法

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

/**
 * Unsafe中的方法
 * @param o         操作的对象
 * @param offset    对象中字段的偏移量
 * @param expected  原来的值,即期望的值
 * @param x         要修改的值
 */
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);

调用Unsafe.compareAndSwapInt()方法实现,这是一个native方法,底层是使用C/C++写的,主要是调用CPUCAS指令来实现,它能够保证只有当对应偏移量处的字段值是期望值时才更新,即类似下面这样的两步操作:

if(value == expect) {
    value = newValue;
}

通过CPUCAS指令可以保证这两步操作是一个整体,也就不会出现多线程环境中可能比较的时候value值是a,而到真正赋值的时候value值可能已经变成b了的问题。

3.4 getAndIncrement()方法

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

/**
 * Unsafe中的方法
 * @param o      操作的对象
 * @param offset 对象中字段的偏移量
 * @param delta  要增加的值
 */
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}

getAndIncrement()方法底层是调用的UnsafegetAndAddInt()方法,可以看到它是先获取当前的值,然后再调用compareAndSwapInt()尝试更新对应偏移量处的值,如果成功了就跳出循环,如果不成功就再重新尝试,直到成功为止,这可不就是(CAS+自旋)的乐观锁机制么

3.5 incrementAndGet方法

上面案例中使用了AtomicIntegerincrementAndGet方法,以原子的操作对int值进行自增,该段程序执行的最终结果为50(5个线程,每个线程对AtomicInteger增加10),如果不使用AtomicInteger,使用原始的intInteger,最终结果值可能会小于50(并发时读到了过时的数据或存在值覆盖的问题)。

我们来看下incrementAndGet内部:

/**
 * 将value加1,并返回新值,原子操作
 *
 * @return value加1后的值
 */
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

内部调用了Unsafe类的getAndAddInt方法,以原子方式将value值增加1,然后返回增加前的原始值。

注意,上述是JDK1.8的实现,在JDK1.8之前,上述方法采用了自旋+CAS操作的方式:

public final int getAndIncrement() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return current;
    }
}

3.6 getAndIncrement方法

/**
 * 将value加1,然后返回旧值,原子操作
 *
 * @return 旧值
 */
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

getAndIncrement是需要明确返回值的,因此getAndAddInt实现是需要失败重试,最后拿到返回值的。

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

CompareAndset这样的直接返回Boolean值,不需要失败重试。

3.7 lazySet方法

AtomicInteger中有一个比较特殊的方法——lazySetlazySet方法是set方法的不可见版本。什么意思呢?

/**
 * 不会立即修改或者设置值(但是最终会)
 * lazySet不能保证可见性,可能会发生指令重排,但是性能比set高
 *
 * @param newValue 要设置的值
 * @since 1.6
 */
public final void lazySet(int newValue) {
    unsafe.putOrderedInt(this, valueOffset, newValue);
}

我们知道通过volatile修饰的变量,可以保证在多处理器环境下的“可见性”。也就是说当一个线程修改一个共享变量时,其它线程能立即读到这个修改的值。volatile的实现最终是加了内存屏障:

  1. 保证写volatile变量会强制把CPU写缓存区的数据刷新到内存
  2. volatile变量时,使缓存失效,强制从内存中读取最新的值
  3. 由于内存屏障的存在,volatile变量还能阻止重排序

lazySet内部调用了Unsafe类的putOrderedInt方法,通过该方法对共享变量值的改变,不一定能被其他线程立即看到。也就是说以普通变量的操作方式来写变量。

为什么会有这种奇怪方法?什么情况下需要使用lazySet呢?

考虑下面这样一个场景:

private AtomicInteger ai = new AtomicInteger();
lock.lock();
try {
    // ai.set(1);
} finally {
    lock.unlock();
}

由于锁的存在:

  • lock() 方法获取锁时,和volatile变量的读操作一样,会强制使CPU缓存失效,强制从内存读取变量。
  • unlock() 方法释放锁时,和volatile变量的写操作一样,会强制刷新CPU写缓冲区,把缓存数据写到主内存

所以,上述ai.set(1)可以用ai.lazySet(1)方法替换:由锁来保证共享变量的可见性,以设置普通变量的方式来修改共享变量,减少不必要的内存屏障,从而提高程序执行的效率。

3.8 其他方法

AtomicInteger中的其它方法几乎都是类似的,最终会调用到UnsafecompareAndSwapInt()来保证对value值更新的原子性。

/**
 * 设置新值,并且返回旧值(原子操作)
 *
 * @param newValue 新值
 * @return 旧值
 */
public final int getAndSet(int newValue) {
    return unsafe.getAndSetInt(this, valueOffset, newValue);
}

/**
 * 原子操作,CAS,当value和expect相等时,才将value修改为update
 *
 * @param expect 期望value的值
 * @param update 要修改的值
 * @return true:value和expect相等,且完成修改;false:value和expect不相等
 */
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

/**
 * 和compareAndSet相同功能,都是使用CAS原子操作,但是无法保证多个线程CAS的有序性
 *
 * @param expect 期望的value值
 * @param update 更改后的值
 * @return 操作是否成功
 */
public final boolean weakCompareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

/**
 * 将value减1,然后返回旧值,原子操作
 *
 * @return 旧值
 */
public final int getAndDecrement() {
    return unsafe.getAndAddInt(this, valueOffset, -1);
}

/**
 * 对value增加指定值,然后返回旧值,原子操作
 *
 * @param delta 要加的值
 * @return 旧值
 */
public final int getAndAdd(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta);
}

/**
 * 将value减1,并返回新值,原子操作
 *
 * @return value减1后的新值
 */
public final int decrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}

/**
 * 对value增加值,然后返回新值,原子操作
 *
 * @param delta 新增的值
 * @return value新增后的值
 */
public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

/**
 * 传入Function,对value进行操作(同样使用CAS保证原子性),会一直重试直到成功才中断,然后返回旧值
 *
 * @param updateFunction a side-effect-free function
 * @return 旧值
 * @since 1.8
 */
public final int getAndUpdate(IntUnaryOperator updateFunction) {
    int prev, next;
    do {
        prev = get();
        next = updateFunction.applyAsInt(prev);
    } while (!compareAndSet(prev, next));
    return prev;
}

/**
 * 传入Function,对value进行操作(同样使用CAS保证原子性),会一直重试直到成功才中断,然后返回新值
 *
 * @param updateFunction a side-effect-free function
 * @return the updated value
 * @since 1.8
 */
public final int updateAndGet(IntUnaryOperator updateFunction) {
    int prev, next;
    do {
        prev = get();
        next = updateFunction.applyAsInt(prev);
        // 一直重试,直到CAS操作成功
    } while (!compareAndSet(prev, next));
    return next;
}

/**
 * 阻塞式更新,并对prev和x,进行二元运算操作
 *
 * @param x                   the update value
 * @param accumulatorFunction a side-effect-free function of two arguments
 * @return the previous value
 * @since 1.8
 */
public final int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) {
    int prev, next;
    do {
        prev = get();
        next = accumulatorFunction.applyAsInt(prev, x);
    } while (!compareAndSet(prev, next));
    return prev;
}

/**
 * Atomically updates the current value with the results of
 * applying the given function to the current and given values,
 * returning the updated value. The function should be
 * side-effect-free, since it may be re-applied when attempted
 * updates fail due to contention among threads.  The function
 * is applied with the current value as its first argument,
 * and the given update as the second argument.
 *
 * @param x                   the update value
 * @param accumulatorFunction a side-effect-free function of two arguments
 * @return the updated value
 * @since 1.8
 */
public final int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) {
    int prev, next;
    do {
        prev = get();
        next = accumulatorFunction.applyAsInt(prev, x);
    } while (!compareAndSet(prev, next));
    return next;
}

public String toString() {
    return Integer.toString(get());
}

public int intValue() {
    return get();
}

public long longValue() {
    return (long) get();
}

public float floatValue() {
    return (float) get();
}

public double doubleValue() {
    return (double) get();
}

四、拓展

4.1 为什么使用CAS而不用Synchronized呢?

synchronized加锁同一时间段只允许一个线程访问,能够保证一致性但是并发性下降。而是用CAS算法使用do-while不断判断而没有加锁(实际是一个自旋锁),保证一致性和并发性。

五、总结

  1. AtomicInteger中维护了一个使用volatile修饰的变量value,保证可见性;
  2. AtomicInteger中的主要方法最终几乎都会调用到UnsafecompareAndSwapInt()方法保证对变量修改的原子性。

参考文章

posted @ 2022-06-24 16:51  夏尔_717  阅读(165)  评论(0编辑  收藏  举报