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

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

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

Integer的测试样例

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

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

public class AddDemo {// 定义一个Integer类型的共享变量valueprivate static Integer value = 0;public static class AddThread extends Thread{@Overridepublic 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相差还是挺大的

AtomicInteger的测试样例

(1)定义一个AtomicInteger原子类

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

public class AtomicAddDemo {private static AtomicInteger value = new AtomicInteger(0);public static class AtomicAddThread extends Thread{@Overridepublic 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,与预期的结果准确无误

老王:小陈啊,通过上述的实际例子,说明 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变量在内存的地址是多少,然后就可以直接给这块内存赋值了。

小陈:额,这个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操作是怎么保证原子性的,小陈你还记得住不,前两章的时候我们还画了一个图的:

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

老王:好,那我也就不在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 = 0value = 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 的底层原理就到这里了,我们明天继续......

小陈:我们下一章见。

关注小陈,公众号上更多更全的文章

JAVA并发文章目录(公众号)

JAVA并发专题 《筑基篇》

1.什么是CPU多级缓存模型?

2.什么是JAVA内存模型?

3.线程安全之可见性、有序性、原子性是什么?

4.什么是MESI缓存一致性协议?怎么解决并发的可见性问题?

JAVA并发专题《练气篇》

5.volatile怎么保证可见性?

6.什么是内存屏障?具有什么作用?

7.volatile怎么通过内存屏障保证可见性和有序性?

8.volatile为啥不能保证原子性?

9.synchronized是个啥东西?应该怎么使用?

10.synchronized底层之monitor、对象头、Mark Word?

11.synchronized底层是怎么通过monitor进行加锁的?

12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁

13.synchronized怎么保证可见性、有序性、原子性?

JAVA并发专题《结丹篇》

14. JDK底层Unsafe类是个啥东西?

15.unsafe类的CAS是怎么保证原子性的?

16.Atomic原子类体系讲解

17.AtomicInteger、AtomicBoolean的底层原理

18.AtomicReference、AtomicStampReference底层原理

19.Atomic中的LongAdder底层原理之分段锁机制

20.Atmoic系列Strimped64分段锁底层实现源码剖析

JAVA并发专题《金丹篇》

21.AQS是个啥?为啥说它是JAVA并发工具基础框架?

22.基于AQS的互斥锁底层源码深度剖析

23.基于AQS的共享锁底层源码深度剖析

24.ReentrantLock是怎么基于AQS实现独占锁的?

25.ReentrantLock的Condition机制底层源码剖析

26.CountDownLatch 门栓底层源码和实现机制深度剖析

27.CyclicBarrier 栅栏底层源码和实现机制深度剖析

28.Semaphore 信号量底层源码和实现机深度剖析

29.ReentrantReadWriteLock 读写锁怎么表示?

30. ReentrantReadWriteLock 读写锁底层源码和机制深度剖析

JAVA并发专题《元神篇》并发数据结构篇

31.CopyOnAarrayList 底层分析,怎么通过写时复制副本,提升并发性能?

32.ConcurrentLinkedQueue 底层分析,CAS 无锁化操作提升并发性能?

33.ConcurrentHashMap详解,底层怎么通过分段锁提升并发性能?

34.LinkedBlockedQueue 阻塞队列怎么通过ReentrantLock和Condition实现?

35.ArrayBlockedQueued 阻塞队列实现思路竟然和LinkedBlockedQueue一样?

36.DelayQueue 底层源码剖析,延时队列怎么实现?

37.SynchronousQueue底层原理解析

JAVA并发专题《飞升篇》线程池底层深度剖析

38. 什么是线程池?看看JDK提供了哪些默认的线程池?底层竟然都是基于ThreadPoolExecutor的?

39.ThreadPoolExecutor 构造函数有哪些参数?这些参数分别表示什么意思?

40.内部有哪些变量,怎么表示线程池状态和线程数,看看道格.李大神是怎么设计的?

41. ThreadPoolExecutor execute执行流程?怎么进行任务提交的?addWorker方法干了啥?什么是workder?

42. ThreadPoolExecutor execute执行流程?何时将任务提交到阻塞队列? 阻塞队列满会发生什么?

43. ThreadPoolExecutor 中的Worker是如何执行提交到线程池的任务的?多余Worker怎么在超出空闲时间后被干掉的?

44. ThreadPoolExecutor shutdown、shutdownNow内部核心流程

45. 再回头看看为啥不推荐Executors提供几种线程池?

46. ThreadPoolExecutor线程池篇总结

17.AtomicInteger、AtomicBoolean的底层原理相关推荐

  1. 19.Atomic系列之LongAdder的底层原理(分段锁提升并发性能)

    老王:小陈啊,上一章我们讲解了cas的缺陷,无法同时更新多个变量.以及ABA的问题.以及如果使用AtomicReference解决同时更新多个变量,如果使用AtomicStampedReference ...

