被阿里面试官吊打,搞不定的a++

简介: 大家好,我是指北君。上周指北君的朋友小 B,遇上了这么一道面试题:在多线程的情况下,对一个值进行 a++ 操作,会出现什么问题?

a++ 的问题


先写个 demo 的例子。把 a++ 放入多线程中运行一下。定义 10 个线程,每个线程里面都调用 5 次 a++,把 a 用 volatile 修饰,可以让 a 的值在修改之后,所有的线程立刻就可以知道。最后结果是不是 50,还是其他的数字?


1.png

从结果上看 a++ 的操作并没有达到预期值的 50,而是少了很多,其中还有一定是有问题的。那就是因为 a++ 的操作并不是原子性的。


原子性


并发编程,有三大原则:有序性、可见性、原子性

  1. 有序性:正常编译器执行代码,是按顺序执行的。有时候,在代码顺序对程序的结果没有影响时,编译器可能会为了性能从而改变代码的顺序。
  2. 可见性:一个线程修改了一个变量的值,另外一个线程立刻可以知道修改后的值。
  3. 原子性:一个操作或者多个操作在执行的时候,要么全部被执行,要么全部都不执行。

上面的 a++ 就没有原子性,它有三个步骤:

  1. 在内存中读取了 a 的值。
  2. 对 a 进行了 + 1 操作。
  3. 将新的 a 值刷回到内存。

这三个步骤可以被示例中的 10 个线程上下文切换打断:当 a = 10

  1. 线程 1 将 a 的值读取到内存, a = 10
  2. 线程 2 将 a 的值读取到内存, a = 10
  3. 线程 1 将 a + 1,a = 11
  4. 此时线程发生切换,线程 2 对 a 进行 + 1 操作, a = 11
  5. 线程 2 将 a 的值写回到内存, a = 11
  6. 线程 1 将 a 的值写回到内存, a = 11

从上面的步骤中可以看出 a 的值在两次相加后没有得到 12 的值,而是 11。这就是 a++ 引发的问题。

小 B 把上面的步骤对面试官讲了一遍,面试官又问了,有什么方式可以避免这个问题,小 B 不加思索的回答用 synchronized 加锁。面试官说 synchronized 太重了,还有其他的解决方式吗?小 B 晕了。其实可以使用 AtomicInteger 的 incrementAndGet() 方法。


AtomicInteger 源码分析


主要属性

首先看看 AtomicInteger 的主要属性。

2.png


从属性中可以看出 AtomicInteger 调用的是 Unsafe 类,Unsafe 类中大多数的方法是用 native 修饰的,可以直接进行一些系统级别的操作。

用 volatile 修饰 value 值,保证了一个线程的值对另外一个线程立即可见。

incrementAndGet()

3.png

incrementAndGet() 首先获取了当前值,然后调用 compareAndSwapInt() 方法更新数据。

compareAndSwapInt() 是 CAS 的缩写来源,比较并替换。被 native 修饰,调用了操作系统底层的方法,保证了硬件级别的原子性。

var2,var4,var5 是它的三个操作数,表示内存地址偏移量 valueOffset,预期原值 expect,新的值 update。把 this.compareAndSwapInt(var1, var2, var5, var5 + var4) 变成 this.compareAndSwapInt(obj, valueOffset, expect, update),释义就是如果内存位置中的 valueOffset 值 与 expect 的值相同,就把内存中的 valueOffset 改成 update,否则不操作。

getAndAddInt() 方法中用了 do-while,就相当于如果 CAS 一直更新不成功,就不退出循环。直到更新成功为止。


ABA 问题


CAS 操作也并不是没有问题的。

  1. 循环操作时间长了,开销大。用了 do-while,如果更新一直不成功,就一直在循环。会给 CPU 带来很大的开销。
  2. 只能保证一个共享变量的原子性。循环 CAS 的方式只能保证一个变量进行原子操作,在对多个变量进行 CAS 的时候就没办法保证原子性了。
  3. ABA 问题。CAS 的操作一般是 1. 读取内存偏移量 valueOffset。2. 比较 valueOffset 和 expect 的值。3. 更新 valueOffset 的值。如果线程 A 读取 valueOffset 后,线程 B 修改了 valueOffset 的值,并且将 valueOffset 的值又改了回来。线程 A 会认为 valueOffset 的值并没有改变。这就是 ABA 问题。要解决这个问题,就是在每次修改 valueOffset 值的时候带上一个版本号。


总结


这篇文章介绍了 CAS,它是 java 中的乐观锁,每次认为操作并不会有其他线程去修改数据,如果有其他线程操作了数据,就重试,一直到成功为止。

我是指北君,操千曲而后晓声,观千剑而后识器。感谢各位人才的:点赞、收藏和评论,我们下期更精彩!

