17.AtomicInteger、AtomicBoolean的底层原理

news/2024/11/20 11:42:19/

小陈:老王啊,今天就要开始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线程池篇总结


http://www.ppmy.cn/news/834977.html

相关文章

16.Atomic原子类体系概览

老王&#xff1a;小陈啊&#xff0c;从今天开始我们就要进入Atomic原子类系列的学习了&#xff0c;首先啊给你看一下JDK中提供给我们使用的原子类有哪些&#xff1f; 小陈&#xff1a;好啊&#xff0c;我记得JUC下面提供的原子类还是挺多的&#xff0c;所有的原子类的使用和底层…

Java并发:可重入锁、读写锁、(非)公平锁都怎么用?这篇给大家总结全了

下面我们先来简单介绍下JUC这个包的由来、它里面包含哪几部分功能&#xff0c;再来学习一下它为我们提供的锁的常用功能&#xff0c;本文大纲如下&#xff1a; J.U.C 介绍 Java 在版本 1.5 之前&#xff0c;协调多线程对共享对象的访问时可以使用的机制只有 synchronized 和 v…

不同协议的数据包如何处理_【干货】不同养护条件下湿度偏高的雪茄应该如何处理?...

布尔道格VC308德国恒温恒湿雪茄柜​item.taobao.com 雪茄出现偏湿的情况&#xff0c;茄体会湿软无弹性&#xff1b;膨胀导致的烟阻、烟体的结块&#xff0c;雪茄末端水分堆积后味道会变苦&#xff0c;甚至容易熄火。尤其南方的回南天天气&#xff0c;往往相对湿度超过80%&#…

【Redis】4、全局唯一 ID生成、单机(非分布式)情况下的秒杀和一人一单

目录 一、利用 Redis 实现全局唯一 ID 生成(1) 为啥要用全局唯一 ID 生成(2) 全局唯一 ID 生成器(3) 全局 ID 的结构(4) 代码实现① RedisIdWorker② Test (5) 全局唯一 ID 其他生成策略 二、添加优惠券(1) 数据库(2) 添加优惠券接口 三、优惠券秒杀下单功能(1) 超卖问题(2) 乐…

在 TypeScript 中 interface 和 type 的区别

在 TypeScript 中&#xff0c;interface 和 type 都用于定义自定义类型&#xff0c;但它们有一些区别&#xff1a; 语法风格&#xff1a;interface 使用关键字 interface 开头&#xff0c;而 type 使用关键字 type 开头。例如&#xff1a; interface Person {name: string;age:…

4k对齐 diskgenius修复分区表 ubuntu安装

最近被500G 日历硬盘折腾了很久&#xff0c;今天终于解决问题。 问题起源&#xff1a; 前两天打算从fedora转向ubuntu。原来的fedora装在硬盘的最后一个分区&#xff08;G盘&#xff0c;为一主分区&#xff09;&#xff0c;可万万没想到&#xff0c;当我删除该分区的逻辑驱动器…

磁盘分区4K未对齐的解决方案

磁盘分区4K未对齐的解决方案 后续在遇到用户反馈电脑运行慢&#xff0c;不区分机型&#xff0c;先使用 AS SSD benchmark工具&#xff08;请查看附件&#xff09;检测分区对齐是否正常&#xff0c;华为办公区访问服务器\\szxems12-fs\drivers_for_IT下载工具。 如果显示为10…

怎么看ssd有没有4k对齐?3分钟包教包会!

固态硬盘成为许多电脑配置不够的标配&#xff0c;但是许多小伙伴告诉快启动小编&#xff1a;自己的电脑装上了固态硬盘之后并没有太大的变化。其实大家在使用固态硬盘是有盲区的&#xff0c;首先我们需要将其设置为4k对齐才能发挥到最佳性能&#xff0c;所以大家在分区时一定要…