并发编程之AtomicInteger

968 阅读9分钟

什么是原子性呢?

原子性是指某个操作或者一些操作要么都成功,要么都失败,不允许出现因中断而导致的部分成功或部分失败的情况。

那 i++ 是线程安全的吗?

今天我们就从这个问题出发来讲解JAVA的原子操作。

当我们学习JAVA基础的时候就知道了i++操作,那你知道他是线程安全的吗?

在你心中肯定有那么一个人,嗯……不对,是有一个答案!

我们来做一个实验:

@Slf4j
public class Add {

    public static void main(String[] args) {
        AddThread addThread = new AddThread();
        //创建10个线程
        IntStream.range(0, 10).forEach(
                value -> new Thread(addThread).start()
        );
    }

    static class AddThread implements Runnable {
      	//初始化i值
        private int i = 0;

        @Override
        public void run() {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //自增操作
            i++;
            log.info("当前线程:{} ,i值为:{}", Thread.currentThread().getName(), i);
        }
    }
}

执行结果如下:

[Thread-5] INFO com.xiaozhi.simple.Add - 当前线程:Thread-5 ,i值为:4
[Thread-1] INFO com.xiaozhi.simple.Add - 当前线程:Thread-1 ,i值为:3
[Thread-9] INFO com.xiaozhi.simple.Add - 当前线程:Thread-9 ,i值为:8
[Thread-4] INFO com.xiaozhi.simple.Add - 当前线程:Thread-4 ,i值为:2
[Thread-6] INFO com.xiaozhi.simple.Add - 当前线程:Thread-6 ,i值为:5
[Thread-0] INFO com.xiaozhi.simple.Add - 当前线程:Thread-0 ,i值为:2
[Thread-2] INFO com.xiaozhi.simple.Add - 当前线程:Thread-2 ,i值为:2
[Thread-8] INFO com.xiaozhi.simple.Add - 当前线程:Thread-8 ,i值为:7
[Thread-7] INFO com.xiaozhi.simple.Add - 当前线程:Thread-7 ,i值为:6
[Thread-3] INFO com.xiaozhi.simple.Add - 当前线程:Thread-3 ,i值为:3

可以看出部分线程的i值是相同的(例如:Thread-1、Thread-3),则说明出现了线程不安全的情况,那如何解决呢?

