17.AtomicInteger、AtomicBoolean的底层原理

简介: 17.AtomicInteger、AtomicBoolean的底层原理

小陈:老王啊,今天就要开始Atomic原子类的学习了吧......

老王:是啊,之前我们只是简单介绍了Atomic的体系,今天我们就要进入Atomic底层原理的的学习了,首先我们从AtomicInteger这个比较简单的原子类开始,在说AtomicInteger的底层原理之前呢,我先给你看两个例子:

实测样例对比Integer和AtomicInteger的线程安全性

Integer的测试样例

(1)定义一个共享变量Integer

(2)定义一个新的线程类,创建两个线程每个线程执行10000value++ 操作


public class AddDemo {
    // 定义一个Integer类型的共享变量value
    private static Integer value = 0;
    public static class AddThread extends Thread{
        @Override
        public void run() {
           for (int i = 0; i < 10000; i++) {
               value++;
           }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        // 定义两个线程各自对value执行10000次自增操作
        AddThread addThread1 = new AddThread();
        AddThread addThread2 = new AddThread();
        // 启动两个线程
        addThread1.start();
        addThread2.start();
        // 主线程等待两个线程执行完毕
        addThread1.join();
        addThread2.join();
        // 输出最新的value结果
        System.out.println("value的值为:" + value);
    }
}

看看最后得到的结果19513,比预期20000相差还是挺大的

image.png

AtomicInteger的测试样例

(1)定义一个AtomicInteger原子类

(2)定义一个新的线程类AtomicAddThread,创建两个线程,每个线程执行10000incrementAndGet() 操作

public class AtomicAddDemo {
    private static AtomicInteger value = new AtomicInteger(0);
    public static class AtomicAddThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                value.incrementAndGet();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        // 定义两个线程各自对value执行10000次自增操作
        AtomicAddThread atomicAddThread1 = new AtomicAddThread();
        AtomicAddThread atomicAddThread2 = new AtomicAddThread();
        // 启动两个线程
        atomicAddThread1.start();
        atomicAddThread2.start();
        // 主线程等待两个线程执行完毕
        atomicAddThread1.join();
        atomicAddThread2.join();
        // 输出最新的value结果
        System.out.println("value的值为:" + value.get());
    }
}

实际的结果20000,与预期的结果准确无误

image.png

老王:小陈啊,通过上述的实际例子,说明 AtomicInteger原子类确实是线程安全的。

小陈:是啊,使用AtomicInteger两个线程执行20000次自增操作得到的结果于预期值一致,那AtomicInteger底层到底是怎么确保线程安全的呢?

老王:这个啊,我们慢慢来剖析......

AtomicInteger的内部属性

老王:我们先通过源码来看一下AtomicInteger内部有哪些属性以及作用是什么:


public class AtomicInteger extends Number implements java.io.Serializable {
    // unsafe对象,可以直接根据内存地址操作数据,可以突破java语法的限制
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // 存储实际的值
    private volatile int value;
    // 存储value属性在AtomicInteger类实例内部的偏移地址
    private static final long valueOffset;
    static {
        try {
            // 在类初始化的时候就获取到了value变量在对象内部的偏移地址
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}

(1)首先内部持有一个unsafe对象,Atomic原子类底层的操作都是基于unsafe对象来进行的

(2)然后有一个volatile int value变量,这个value就是原子类的实际数值,使用volatile来修饰,volatile可以保证并发中的可见性和有序性这里之前讲过volatile可以保证可见性和有序性,不记得的要回去重新看一下哦

(3)还有一个valueOffset,看看这段代码,其实就是获得value属性在AtomicInteger对象内部的偏移地址的


valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));

这个value属性相对于AtomicInter对象内部偏移量存储在valueOffset中,我们之前讲过的 ,通过unsafe类是直接在内存级别去给变量赋值的。这里啊,我们再回顾一下unsafe值怎么从内存级别操作数据的:

  • 首先要知道你要操作对象的内存地址,也就是AtomicInteger对象引用指向的内存地址
  • 其次是要知道value属性在对象内部的偏移量offset,就可以通过 (对象地址 + offset偏移量) 直接找到value变量在内存的地址是多少,然后就可以直接给这块内存赋值了。

