前言
自学了一年 JAVA 阿巴阿巴终于约到了面试,这次面试官让她谈谈对 CAS 的了解。
<Center> 回去等告诉 </Center>
如果对 CAS 齐全不理解的同学倡议先去看看相干的博客理解了根本的原理,再来看面试的时候如何解答
面试官: 对 CAS 有理解吗?能够讲讲吗?
阿巴阿巴: 理解一些,CAS 全称 Compare And Swap,也就是比拟和替换。
阿巴阿巴: CAS 的思维比较简单,次要波及到三个值:以后内存值 V、预期值(旧的内存值)O、行将更新的内存值 U, 当且仅当预期值 O 与以后内存值 V 相等时,将内存值 V 批改为更新值 U,并返回 true,否则返回 false。
面试官: 还有嘛?CAS 的应用场景晓得吗?
阿巴阿巴: 额 … 应该差不多了,CAS 如同在并发包里应用到了
面试官: 好,CAS 有啥毛病吗?
阿巴阿巴: 额 …. 好.. 如同有个 ABA 的问题,如同是用 AtomicStampedReference 解决
面试官: 还有其余毛病吗?
阿巴阿巴: 额 … 记不太清了 ….
面试官: 行,那你这边先回去等告诉哈😈
阿巴阿巴: 好的~
<Center> 当场发 offer</Center>
面试官: CAS 理解吗?讲讲
阿巴阿巴: CAS 全称 Compare and Swap,也就是比拟和替换。
阿巴阿巴: CAS 的思维比较简单,次要波及到三个值:以后内存值 V、预期值(旧的内存值)O、行将更新的内存值 U, 当且仅当预期值 O 与以后内存值 V 相等时,将内存值 V 批改为更新值 U,返回 true,否则返回 false。
阿巴阿巴: CAS 次要应用在一些须要上锁的场景充当乐观锁解决方案,个别在一些简略且要上锁的操作但又不想引入锁场景,这时候来应用 CAS 代替锁。
阿巴阿巴: CAS 次要波及到三个问题:ABA 问题、自旋带来的耗费、CAS 只能单变量
面试官: 能够具体讲一下这三个问题吗?
阿巴阿巴: ABA 问题 是指有一个线程 t1 在进行 CAS 操作时,其余线程 t2 将变量 A 改成了 B,而后又将其改成 A,这时候 t1 发现 A 并没有扭转,因而进行了替换操作,因为在替换操作进行前变量 A 其实是有变动的,只不过最终又批改回 A 了,此 A 非彼 A,这时候进行替换操作在一些业务场景下很可能要出问题,要解决 ABA 问题有 2 种计划。
阿巴阿巴: 计划一:在对变量进行操作的时候给变量加一个版本号,每次对变量操作都将版本号加 1,常见在数据库的乐观锁中可见。
阿巴阿巴: 计划二:Java 提供了相应的原子援用类 AtomicStampedReference,它通过包装 [E,Integer] 的元组来对对象标记版本戳 stamp,从而防止 ABA 问题。
阿巴阿巴: 自旋带来的耗费CAS 自旋如果很长时间都不胜利,这会给 CPU 带来很大的开销
阿巴阿巴: 解决方案:1、代码层面毁坏掉 for 循坏,设置适合的循环次数。2、应用 JVM 能反对处理器提供的 pause 指令来晋升效率,它能够提早流水线执行指令,防止耗费过多 CPU 资源。
阿巴阿巴: CAS 只能单变量 对于一个共享变量,能够应用 CAS 形式来保障原子操作,然而当多个共享变量时,那就无奈应用 CAS 来保障原子性。JDK1.5 开始,提供了 AtomicReference 类来保障援用对象之前的原子性,就能够把多个变量放在一个对象里来进行 CAS 操作。
阿巴阿巴: 在 JDK1.5 中新增的 java.util.concurrent(JUC), 就是建设在 CAS 之上的,一般来说 CAS 这种乐观锁适宜读多写少的场景。
面试官见阿巴阿巴对答如流,决定尴尬一下她
面试官: 理解 JMM 吗,讲一下 JMM。
阿巴阿巴: 晓得一些,JMM 是 JAVA 内存模型(JAVA Memory Model),目标是为了屏蔽各种硬件和操作系统之间的内存拜访差别,从而让 JAVA 程序在各种平台对内存的拜访统一。
阿巴阿巴: 不仅如此,JMM 还规定了所有的变量都存储在主存中,每个线程都有本人独立的工作空间,线程对变量的操作必须先从主存中读取到本人的工作内存中而后再进行操作,最初回写回主存。
阿巴阿巴: 对于主存和工作内存的交互 JAVA 定义了八种操作来实现,且这些操作都是原子性的:lock、unlock、read、load、use、assign、store、write
面试官: 不错不错,那 JMM 是实在存在的嘛,和 JVM 内存模型 (JAVA 虚拟机内存模型) 是一样的嘛?
阿巴阿巴: 不是实在存在的,JMM 讲的也只是一种模型,实在的实现可能还是和模型会有差别的。JMM 和 JVM 是不一样的,它们并不是同一个档次的划分,基本上没啥关系。
堆和办法区是线程共享的,虚拟机栈、本地办法栈、程序计数器是线程公有的
程序计数器是这几块区域惟一一个不会产生 OOM 的区域
面试官: 了解的还不错嘛,那你讲讲 Volatile 关键字呗
阿巴阿巴: Volatile 能够说是 JAVA 虚拟机提供的最轻量级的同步机制,当一个变量被定义为 volatile 后,它将具备俩种个性,第一个是保障此变量对所有线程的可见性,即当一个线程扭转了这个变量的值后,其余线程可能立刻感知的到,尽管具备可见性,然而多线程在并发状况下对 volatile 润饰的变量进行操作时是会有线程安全性的问题的。这是因为 volatile 润饰的变量在各个线程工作内存中是不存在一致性的,然而因为每次应用都要进行刷新,导致执行引擎看不到不统一的状况。
阿巴阿巴: Volatile 润饰的变量的第二个个性是禁止指令重排序优化,一般的变量仅仅会保障在该办法的执行过程中所有依赖的赋值后果的中央都可能获取到正确的后果。而不能保障赋值的程序和代码中的书写程序统一。例如上面的 DCL 的单例模式。
public class Instance {
private String str = "";
private volatile static Instance ins = null;
/**
* 构造方法私有化
*/
private Instance(){str = "hi";}
/**
* DCL 获取单例
* @return
*/
public static Instance getInstance(){if (ins == null){synchronized (Instance.class){if (ins == null){ins = new Instance();
}
}
}
return ins;
}
}
阿巴阿巴: 如果下面 ins 变量不应用 volatile 变量进行润饰,那么当线程 A 在获取了 Instance.class 锁后,对 ins 变量进行 ins = new Instance() 初始化时,因为这是很多条指令,jvm 可能会乱序执行。这个时候如果线程 B 在执行 if (ins == null)时,失常状况下,如果为 true,阐明须要获取 Instance.class 锁,期待初始化。然而这时候,假如线程 A 再没有对 ins 进行初始化完,比方只调配了空间,对象还没结构完,然而曾经将援用返回了,这样线程 B 失去的就是一个未能实例化齐全的对象,从而产生异样。而加了 volatile 关键字后,如果实例还未初始化实现,那么它的援用是不会向外公布的,这样即可防止异样的产生。
面试官: 不错,你这块都把握的挺扎实的,今天能够来下班了。
阿巴阿巴: 好的😈
/ 感激反对 /
以上便是本次分享的全部内容,心愿对你有所帮忙 ^_^
喜爱的话别忘了 分享、点赞、珍藏 三连哦~
欢送关注公众号 程序员巴士,来自字节、虾皮、招银的三端兄弟,分享编程教训、技术干货与职业规划,助你少走弯路进大厂。