相信大家肯定都有自己的解决方案,这里就不贴出详细代码了,代码已经上传码云,文末有链接。

  1. 核心代码使用synchronized修饰
  2. 核心代码使用显示锁Lock包裹
  3. 使用JDK的原子类(AtomicInteger

synchronized、lock、AtomicInteger性能如何呢?

那我们使用上一篇文章所介绍的JMH测量工具进行性能测试 测试代码如下:

@BenchmarkMode(Mode.AverageTime)  //统计平均时间
@OutputTimeUnit(TimeUnit.MICROSECONDS) //单位微秒
@Warmup(iterations = 10) //预热10次
@Measurement(iterations = 10)  //度量10次
public class SynchronizedVsLockVsAtomicInteger {

    @State(Scope.Group)
    public static class SynMonitor {
        private int i = 0;

        public void synInc() {
            synchronized (this) {
                i++;
            }
        }
    }

    @State(Scope.Group)
    public static class LockMonitor {
        private final Lock lock = new ReentrantLock();
        private int i = 0;

        public void lockInc() {
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
            }
        }
    }

    @State(Scope.Group)
    public static class AtomicIntegerMonitor {
        private AtomicInteger i = new AtomicInteger(0);

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

  	//定义基准方法,测试synchronized修饰的性能
    @GroupThreads(10) //线程数量
    @Group("sync") //线程组名称
    @Benchmark
    public void synInc(SynMonitor synMonitor) {
        synMonitor.synInc();
    }

    //定义基准方法,测试显示锁Lock性能
    @GroupThreads(10)
    @Group("lock")
    @Benchmark
    public void lockInc(LockMonitor lockMonitor) {
        lockMonitor.lockInc();
    }

  	//定义基准方法,测试AtomicInteger性能
    @GroupThreads(10)
    @Group("atomic")
    @Benchmark
    public void atomicInc(AtomicIntegerMonitor atomicIntegerMonitor) {
        atomicIntegerMonitor.inc();
    }

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

基准测试执行结果如下:

Benchmark                                        Mode  Cnt  Score   Error  Units
SynchronizedVsLockVsAtomicInteger.atomic         avgt   10  0.150 ± 0.007  us/op
SynchronizedVsLockVsAtomicInteger.lock           avgt   10  0.247 ± 0.005  us/op
SynchronizedVsLockVsAtomicInteger.sync           avgt   10  1.128 ± 0.408  us/op

不难看出其性能顺序为:AtomicInteger > 显示锁Lock > synchronized关键字

仔细的小伙伴肯定注意到了代码中我们配置了StackProfiler.class,那我们来看看线程状态数据:

Secondary result "com.xiaozhi.simple.SynchronizedVsLockVsAtomicInteger.atomic:·stack":
 98.8%         RUNNABLE
  1.2%         WAITING
  
Secondary result "com.xiaozhi.simple.SynchronizedVsLockVsAtomicInteger.lock:·stack":
 78.2%         WAITING
 21.8%         RUNNABLE
 
Secondary result "com.xiaozhi.simple.SynchronizedVsLockVsAtomicInteger.sync:·stack":
 79.9%         BLOCKED
 19.3%         RUNNABLE
  0.8%         WAITING

观察上面线程状态统计数据得知:

AtomicInteger线程的RUNNABLE状态高达了98.8%,并没有BLOCKED状态;而synchronized的BLOCKED状态高达了79.9%;

所以AtomicInteger的性能为什么如此之高? 是不是一下就明白了!

Atomic原子类

以上讲解那么多,也是为了展现出今天主角儿的强大之处。

  1. 那你知道JDK的原子类型有哪些吗?

    我列举几个大家可能在开发过程中会用到的:

    1. AtomicInteger
    2. AtomicLong
    3. AtomicBoolean
    4. AtomicReference
    5. AtomicStampedReference
    6. AtomicArray
    7. AtomicFieldUpdater
  2. 这么多原子类型,分别怎么使用、用于什么场景呢?

    今天我们先对AtomicInteger,AtomicLong进行详细讲解

AtomicInteger

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

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

以下是从源码摘抄出来的:

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

    private static final Unsafe unsafe = Unsafe.getUnsafe();
  
    private static final long valueOffset;
  	//volatile修饰我们的值
    private volatile int value;
}
  1. 创建AtomicInteger

    1、使用无参构造进行创建
    AtomicInteger atomicInteger = new AtomicInteger();
    
    2、使用有参构造进行创建
    AtomicInteger atomicInteger = new AtomicInteger(1234567);
    

    其实无参构造等价于有参构造传入一个0值

  2. Incremental操作

    i ++或者i = i + 1 这样的操作是非原子性的,所以我们可以借助AtomicInteger中提供的原子性Incremental的操作方法

    int getAndIncrement()

    返回当前的value值,并对其进行自增操作

    @Test
    public void getAndIncrement() {
      final AtomicInteger ai = new AtomicInteger(18);
      assert 18 == ai.getAndIncrement();
      assert 19 == ai.get();
    }
    

    int incrementAndGet()

    直接返回自增后的值

    @Test
    public void incrementAndGet() {
      final AtomicInteger ai = new AtomicInteger(18);
      assert 19 == ai.incrementAndGet();
      assert 19 == ai.get();
    }
    
  3. Decremental操作

    i --或者i = i - 1 这样的操作也非原子性的,所以我们可以借助AtomicInteger中提供的原子性Decremental的操作方法

    int getAndDecrement()

    返回当前的value值,并对其进行自减操作

    @Test
    public void getAndDecrement() {
      final AtomicInteger ai = new AtomicInteger(18);
      assert 18 == ai.getAndDecrement();
      assert 17 == ai.get();
    }
    

    int decrementAndGet()

    直接返回自减后的值

    @Test
    public void decrementAndGet() {
      final AtomicInteger ai = new AtomicInteger(18);
      assert 17 == ai.decrementAndGet();
      assert 17 == ai.get();
    }
    
  4. 原子性的更新value值

    boolean compareAndSet(int expect, int update)

    expect代表当前AtomicInteger值,update代表需要设置的值,该方法会返回一个boolean结果

    当expect值和当前值不一致时,会修改失败,返回结果为false

    注意:boolean weekCompareAndSet(int expect, int update)与当前方法完全一样

    @Test
    public void compareAndSet() {
      final AtomicInteger ai = new AtomicInteger(18);
      
      //传入值与ai值不一致
      assert !ai.compareAndSet(123, 24);
      assert 18 == ai.get();
    
      //传入值与ai值一致
      assert ai.compareAndSet(18, 24);
      assert 24 == ai.get();
    }
    

    int getAndAdd(int delta)

    直接返回当前值,然后让value与delta进行相加操作

    该方法实际上是基于自旋+CAS算法实现的原子性操作

    @Test
    public void getAndAdd() {
      final AtomicInteger ai = new AtomicInteger(18);
      assert 18 == ai.getAndAdd(13);
      assert 31 == ai.get();
    }
    

    int addAndGet(int delta);

    将value值与delta值进行相加操作,并直接返回

    @Test
    public void addAndGet() {
      final AtomicInteger ai = new AtomicInteger(18);
      assert 31 == ai.addAndGet(13);
      assert 31 == ai.get();
    }
    
  5. AtomicInteger与函数式接口

    函数式接口是JDK1.8引入的,AtomicInteger也提供了对函数式接口的支持

    int getAndUpdate(IntUnaryOperator updateFunction)

    int updateAndGet(IntUnaryOperator updateFunction)

    @Test
    public void getAndUpdate02() {
      final AtomicInteger ai = new AtomicInteger(18);
      //传入lambda表达式
      assert 18 == ai.getAndUpdate(i -> i * 3);
      assert 54 == ai.get();
    }
    
    @Test
    public void updateAndGet02() {
      final AtomicInteger ai = new AtomicInteger(18);
      //传入lambda表达式
      assert 54 == ai.updateAndGet(i -> i * 3);
      assert 54 == ai.get();
    }
    

    int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction)

    int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction)

    @Test
    public void getAndAccumulate() {
      final AtomicInteger ai = new AtomicInteger(18);
      //使用lambda表达式
      assert 18 == ai.getAndAccumulate(14, Integer::sum);
      assert 32 == ai.get();
    }
    
    @Test
    public void accumulateAndGet() {
      final AtomicInteger ai = new AtomicInteger(18);
      assert 35 == ai.accumulateAndGet(17, Integer::sum);
      assert 35 == ai.get();
    }
    

    当AtomicInteger支持函数式接口后,我们应该感到无比的开心,因为可以自己DIY方法,且以原子性的方式进行执行

    自定义函数式接口如下:

    @Test
    public void getAndAccumulate02() {
      final AtomicInteger ai = new AtomicInteger(18);
      
      assert 18 == ai.getAndAccumulate(14, new IntBinaryOperator() {
        @Override
        public int applyAsInt(int left, int right) {
          assert 18 == left;
          assert 14 == right;
    
          //两值相加
          int temp = left + right;
          //结果扩大7倍进行返回
          return temp * 7;
        }
      });
      
      //获取自定义函数式操作后的值
      assert 224 == ai.get();
    }
    
  6. AtomicInteger还有两个方法

    void set(int value)

    void lazySet(int value)

    这两个方法达到的效果是一样的,直接修改AtomicInteger的value值

    set方法修改被volatile修饰的value值会被强制刷新到主内存中,从而其他线程就可以立即看见,其原理还是volatile关键字底层的内存屏障。

    内存屏障虽然足够轻量,但毕竟还是会带来性能开销。

    在单线程下进行set操作时,就没必要保留这种内存屏障机制了,所以lazySet方法由此诞生!

    JVM开发者对性能的追求真是达到了极致啊! 在此佩服、佩服!

    针对这两个方法的性能测试详情结果,见文末码云代码链接。

