原子类型:AtomicInteger认识

1 原子类型介绍

无论是基本数据类型还是引用类型的变量,只要被volatile关键字修饰,从JMM(Java Memory Model)的角度分析,该变量就具备了有序性和可见性这两个语义特质,但是它还是无法保证原子性。那么,什么是原子性呢?原子性是指某个操作或者一系列操作要么都成功,要么都失败,不允许出现因中断而导致的部分成功或部分失败的情况。

比如,对int类型的加法操作就是原子性的,如x+1。但是我们在使用的过程中往往会将x+1的结果赋予另一个变量甚至是x变量本身,即进行x=x+1或者x++这样的操作,而这样的语句事实上是由若干个原子性的操作组合而来的,因此它们就不具备原子性。

1)将主内存中x的值读取到CPU Cache中。
2)对x进行加一运算。
3)将结果写回到CPU Cache中。
4)将x的值刷新到主内存中。

参考:深入volatile关键字(二)volatile特性,使用场景,volatile和synchronized区别
在JDK 1.5版本之前,为了确保在多线程下对某基本数据类型或者引用数据类型运算的原子性,必须依赖于关键字synchronized。

但是自JDK 1.5版本以后这一情况发生了改变,JDK官方为开发者提供了原子类型的工具集,比如AtomicInteger、AtomicBoolean等,这些原子类型都是Lock-Free及线程安全的;实际上在Java推出原子工具集之前,很多第三方库也提供了类似的解决方案,比如Google的Guava,甚至于JDK自身的原子类工具集也是来自Doug Lea的个人项目

2 AtomicInteger性能对比(了解)

先对比一下被synchronized关键字和显式锁Lock进行同步的int类型和AtomicInteger类型在多线程场景下的性能表现:

测试对int的++操作:

// 度量批次为10次
@Measurement(iterations = 10)
// 预热批次为10次
@Warmup(iterations = 10)
// 采用平均响应时间作为度量方式
@BenchmarkMode(Mode.AverageTime)
// 时间单位为微秒
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class SynchronizedVsLockVsAtomicInteger {

    /**
     * IntMonitor设置为线程组共享
     */
    @State(Scope.Group)
    public static class IntMonitor {
        // 共享资源
        private int x;
        private final Lock lock = new ReentrantLock();

        /**
         * 使用显式锁进行共享资源同步
         */
        public void lockInt() {
            lock.lock();
            try {
                x++;
            } finally {
                lock.unlock();
            }
        }

        /**
         * 使用synchronized关键字进行共享资源同步
         */
        public void synInc() {
            synchronized (this) {
                x++;
            }
        }
    }

    /**
     * 直接采用AtomicInteger
     */
    @State(Scope.Group)
    public static class AtomicIntegerMonitor {
        private AtomicInteger x = new AtomicInteger();

        public void inc() {
            x.incrementAndGet();
        }
    }

    @Benchmark
    @GroupThreads(10) // 搞10个线程测试synInc
    @Group("sync") // 线程组名sync
    public void syncInc(IntMonitor intMonitor) {
        intMonitor.synInc();
    }

    @Benchmark
    @GroupThreads(10) // 搞10个线程测试lockInt
    @Group("lock") // 线程组名lock
    public void lockInt(IntMonitor intMonitor) {
        intMonitor.lockInt();
    }

    @Benchmark
    @GroupThreads(10) // 搞10个线程测试使用原子类型
    @Group("atom") // 线程组名atom
    public void atom(AtomicIntegerMonitor intMonitor) {
        intMonitor.inc();
    }

    public static void main(String[] args) throws RunnerException {
        Options opts = new OptionsBuilder()
                .include(SynchronizedVsLockVsAtomicInteger.class.getSimpleName())
                .forks(1)
                // 超时时间
                .timeout(TimeValue.seconds(10))
                .addProfiler(StackProfiler.class)
                .build();
        new Runner(opts).run();
    }
}

这里使用的是JMH框架,关于这个框架的使用可参考:JMH专栏

基准测试结果输出:

Benchmark                               Mode  Cnt  Score   Error  Units
SynchronizedVsLockVsAtomicInteger.atom  avgt   10  0.268 ± 0.011  us/op
SynchronizedVsLockVsAtomicInteger.lock  avgt   10  0.344 ± 0.006  us/op
SynchronizedVsLockVsAtomicInteger.sync  avgt   10  0.450 ± 0.026  us/op

从基准测试的结果不难看出,AtomicInteger的表现更优,AtomicInteger>显式锁Lock>synchronized关键字

在该基准测试的配置中,我们增加了StackProfiler,因此很容易窥探出AtomicInteger表现优异的原因:

