zoukankan      html  css  js  c++  java
  • Java多线程:AtomicInteger 原子更新基本类型类

    前言

    原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以它不是一个原子操作(线程执行a=0这个语句时直接将数据写入内存中;而执行a++时,会先获取a的值,再去执行加操作,最后再将数据写入内存)。只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。

    Atomic包介绍

    在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。

    原子更新基本类型

    用于通过原子的方式更新基本类型,Atomic包提供了以下三个类:

    • AtomicBoolean:原子更新布尔类型。
    • AtomicInteger:原子更新整型。
    • AtomicLong:原子更新长整型。

    AtomicInteger的常用方法如下:

    • AtomicInteger(int initialValue):创建一个AtomicInteger实例,初始值由参数指定。不带参的构造方法初始值为0。
    • int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果,与getAndAdd(int delta)相区分,从字面意思即可区分,前者返回相加后结果,后者先返回再相加。
    • boolean compareAndSet(int expect, int update) :如果当前值等于预期值,则以原子方式将该值设置为输入的值。
    • int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
    • void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。关于该方法的更多信息可以参考并发网翻译的一篇文章《AtomicLong.lazySet是如何工作的?
    • int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。

    AtomicInteger例子代码如下:

     1 import java.util.concurrent.atomic.AtomicInteger;
     2 
     3 public class Demo
     4 {
     5     // 初始值设为1
     6     static AtomicInteger atom = new AtomicInteger(1);
     7 
     8     public static void main(String[] args)
     9     {
    10         System.out.println("初始值 = " + atom);
    11         
    12         // 以原子的方式加1,注意是先返回原数值再加1
    13         System.out.println("调用getAndIncrement()返回值 = " + atom.getAndIncrement());
    14         System.out.println("调用getAndIncrement()后初始值变为 = " + atom);
    15 
    16         // 以原子的方式与指定数值相加,注意是先加再返回相加后的值
    17         System.out.println("调用addAndGet()返回值 = " + atom.addAndGet(10));
    18         System.out.println("调用addAndGet()后初始值变为 = " + atom);
    19 
    20         // 以原子的方式将当前值设为指定数值,注意是先返回原值再设为指定值
    21         System.out.println("调用getAndSet()返回值 = " + atom.getAndSet(-5));
    22         System.out.println("调用getAndSet()后初始值变为 = " + atom);
    23 
    24         // 如果当前值等于预期值(第一个参数),则以原子的方式将当前值设置为指定值,并返回true,否则返回false
    25         System.out.println("调用compareAndSet()返回值 = " + atom.compareAndSet(-5, 100));
    26         System.out.println("调用compareAndSet()后初始值变为 = " + atom);
    27     }
    28 }

    结果如下:

    初始值 = 1
    调用getAndIncrement()返回值 = 1
    调用getAndIncrement()后初始值变为 = 2
    调用addAndGet()返回值 = 12
    调用addAndGet()后初始值变为 = 12
    调用getAndSet()返回值 = 12
    调用getAndSet()后初始值变为 = -5
    调用compareAndSet()返回值 = true
    调用compareAndSet()后初始值变为 = 100

    那 AtomicInteger 怎么实现院子操作的呢,以 getAndIncrement() 为例,我们看看源码是怎样实现这个方法的

    public final int getAndIncrement() {
        for (;;) {
            // 获取AtomicInteger当前对应的long值
            int current = get();
            // 将current加1
            int next = current + 1;
            // 通过CAS函数,更新current的值
            if (compareAndSet(current, next))
                return current;
        }
    }

    getAndIncrement()首先会根据get()获取AtomicInteger对应的int值。该值是volatile类型的变量,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时发现此时值改变了,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

    get()的源码如下:

    // value是AtomicInteger对应的int值
    private volatile int value;
    // 返回AtomicInteger对应的int值
    public final int get() {
        return value;
    }

    getAndIncrement()接着将current加1,然后通过CAS函数(乐观锁),将新的值赋值给value。compareAndSet()的源码如下:

    1 public final boolean compareAndSet(intexpect, int update) {
    2     return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
    3 }

    调用 Unsafe 来实现

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

    这段代码写得很巧妙:

    1. compareAndSet 方法首先判断当前内存值this是否等于预期值current;

    2. 如果当前值 = current,说明 AtomicInteger 类的值没有被其他线程修改,则将内存值更新为next

    3. 如果当前值 != current,说明 AtomicInteger 类的值已经被其他类修改了,这时会再次进入循环重新获取更新后值并比较

    valueOffset表示的是变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的原值的。

    Atomic包提供了三种基本类型的原子更新,但是Java的基本类型里还有char,float和double等。那么问题来了,如何原子的更新其他的基本类型呢?Atomic包里的类基本都是使用Unsafe实现的,它提供了硬件级别的原子操作。让我们一起看下Unsafe的源码,发现Unsafe只提供了三种CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现其是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新double也可以用类似的思路来实现。

    参考资料:

    Java多线程系列--“JUC原子类”02之 AtomicLong原子类

    Java中的Atomic包使用指南

    java中的原子操作类AtomicInteger及其实现原理

  • 相关阅读:
    C语言 数组排序 – 冒泡法排序
    类加载过程
    布隆过滤器的原理及应用
    回答两个被频繁问到的代码写法问题
    关于分布式事务的理解
    根据使用者反馈,对开源项目 go-gin-api 新增两个功能
    图书速度 | 《如何高效学习》
    报错kernel:NMI watchdog: BUG: soft lockup
    容器Jenkins中运行docker
    容器Jenkins中运行docker
  • 原文地址:https://www.cnblogs.com/2015110615L/p/6748365.html
Copyright © 2011-2022 走看看