AtomicLong

由于AtomicLong和AtomicInteger方法基本一致,我就不再文章中重复的列出讲解。

自上一篇文章JMH发出之后,有朋友说我太长了!

我当时有点蒙,结果是说我文章内容有点长;哎,没办法,为了详细讲解知识,怎么可以走马观花的草草了事呢?

但是太长了你们会受不了,所以这里还是适当精简一下。

内幕

经过上面漫长的过程,我们学习了AtomicInteger的使用。

那现在我们就来大概看看AtomicInteger的内幕吧!

在刚介绍AtomicInteger的时候,我贴出了一小段源码,我们再来看看:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

Unsafe是由C++编写的,内部存在很多大量的汇编CPU指令代码

valueOffset用于存放value的内存地址偏移量

我们来看看compareAndSet方法的源码 ——CAS

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

其中**unsafe.compareAndSwapInt(this, valueOffset, expect, update);**是一个native方法,能保证我们的操作是原子性的,会将传入的值与当前值进行比较,当expect值和当前值不一致时,会修改失败;

addAndGet等方法底层也是这个native操作,但是不会给我们返回失败操作的情况,其底层使用了do……while进行自旋的结果。

AtomicLong底层也有unsafe.compareAndSwapIntLong方法

由于native方法相对底层,反编译比较麻烦,我的知识能力有限,就不继续深入了!

今天对AtomicInteger、AtomicLong学习就到这里,相信大家对这个知识都比较清楚了吧;其他原子类我会在后面的文章中陆续更新。

公众号传送门——《并发编程之AtomicInteger》

码云代码链接如下:

gitee.com/songyanzhi/…

感谢阅读。

祝大家工作愉快、身体健康!