Java多线程进阶(十三)—— J.U.C之atomic框架:AtomicInteger

7.jpeg

本文首发于一世流云的专栏: https://segmentfault.com/blog...

1、AtomicInteger简介

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

1. 建立AtomicInteger对象

先来看下AtomicInteger对象的建立。segmentfault

AtomicInteger提供了两个构造器,使用默认构造器时,内部int类型的value值为0:
AtomicInteger atomicInt = new AtomicInteger();api

AtomicInteger类的内部并不复杂,全部的操做都针对内部的int值——value,并经过Unsafe类来实现线程安全的CAS操做:
clipboard.png缓存

2. AtomicInteger的使用

来看下面这个示例程序:安全

public class Main {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger ai = new AtomicInteger();

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(new Accumlator(ai), "thread-" + i);
            list.add(t);
            t.start();
        }

        for (Thread t : list) {
            t.join();
        }

        System.out.println(ai.get());
    }

    static class Accumlator implements Runnable {
        private AtomicInteger ai;

        Accumlator(AtomicInteger ai) {
            this.ai = ai;
        }

        @Override
        public void run() {
            for (int i = 0, len = 1000; i < len; i++) {
                ai.incrementAndGet();
            }
        }
    }
}

上述代码使用了AtomicInteger的incrementAndGet方法,以原子的操做对int值进行自增,该段程序执行的最终结果为10000(10个线程,每一个线程对AtomicInteger增长1000),若是不使用AtomicInteger,使用原始的int或Integer,最终结果值可能会小于10000(并发时读到了过期的数据或存在值覆盖的问题)。并发

咱们来看下incrementAndGet内部:
clipboard.pngoracle

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

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

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

3. AtomicInteger的特殊方法说明

AtomicInteger中有一个比较特殊的方法——lazySet
clipboard.png性能

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

因为锁的存在:

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

因此,上述ai.set(1)能够用ai.lazySet(1)方法替换:

由锁来保证共享变量的可见性,以设置普通变量的方式来修改共享变量,减小没必要要的内存屏障,从而提升程序执行的效率。

2、类/接口说明

类声明

clipboard.png

构造器

clipboard.png

接口声明

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

3、其它原子类

AtomicInteger相似的原子类还有AtomicBooleanAtomicLong,底层都是经过Unsafe类作CAS操做,来原子的更新状态值。能够参考Oracle官方文档:https://docs.oracle.com/javas...,再也不赘述。

相关文章
相关标签/搜索