其他分享
首页 > 其他分享> > atomic框架:AtomicInteger

atomic框架:AtomicInteger

作者:互联网

一、为什么使用AtomicInteger

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();
        }
    }
}

二、AtomicInteger简介

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

2.1 创建AtomicInteger对象

先来看下AtomicInteger对象的创建。AtomicInteger提供了两个构造器,使用默认构造器时,内部int类型的 value值为0

private volatile int value;

public AtomicInteger(int initialValue) {
    value = initialValue;
}

public AtomicInteger() {
}

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

2.2 AtomicInteger的使用

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

我们来看下incrementAndGet内部:

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;
    }
}

2.3 AtomicInteger的特殊方法说明

AtomicInteger中有一个比较特殊的方法——lazySet

public final void lazySet(int newValue) {
    unsafe.putOrderedInt(this, valueOffset, newValue);
}

lazySet方法是set方法的不可见版本。什么意思呢?

我们知道通过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();
}

由于锁的存在:

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

三、接口说明

方法声明 描述
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) weakCompareAndSet无法保证除操作目标外的其他变量的执行顺序( 编译器和处理器为了优化程序性能而对指令序列进行重新排序 ),同时也无法保证这些变量的可见性。

四、源码

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
    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;
    ......
}

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

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
 
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是需要明确返回值的,因此getAndAddInt实现是需要失败重试,最后拿到返回值的。

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

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

五、其它原子类

AtomicInteger类似的原子类还有AtomicBooleanAtomicLong,底层都是通过Unsafe类做CAS操作,来原子的更新状态值。可以参考Oracle官方文档:https://docs.oracle.com/javase/8/docs/api

参考文章

标签:Thread,框架,int,AtomicInteger,volatile,atomic,线程,public
来源: https://www.cnblogs.com/ciel717/p/16190549.html