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及其实现原理

  • 相关阅读:
    JavaWeb--HttpSession案例
    codeforces B. Balls Game 解题报告
    hdu 1711 Number Sequence 解题报告
    codeforces B. Online Meeting 解题报告
    ZOJ 3706 Break Standard Weight 解题报告
    codeforces C. Magic Formulas 解题报告
    codeforces B. Sereja and Mirroring 解题报告
    zoj 1109 Language of FatMouse 解题报告
    hdu 1361.Parencodings 解题报告
    hdu 1004 Let the Balloon Rise 解题报告
  • 原文地址:https://www.cnblogs.com/2015110615L/p/6748365.html
Copyright © 2011-2022 走看看