  2. 18.AtomicReference、AtomicStampReference底层原理。多个变量更新怎么保证原子性?CAS的ABA问题怎么解决?

    老王:小陈啊,上一章我们说了AtomicInteger.AtomicBoolean的底层原理,这一篇我们就来说说Atomic系列的另一个分类AtomicReference和AtomicStampRef ...

  3. 并发底层原理:线程、资源共享、volatile 关键字

    并发底层原理:线程.资源共享.volatile 关键字 1.线程 1.1 定义任务 1.2 Thread 类 1.3 使用 Executor 1.4 从任务中产生返回值 1.6 优先级 1.7 后台线 ...

  4. JAVA8线程池THREADPOOLEXECUTOR底层原理及其源码解析

    小侃一下 1. 使用线程池的好处. 为什么要使用线程池? 2. 线程池核心参数介绍 3. 提交任务到线程池中的流程 3.1 ThreadPoolExecutor#execute方法整体流程 3.2 排 ...

  5. 没有与参数列表匹配的 重载函数 getline 实例_面试题:方法重载的底层原理?...

    前语:微信改版后,大量读者还没养成点赞的习惯,如写得好,望大家阅读后在右下边"好看"处点个赞,以示鼓励!长期坚持原创真的很不容易,多次想放弃,坚持是一种信仰,专注是一种态度. 关于 ...

  6. 为了把mysql的索引底层原理讲清楚,我把计算机翻了个底朝天

    来自:非科班的科班 什么是索引 概念:索引是提高mysql查询效率的数据结构.总的一句话概括就是索引是一种数据结构. 数据库查询是数据库的最主要功能之一.设计者们都希望查询数据的速度能尽可能的快,因此 ...

  7. java for each 的源_Java JDK1.5的新特性之for-each循环的底层原理

    今天给大家分享一下for-each循环的底层原理,故事还得从字节码反汇编和反编译说起... 首先看一下Java JDK1.5的新特性 泛型: ... for-each. ... 自动拆箱和装箱功能 . ...

  8. 深入理解Go底层原理剖析 (送书)

    互联网迅猛发展的数十年时间里,不断面领着各种新的场景与挑战,例如大数据.大规模集群计算.更复杂的网络环境.多核处理器引起对于高并发的需求,云计算,上千万行的服务器代码-- 那些成熟但上了年纪的语言没能 ...

  9. 分布式事务之底层原理揭秘

    , hi 大家好,今天分享一这篇文章,让大家彻底了解分布式原理,这个是后台开发必须掌握技能. 刚性事务 柔性事务 本地事务 分布式事务 单阶段原子提交协议 两阶段提交协议 定义 原理 性能 恢复 缺陷 ...

最新文章

  1. Google AI 又来放大招,放射科医生会被取代吗?
  2. Semantic UI实现一个landing page
  3. 破站www.2637.cn居然敢截持偶的IE!
  4. 【送书】2021年哪些好书值得读(小姐姐配音)
  5. 将状态机模式实现为流处理器
  6. Java求最小数用哪个函数_在Java中使用小数进行计算的函数
  7. YouSwap将于4月16日新增HGT/USDT、HGT/YOU流动性挖矿
  8. 【Java程序设计】异常处理
  9. 第5章 初识SqlSugarCore之内置逻辑分页
  10. 禁用计算机中的u盘功能,怎样禁用U盘的写入功能 -电脑资料
  11. 霍普金斯计算机专业研究生如何,约翰霍普金斯大学计算机科学研究生项目详情!_托普仕留学...
  12. 从SPS帧解析视频分辨率
  13. 大气校正6S与FLAASH计算与对比
  14. 【OpenCV】HSV颜色识别-HSV基本颜色分量范围
  15. 红外额温枪方案开发技术支持
  16. 【Bugzilla】我按照bugzilla的官方指导进行的安装。(一)
  17. 纸浆模塑包装(MPP)行业调研报告 - 市场现状分析与发展前景预测
  18. Pandas Dataframe 每隔n行取1行
  19. HEVC Study Three(基于HM14.0平台)--GOP研究之大揭秘
  20. tm1650中文资料_[STM8入门资料]第53讲 TM1650 LED数码管驱动芯片工作原理

热门文章

  1. 樱花庄的宠物女孩AtCoder Grand Contest 015E - Mr.Aoki Incubator
  2. UCAS计算机网络实验
  3. 门子1200plc485 modbus rtu通讯,55条通讯,几百个数据,1s内完成
  4. 井字棋LeetCode简单题
  5. Java面试:基础概念
  6. 摄像头poe供电原理_POE供电的原理以及非标POE和标准POE的区分!
  7. 硅麦驱动开发及调试(pdm>>I2S>>pcm)
  8. 基于python编写的RAT(Remote Administration Tool)——Stitch
  9. 网校搭建完成,课程还需要设计吗?
  10. outlook仅限于此计算机如何解决,Outlook2013中IMAP方式已发送邮件、已删除邮件等文件夹注册失败...