相关文章
|
6月前
|
Python 开发工具
2024年Python最全使用Python实现音频双通道分离,2024年最新阿里p7面试难度
2024年Python最全使用Python实现音频双通道分离,2024年最新阿里p7面试难度
2024年Python最全使用Python实现音频双通道分离,2024年最新阿里p7面试难度
|
1月前
|
存储 关系型数据库 MySQL
阿里面试:为什么要索引?什么是MySQL索引?底层结构是什么?
尼恩是一位资深架构师,他在自己的读者交流群中分享了关于MySQL索引的重要知识点。索引是帮助MySQL高效获取数据的数据结构,主要作用包括显著提升查询速度、降低磁盘I/O次数、优化排序与分组操作以及提升复杂查询的性能。MySQL支持多种索引类型,如主键索引、唯一索引、普通索引、全文索引和空间数据索引。索引的底层数据结构主要是B+树,它能够有效支持范围查询和顺序遍历,同时保持高效的插入、删除和查找性能。尼恩还强调了索引的优缺点,并提供了多个面试题及其解答,帮助读者在面试中脱颖而出。相关资料可在公众号【技术自由圈】获取。
|
13天前
|
SQL 关系型数据库 MySQL
阿里面试:1000万级大表, 如何 加索引?
45岁老架构师尼恩在其读者交流群中分享了如何在生产环境中给大表加索引的方法。文章详细介绍了两种索引构建方式:在线模式(Online DDL)和离线模式(Offline DDL),并深入探讨了 MySQL 5.6.7 之前的“影子策略”和 pt-online-schema-change 方案,以及 MySQL 5.6.7 之后的内部 Online DDL 特性。通过这些方法,可以有效地减少 DDL 操作对业务的影响,确保数据的一致性和完整性。尼恩还提供了大量面试题和解决方案,帮助读者在面试中充分展示技术实力。
|
1月前
|
消息中间件 存储 canal
阿里面试:canal+MQ,会有乱序的问题吗?
本文详细探讨了在阿里面试中常见的问题——“canal+MQ,会有乱序的问题吗?”以及如何保证RocketMQ消息有序。文章首先介绍了消息有序的基本概念,包括全局有序和局部有序,并分析了RocketMQ中实现消息有序的方法。接着,针对canal+MQ的场景,讨论了如何通过配置`canal.mq.partitionsNum`和`canal.mq.partitionHash`来保证数据同步的有序性。最后,提供了多个与MQ相关的面试题及解决方案,帮助读者更好地准备面试,提升技术水平。
阿里面试:canal+MQ,会有乱序的问题吗?
|
6月前
|
机器学习/深度学习 Python 算法
最新【Python 百练成钢】时间调整、二进制数、回文素数、字母距离(1),2024年最新2024年阿里Python岗面试必问
最新【Python 百练成钢】时间调整、二进制数、回文素数、字母距离(1),2024年最新2024年阿里Python岗面试必问
最新【Python 百练成钢】时间调整、二进制数、回文素数、字母距离(1),2024年最新2024年阿里Python岗面试必问
|
1月前
|
消息中间件 架构师 Java
阿里面试:秒杀的分布式事务, 是如何设计的?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试阿里、滴滴、极兔等一线互联网企业时,遇到了许多关于分布式事务的重要面试题。为了帮助大家更好地应对这些面试题,尼恩进行了系统化的梳理,详细介绍了Seata和RocketMQ事务消息的结合,以及如何实现强弱结合型事务。文章还提供了分布式事务的标准面试答案,并推荐了《尼恩Java面试宝典PDF》等资源,帮助大家在面试中脱颖而出。
|
1月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
1月前
|
Kubernetes 架构师 算法
阿里面试:全国14亿人,统计出重名最多的前100个姓名
文章介绍了如何解决“从全国14亿人的数据中统计出重名人数最多的前100位姓名”的面试题,详细分析了多种数据结构的优缺点,最终推荐使用前缀树(Trie)+小顶堆的组合。文章还提供了具体的Java代码实现,并讨论了在内存受限情况下的解决方案,强调了TOP N问题的典型解题思路。最后,鼓励读者通过系统化学习《尼恩Java面试宝典》提升面试技巧。
阿里面试:全国14亿人,统计出重名最多的前100个姓名
|
1月前
|
存储 缓存 NoSQL
阿里面试题:缓存的一些常见的坑,你遇到过哪些,怎么解决的?
阿里面试题:缓存的一些常见的坑,你遇到过哪些,怎么解决的?
|
2月前
|
缓存 监控 NoSQL
阿里面试让聊一聊Redis 的内存淘汰(驱逐)策略
大家好,我是 V 哥。粉丝小 A 面试阿里时被问到 Redis 的内存淘汰策略问题,特此整理了一份详细笔记供参考。Redis 的内存淘汰策略决定了在内存达到上限时如何移除数据。希望这份笔记对你有所帮助!欢迎关注“威哥爱编程”,一起学习与成长。
下一篇
无影云桌面