image.png

小陈:额,这个AtomicInteger内部还是蛮简单的呀,一个 volatile int value的属性、一个unsafe类、一个偏移地址就完事了

老王:哈哈,是啊,其实Atomic原子类啊,就是对基础的类型进行了一下包装而已,使得他们是线程安全的。比如AtomicInteger要对int进行包装,所以它内部肯定是有一个属性来存储int的值的。至于它其他两个属性valueOffset、unsafe是辅助实现并发安全的属性

AtomicInteger的构造方法

老王:让我们再来看看AtomicInteger的构造方法源码:


public AtomicInteger(int initialValue) {
    value = initialValue;
}
public AtomicInteger() {
}

提供了两个构造方法,第一个是在创建AtomicInteger对象的时候直接给内存存储值的volatile int value设置初始化的值;第二个没有赋初始值,那默认就是0

AtomicInteger方法的源码分析

getAndIncrement()方法源码


public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

我们看到AtomicIntegergetAndIncrement()方法源码很简单,底层就是基于unsafe.getAndAddInt包装了一下,让我们继续看一下unsafe.getAndAddInt方法源码


public final int getAndAddInt(Object o, long valueOffset, int x) {
    int expected;
    do {
        expected = this.getIntVolatile(o, valueOffset);
    } while(!this.compareAndSwapInt(o, valueOffset, expected, expected + x));
    return expected;
}

(1)首先 (o + valueOffset) 得到value变量在内存中的地址,然后根据地址直接取出value在主内存值,这个值记录为expected

(2)根据  (o + offsetSet)地址偏移量expected期待的值当前内存的值进行对比,如果相等则CAS操作成功,内存的值修改为 expected + x

(3)如果值不相等,则进入下一次循环,直到CAS操作成功为止。

(4)由于使用了volatile 修饰符修饰了value,所以一旦修改了别的线程能立马可见、同时volatile还是用内存屏障确保有序性

(5)所以上面的CAS操作确保了原子性,通过volatile确保可见性、有序性;线程安全的三个特性都满足了,上面的操作就是线程安全的。

小陈:原来这里AtomicInteger底层执行getAndIncrement() 操作底层就是直接调用unsafe的getAndAddInt()方法啊,最后还是走到了unsafe的compareAndSwapInt方法里面了,这里还是简单的呀。

老王:哈哈,AtomicInteger底层的源码本来就是不难的,底层都是基于unsafe进行薄薄的包装了一层而已,然后底层都是基于unsafe的CAS操作来保证原子性的,然后有使用volatile来修饰变量,保证了可见性和有序性,这样它就是线程安全的。

老王:关于unsafe的CAS操作是怎么保证原子性的,小陈你还记得住不,前两章的时候我们还画了一个图的:

image.png

小陈:嗯嗯,这个我记得的。

老王:好,那我也就不在CAS怎么保证原子性的话题上多说的了,我们继续看AtomicInteger原子类的其它源码:

AtomicInteger的compareAndSet源码


public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

底层也还是直接调用unsafe的compareAndSwapInt方法直接去修改,不过这里不同的是,只会执行一次CAS操作,即使失败了也不会重复CAS

其它方法源码

其它的方法,基本都是直接调用unsafe.getAndInt方法,上面我们分析过了


public final int getAndDecrement() {
    return unsafe.getAndAddInt(this, valueOffset, -1);
}
public final int getAndAdd(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta);
}

老王:好了,AtomicInteger的源码基本就分析到这里了,小陈关于AtomicInteger的底层原理这块,你还有其它的疑问不?

小陈:基本上没有了,AtomicInteger的底层还是比较简单的,基本都是调用unsafe的CAS操作确保原子性,然后使用volatile修饰变量,确保可见性和有序性,我理解上应该没问题了。

老王:好的,那我们就进入下一个原子类AtomicBoolean的讨论

AtomicBoolean 底层原理分析

