并发编程学习笔记(二十九、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方法对原值进行累加。若累加失败,则进入下一次循环,知道累加成功自旋才会结束。