• 并发编程学习笔记(三十、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方法对原值进行累加。若累加失败,则进入下一次循环,知道累加成功自旋才会结束。
  • 相关阅读:
    linux 终端光标消失问题
    linux系统中条件测试语句
    linux shell if语句
    linux shell for循环
    linux 系统中read命令
    linux中while循环语句
    linux shell脚本中流程控制语句 if 、for、while、case
    pc端WINCE的安装包
    WinCE程序的几种开发方法
    Wince 下开发技巧(一)查看内存
  • 原文地址:https://www.cnblogs.com/bzfsdr/p/13308523.html
Copyright © 2020-2023  润新知