AtomicBoolean 属性


public class AtomicBoolean implements java.io.Serializable {
    // unsafe对象,可以直接根据内存地址操作数据,可以突破java语法的限制
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // 存储实际的值
    private volatile int value;
    // 存储value属性在AtomicInteger类实例内部的偏移地址
    private static final long valueOffset;
    static {
        try {
            // 在类初始化的时候就获取到了value变量在对象内部的偏移地址
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}

小陈:啊这AtomicBoolean拥有的属性怎么跟AtomicInteger是一模一样的!!它不是布尔类型吗?怎么使用一个int类型的 volatile int value来存储?

老王:其实啊,这只是AtomicBoolean 玩的一个小把戏,我们接着看就知道了:

我们看一下AtomicBoolean的构造函数源码:


public AtomicBoolean(boolean initialValue) {
    // 当传入initialValue为true的时候value = 1 , false的时候value = 0
    value = initialValue ? 1 : 0;
}

所以这里我们猜测,AtomicBoolean 底层就是使用一个int类型来表示true和false的,当value = 1的时候表示true,当value = 0的时候表示false

然后继续看一下get()的源码:

直接就是判断value != 0 , 当value = 1则返回true,value = 0 返回false,证明了上面的猜想


public final boolean get() {
    return value != 0;
}

然后再看一下AtomicBoolean最常用最重要的方法compareAndSet源码:


public final boolean compareAndSet(boolean expect, boolean update) {
    int e = expect ? 1 : 0;
    int u = update ? 1 : 0;
    return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

底层就是将true 转成 1,将false转成 0然后还是调用unsafe的compareAndSwapInt方法去执行CAS操作!!, 这个我们在上面将AtomicInteger的时候已经讲过了

小陈:哎呀,原来是这样啊,这个AtomicBoolean 耍花样啊,我还以为它底层使用布尔类型来存储值呢,哪知道这兄弟直接volatile 修饰的int类型,然后1 表示 true,0 表示false,这操作不都跟AtomicInteger一样吗?只是将value表示的意思换了一下而已......

老王:是啊,看过AtomicBoolean的底层源码之后恍然大悟了吧,很多功能啊其实实现起来没有那么难,还是有很多的方式的.....

小陈:恩恩,这个我认同......

老王:小陈啊,今天我们将AtomicInteger、AtomicBoolean 的底层原理就到这里了,我们明天继续......

小陈:我们下一章见。



相关文章
|
9月前
|
存储 Java 数据安全/隐私保护
【CompareAndSwap底层原理】
【CompareAndSwap底层原理】
|
1月前
|
存储 Java 测试技术
滚雪球学Java(57):解密Java中List接口底层实现原理
【6月更文挑战第11天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
29 2
滚雪球学Java(57):解密Java中List接口底层实现原理
|
1月前
|
安全 Java 数据安全/隐私保护
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(二)
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(二)
23 0
|
8月前
|
存储 Cloud Native Linux
C++ list底层实现原理
C++ list底层实现原理
|
存储 Java
Java 最常见的面试题: 说一下 session 的工作原理?
Java 最常见的面试题: 说一下 session 的工作原理?
101 0
|
存储 PHP
Packagist是干什么的?底层原理是什么?
Packagist是干什么的?底层原理是什么?
225 0
|
存储 Java
【Java原理探索】「TreeMap」原理和基础源码的介绍
【Java原理探索】「TreeMap」原理和基础源码的介绍
53 0
【Java原理探索】「TreeMap」原理和基础源码的介绍
|
Java
Java 最常见面试题:List、Set、Map 之间的区别是什么?
Java 最常见面试题:List、Set、Map 之间的区别是什么?
123 0
Java 最常见面试题:List、Set、Map 之间的区别是什么?
|
JSON JavaScript 安全
Java 最常见的面试题:说一下 JSONP 实现原理?
Java 最常见的面试题:说一下 JSONP 实现原理?
|
XML Apache 数据格式
apache的生命周期是什么?底层原理是什么?
apache的生命周期是什么?底层原理是什么?
106 0