原子类型:
Secondary result "study.wyy.juc.atom.SynchronizedVsLockVsAtomicInteger.atom:·stack":
Stack profiler:

....[Thread state distributions]....................................................................
 98.7%         RUNNABLE
  1.3%         WAITING

使用显式锁
Secondary result "study.wyy.juc.atom.SynchronizedVsLockVsAtomicInteger.lock:·stack":
Stack profiler:

....[Thread state distributions]....................................................................
 78.9%         WAITING
 21.1%         RUNNABLE

使用Synchronized:
Secondary result "study.wyy.juc.atom.SynchronizedVsLockVsAtomicInteger.sync:·stack":
Stack profiler:

....[Thread state distributions]....................................................................
 80.6%         BLOCKED
 18.8%         RUNNABLE
  0.6%         WAITING

AtomicInteger线程的RUNNABLE状态高达98.7%,并且没有BLOCKED状态,而synchronized关键字则相反,BLOCKED状态高达 80.6%,因此AtomicInteger高性能的表现也就不足为奇了。

3 AtomicInteger基本用法

与int的引用类型Integer继承Number类一样,AtomicInteger也是Number类的一个子类,除此之外,AtomicInteger还提供了很多原子性的操作方法

在AtomicInteger的内部有一个被volatile关键字修饰的成员变量value,实际上,AtomicInteger所提供的所有方法主要都是针对该变量value进行的操作。

 private volatile int value;

3.1 AtomicInteger的创建(构造方法)

// 创建AtomicInteger的初始值为0。
public AtomicInteger();
// 创建AtomicInteger并且指定初始值,无参的AtomicInteger对象创建等价AtomicInteger(0)。
public AtomicInteger(int initialValue);

3.2 AtomicInteger的加操作

x++或者x=x+1这样的操作是非原子性的,要想使其具备原子性的特性,我们可以借助AtomicInteger中提供的原子性Incremental的操作方法。

// 返回当前int类型的value值,然后对value进行自增运算,该操作方法能够确保对value的原子性增量操作
int getAndIncrement()
直接返回自增后的结果,该操作方法能够确保对value的原子性增量操作。
int incrementAndGet()
public static void main(String[] args) {
    AtomicInteger atomicInteger = new AtomicInteger(10);
    // 返回初始值10,在进行+1,所以输出10,此时value为11 
    System.out.println(atomicInteger.getAndIncrement()); // 10
    // value为11,先进行+1操作变为12,再返回,所以输出12
    System.out.println(atomicInteger.incrementAndGet()); // 12
}

3.3 AtomicInteger的减操作

x–或者x=x-1这样的自减操作同样也是非原子性的,要想使其具备原子性的特性,我们可以借助AtomicInteger中提供的原子性Decremental的操作方法。

// 返回当前int类型的value值,然后对value进行自减运算
int getAndDecrement();
直接返回自减后的结果
int decrementAndGet();
 public static void main(String[] args) {
     AtomicInteger atomicInteger = new AtomicInteger(10);
     System.out.println(atomicInteger.getAndDecrement()); // 10
     System.out.println(atomicInteger.decrementAndGet()); // 8
 }

3.4 原子性地更新value值

  1. compareAndSet和weakCompareAndSet
/**
原子性地更新AtomicInteger的值,其中expect代表当前的AtomicInteger数值,update则是需要设置的新值,
该方法会返回一个boolean的结果:当expect和AtomicInteger的当前值不相等时,修改会失败,返回值为false;
若修改成功则会返回true。
**/
boolean compareAndSet(int expect, int update)
public static void main(String[] args) {
    AtomicInteger atomicInteger = new AtomicInteger(10);
    // 当前值是10 ,所以会修改成功,改为11
    System.out.println(atomicInteger.compareAndSet(10,11)); // true
    System.out.println(atomicInteger.get()); // 11
    // 当前值是11, 不是110,所以修改失败
    System.out.println(atomicInteger.compareAndSet(110,100)); // false
    System.out.println(atomicInteger.get()); //  11
}
// 目前版本JDK中的该方法与compareAndSet完全一样,源码如下所示。
boolean weakCompareAndSet(int expect, int update);

这两个方法的源码:

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

两个方法的实现完全一样,那么为什么要有这两个方法呢?其实在JDK 1.6版本以前双方的实现是存在差异的,compareAndSet方法的底层主要是针对Intel x86架构下的CPU指令CAS:cmpxchg(sparc-TSO,ia64的CPU架构也支持),但是ARM CPU架构下的类似指令为LL/SC:ldrex/strex(ARM架构下的CPU主要应用于当下的移动互联网设备,比如在智能手机终端设备中,高通骁龙、华为麒麟等系列都是基于ARM架构和指令集下的CPU产品),或许在运行Android的JVM设备上这两个方法底层存在着差异。

  1. getAndAdd和addAndGet
