并发编程学习笔记(二十九、AtomicInteger)

目录:

  • 原子操作是什么、什么是AtomicInteger
  • JUC提供的原子类
  • AtomicInteger的使用
  • AtomicInteger源码简述

原子操作是什么、什么是AtomicInteger

原子操作,是一种不会被线程调度机制所打断的操作,这种操作一旦开始,就会一直运行到结束,中间不会有任何上下文的切换

原子操作的核心特征就是将一次操作视为一个整体,这个操作可以是一个步骤,也可以是多个步骤;需要注意的是这个操作的顺序不可以被打乱,也不能被切割的只执行其中的某个部分

AtomicInteger又是什么呢,它其实就是JUC提供的一个原子性更新int类型的一个类

JUC提供的原子类

JUC提供的原子类可分为四大类:

1、原子更新基本类型引用类型

  • AtomicInteger:原子更新int类型。
  • AtomicIntegerLong:原子更新long类型。
  • AtomicBoolean:原子更新boolean类型。
  • AtomicReference:原子更新引用类型,通过泛型指定操作的类
  • AtomicMarkableReference:原子更新引用类型,内部通过Pari承载引用对象是否被更新过的标记避免了BAB问题
  • AtomicStampedeReference:原子更新引用类型,内部通过Pari承载引用对象更新的邮戳避免了BAB问题

2、原子更新数组中的元素:可更新数组中指定索引位置的元素。

  • AtomicIntegerArray:更新int类型数组的元素。
  • AtomicLongArray:更新long类型数组的元素。
  • AtomicReferenceArray:更新Object类型数组的元素。

3、原子更新对象中的字段可更对象中指定字段名称的字段。

  • AtomicIntegerFieldUpdater:原子更新对象中int类型的字段。
  • AtomicLongFieldUpdater:原子更新对象中long类型的字段。
  • AtomicReferenceFieldUpdater:原子更新对象中引用类型的字段。

4、高性能原子类:高性能原子类,是Java8中增加的原子类,它们使用分段的思想,把不同的线程hash到不同的段上去更新,最后再把这些段的值相加得到最终的值。

  • Striped64:下面四个类的父类。
  • LongAccumulator:long类型的聚合器,用于计算各种聚合操作,包括加乘等。
  • LongAdder:LongAccumulator的特例,只用于做加法计算,且从0开始。
  • DoubleAccumulator:double类型的聚合器,用于计算各种聚合操作,包括加乘等。
  • DoubleAdder:DoubleAccumulator的特例,只用于做加法计算,且从0开始。

AtomicInteger的使用

首先在说AtomicInteger用法前我们先来看一段demo。

 1 public class Counter {
 2 
 3     private volatile static int count = 0;
 4 
 5     public void addCount() {
 6         count++;
 7     }
 8 
 9 //  getter and setter
10 
11     public int getCount() {
12         return count;
13     }
14 
15 }
 1 public class AtomicIntegerDemo {
 2 
 3     public static void main(String[] args) throws InterruptedException {
 4         test1();
 5     }
 6 
 7     private static void test1() throws InterruptedException {
 8         Counter counter = new Counter();
 9         // 创建10个线程,每个线程对count累加1000次
10         for (int i = 0; i < 10; i++) {
11             new Thread(() -> {
12                 for (int j = 0; j < 1000; j++) {
13                     counter.addCount();
14                 }
15             }).start();
16         }
17         Thread.sleep(1000);
18         System.out.println("count = " + counter.getCount());
19     }
20 
21 }

上面这段代码中,我们创建了10个线程,且每个线程对count累加1000次。

那么你可能回想最终的结果肯定是10000啊,10 * 1000,简单!

其实结果不尽然,因为count++这个操作时非原子性的,它其实分为三个步骤:

  • 从内存中读取count的值。
  • 对count值加1。
  • 再将加1后的结果写到内存中。

所以最后得出的结果一定是小于等于10000的,不信你可以试试。

——————————————————————————————————————————————————————————————————————

那我就是想让10个线程去跑,就是想让结果为10000,那我们该怎么办么,你可以使用JUC提供的操作int类型的原子类AtomicInteger

 1 public static void main(String[] args) throws InterruptedException {
 2     AtomicInteger count = new AtomicInteger();
 3     // 创建10个线程,每个线程对count累加1000次
 4     for (int i = 0; i < 10; i++) {
 5         new Thread(() -> {
 6             for (int j = 0; j < 1000; j++) {
 7                 count.incrementAndGet();
 8             }
 9         }).start();
10     }
11     Thread.sleep(1000);
12     System.out.println("count = " + count.get());
13 }

你只需要使用AtomicInteger提供的原子性的累加操作incrementAndGet()方法即可,是不是非常的简单呢。

emmmmm,你可能又会说就这?有啥用呢,其实呢对于日常的业务开发来说作用是不大的,但涉及到统计类的需求还是非常有帮助的。

就比如有个统计接口调用次数的需求,那你是用count++肯定就不能做到那么精确了,AtomicInteger就有用啦。

AtomicInteger源码简述

上面说到AtomicInteger可以神奇的实现原子操作,那么这块我们就来谈谈它是如何实现原子操作的。

AtomicInteger类声明:

1 public class AtomicInteger extends Number implements java.io.Serializable

AtomicInteger类属性:

 1 // setup to use Unsafe.compareAndSwapInt for updates
 2 private static final Unsafe unsafe = Unsafe.getUnsafe();
 3 private static final long valueOffset;
 4 
 5 static {
 6     try {
 7         valueOffset = unsafe.objectFieldOffset
 8             (AtomicInteger.class.getDeclaredField("value"));
 9     } catch (Exception ex) { throw new Error(ex); }
10 }
11 
12 private volatile int value;

从类属性上来看,可以得知AtomicInteger就是基于上次说到的Unsafe来实现的,其value属性的偏移量就是在静态代码块中加载到valueOffset中的,使用了objectFieldOffset()方法。

AtomicInteger构造器:

1 public AtomicInteger(int initialValue) {
2     value = initialValue;
3 }
4 
5 public AtomicInteger() {
6 }

构造器也是非常的简单。

incrementAndGet()方法:

这里就是今天的重点了,原子操作的累加如何实现的。

 1 public final int incrementAndGet() {
 2     return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
 3 }
 4 
 5 public final int getAndAddInt(Object var1, long var2, int var4) {
 6     int var5;
 7     do {
 8         var5 = this.getIntVolatile(var1, var2);
 9     } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
10 
11     return var5;
12 }

首先incrementAndGet()方法调用了unsafe的getAndAddInt,getAndAddInt通过Unsafe提供的volatile读来实现的,并且每次会使用CAS方式来修改int值。

综上,AtomicInteger.incrementAndGet()的自增原理如下:

  • 调用unsafe的getAndAddInt方法。
  • unsafe的getAndAddInt方法通过自旋,每次尝试通过CAS方法对原值进行累加。若累加失败,则进入下一次循环,知道累加成功自旋才会结束。
posted @ 2020-07-15 22:46  被猪附身的人  阅读(480)  评论(0编辑  收藏  举报