// int getAndAdd(int delta):原子性地更新AtomicInteger 的value值,
// 更新后的value为value和delta之和,方法的返回值为value的前一个值,
// 该方法实际上是基于自旋+CAS算法实现的(Compare And Swap)原子性操作。
int getAndAdd(int delta)
// 该方法与getAndAdd(int delta)一样,也是原子性地更新AtomicInteger的value值,更新后的结果value为value和delta之和,
// 但是该方法会立即返回更新后的value值。
int addAndGet(int delta)
public static void main(String[] args) {
    AtomicInteger atomicInteger = new AtomicInteger(10);
    int res = atomicInteger.getAndAdd(3);
    System.out.println(res); // 10
    System.out.println(atomicInteger.get()); // 13

    System.out.println(atomicInteger.addAndGet(5)); // 18
    System.out.println(atomicInteger.get()); // 18
}

3.5 AtomicInteger与函数式接口

自JDK1.8增加了函数式接口之后,AtomicInteger也提供了对函数式接口的支持

// 原子性地更新AtomicInteger的值,方法入参为IntUnaryOperator接口,
// 返回值为value更新之前的值。
int getAndUpdate(IntUnaryOperator updateFunction)
// 原子性地更新AtomicInteger的值,方法入参为IntUnaryOperator接口,
// 该方法会立即返回更新后的value值。
int updateAndGet(IntUnaryOperator updateFunction)

// 原子性地更新AtomicInteger的值,方法入参为IntBinaryOperator接口和delta值x,返回值为value更新之前的值。
int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction)

// 该方法与getAndAccumulate类似,只不过会立即返回AtomicInteger的更新值。
int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction)

IntUnaryOperator 接口:IntUnaryOperator为函数式接口,有且仅有一个接口方法(非静态,非default),接口方法的返回值即AtomicInteger被更新后的value的最新值。

@FunctionalInterface
public interface IntUnaryOperator {
    // 入参为被操作数,对应于AtomicInteger的当前value值
    int applyAsInt(int operand);
}

IntBinaryOperator :

@FunctionalInterface
public interface IntBinaryOperator {
    // 该接口在getAndAccumulate方法中,
    // left为AtomicInteger value的当前值,    
    // right为delta值,返回值将被用于更新AtomicInteger的value值
    int applyAsInt(int left, int right);
}

eg:

 public static void main(String[] args) {
     AtomicInteger atomicInteger = new AtomicInteger(10);
     int andUpdate = atomicInteger.getAndUpdate(x -> x + 2);
     // 返回值为value更新之前的值。
     System.out.println(andUpdate); // 10
     System.out.println(atomicInteger.get()); // 12
     // 该方法会立即返回更新后的value值。
     int res = atomicInteger.updateAndGet(x -> x + 5);
     System.out.println(res); // 17
 }
public static void main(String[] args) {
    AtomicInteger atomicInteger = new AtomicInteger(10);
    int res = atomicInteger.getAndAccumulate(3, new IntBinaryOperator() {
        @Override
        public int applyAsInt(int left, int right) {
            // left为AtomicInteger value的当前值,
            System.out.println("left: " + left); // 10
            // right为delta值,返回值将被用于更新AtomicInteger的value值
            System.out.println("right: " + right); // 3
            return left + right;
        }
    });
    System.out.println(res); // 10
    System.out.println(atomicInteger.get()); // 13

    // 使用lambda简化
    // atomicInteger.getAndAccumulate(3, (x, y) -> x + y);
    // 再次简化:
    // atomicInteger.getAndAccumulate(3, Integer::sum);
    
}

3.6 其他方法

3.6.1 set方法和lazySet
// 为AtomicInteger的value设置一个新值
//我们知道在AtomicInteger中有一个被volatile关键字修饰的value成员属性,
// 因此调用set方法为value设置新值后其他线程就会立即看见。
void set(int newValue)

/**
set方法修改被volatile关键字修饰的value值会被强制刷新到主内存中,从而立即被其他线程看到
这一切都应该归功于volatile关键字底层的内存屏障。内存屏障虽然足够轻量,但是毕竟还是会带来性能上的开销
比如,在单线程中对AtomicInteger的value进行修改时没有必要保留内存屏障,而value又是被volatile关键字修饰的,这似乎是无法调和的矛盾
好追求性能极致的JVM开发者们早就考虑到了这一点,lazySet方法的作用正在于此
**/
void lazySet(int newValue)
3.6.2 get方法
// 返回AtomicInteger的value当前值。
int get()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值