热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

面试官问我Java并发/多线程CAS原理分析,我这样回答给了我30koffer

目录什么是CAS并发安全问题举一个典型的例子i++如何解决?底层原理CAS需要注意的问题使用限制ABA问题概念解决方案高竞争下的开销问

目录

  • 什么是CAS

  • 并发安全问题

    • 举一个典型的例子i++

    • 如何解决?

    • 底层原理


  • CAS需要注意的问题

    • 使用限制

    • ABA 问题

      • 概念

        • 解决方案



    • 高竞争下的开销问题



什么是CAS

CAS 即 compare and swap,比较并交换。


CAS是一种原子操作,同时 CAS 使用乐观锁机制。

J.U.C中的很多功能都是建立在 CAS 之上,各种原子类,其底层都用 CAS来实现原子操作。用来解决并发时的安全问题。

另外本人整理收藏了20年多家公司面试知识点整理 ,以及各种Java核心知识点免费分享给大家,想要资料的话请点(点击此处)来免费获取!

并发安全问题

举一个典型的例子i++

public class AddTest {
public volatile int i;
public void add() {
i++;
}
}

通过javap -c AddTest可以看到add 方法的字节码指令:

public void add();
Code:
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: return

i++被拆分成了多个指令:


  1. 执行getfield拿到原始内存值;

  2. 执行iadd进行加 1 操作;

  3. 执行putfield写把累加后的值写回内存。

假设一种情况:


  • 线程 1 执行到iadd时,由于还没有执行putfield,这时候并不会刷新主内存区中的值。

  • 此时线程 2 进入开始运行,刚刚将主内存区的值拷贝到私有内存区。

  • 线程 1正好执行putfield,更新主内存区的值,那么此时线程 2 的副本就是旧的了。错误就出现了。


如何解决?

最简单的,在 add 方法加上 synchronized 。

public class AddTest {
public volatile int i;
public synchronized void add() {
i++;
}
}

虽然简单,并且解决了问题,但是性能表现并不好。

最优的解法应该是使用JDK自带的CAS方案,如上例子,使用AtomicInteger

public class AddIntTest {
public AtomicInteger i;
public void add() {
i.getAndIncrement();
}
}

底层原理

CAS 的原理并不复杂:


  • 三个参数,一个当前内存值 V、预期值 A、更新值 B

  • 当且仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true

  • 否则什么都不做,并返回 false

拿 AtomicInteger 类分析,先来看看源码:

我这里的环境是Java11,如果是Java8这里一些内部的一些命名有些许不同。

public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersiOnUID= 6214790243416807050L;
/*
* This class intended to be implemented using VarHandles, but there
* are unresolved cyclic startup dependencies.
*/
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
private volatile int value;
//...
}

Unsafe 类,该类对一般开发而言,少有用到。

Unsafe 类底层是用 C/C++ 实现的,所以它的方式都是被 native 关键字修饰过的。

它可以提供硬件级别的原子操作,如获取某个属性在内存中的位置、修改对象的字段值。

关键点:


  • AtomicInteger 类存储的值在 value 字段中,而value字段被volatile


  • 在静态代码块中,并且获取了 Unsafe 实例,获取了 value 字段在内存中的偏移量 VALUE


接下回到刚刚的例子:

如上,getAndIncrement() 方法底层利用 CAS 技术保证了并发安全。

public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}

getAndAddInt() 方法:

public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}

v 通过 getIntVolatile(o, offset)方法获取,其目的是获取 o 在 offset 偏移量的值,其中 o 就是 AtomicInteger 类存储的值,即value, offset 内存偏移量的值,即 VALUE

重点weakCompareAndSetInt 就是实现 CAS 的核心方法


  • 如果 o 和 v相等,就证明没有其他线程改变过这个变量,那么就把 v 值更新为 v + delta,其中 delta 是更新的增量值。

  • 反之 CAS 就一直采用自旋的方式继续进行操作,这一步也是一个原子操作。

分析:


  • 设定 AtomicInteger 的原始值为 A,线程 1 和线程 2 各自持有一份副本,值都是 A。



  1. 线程 1 通过getIntVolatile(o, offset)拿到 value 值 A,这时线程 1 被挂起。

  2. 线程 2 也通过getIntVolatile(o, offset)方法获取到 value 值 A,并执行weakCompareAndSetInt方法比较内存值也为 A,成功修改内存值为 B。

  3. 这时线程 1 恢复执行weakCompareAndSetInt方法比较,发现自己手里的值 A 和内存的值 B 不一致,说明该值已经被其它线程提前修改过了。

  4. 线程 1 重新执行getIntVolatile(o, offset)再次获取 value 值,因为变量 value 被 volatile 修饰,具有可见性,线程A继续执行weakCompareAndSetInt进行比较替换,直到成功


CAS需要注意的问题

使用限制

CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的,在Java中普通用户无法直接使用,只能借助atomic包下的原子类使用,灵活性受限。

但是CAS只能保证单个变量操作的原子性,当涉及到多个变量时,CAS无能为力。

原子性也不一定能保证线程安全,如在Java中需要与volatile配合来保证线程安全。

ABA 问题


概念

CAS 有一个问题,举例子如下:


  • 线程 1 从内存位置 V 取出 A

  • 这时候线程 2 也从内存位置 V 取出 A

  • 此时线程 1 处于挂起状态,线程 2 将位置 V 的值改成 B,最后再改成 A

  • 这时候线程 1 再执行,发现位置 V 的值没有变化,符合期望继续执行。

此时虽然线程 1还是成功了,但是这并不符合我们真实的期望,等于线程 2狸猫换太子线程 1耍了。

这就是所谓的ABA问题

解决方案

引入原子引用,带版本号的原子操作。

把我们的每一次操作都带上一个版本号,这样就可以避免ABA问题的发生。既乐观锁的思想。


  • 内存中的值每发生一次变化,版本号都更新。


  • 在进行CAS操作时,比较内存中的值的同时,也会比较版本号,只有当二者都没有变化时,才能执行成功。


  • Java中的AtomicStampedReference类便是使用版本号来解决ABA问题的。



高竞争下的开销问题



  • 在并发冲突概率大的高竞争环境下,如果CAS一直失败,会一直重试,CPU开销较大。


  • 针对这个问题的一个思路是引入退出机制,如重试次数超过一定阈值后失败退出。


  • 更重要的是避免在高竞争环境下使用乐观锁。


另外本人整理收藏了20年多家公司面试知识点整理 ,以及各种Java核心知识点免费分享给大家,想要资料的话请点(点击此处)来免费获取!


推荐阅读
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • XNA 3.0 游戏编程:从 XML 文件加载数据
    本文介绍如何在 XNA 3.0 游戏项目中从 XML 文件加载数据。我们将探讨如何将 XML 数据序列化为二进制文件,并通过内容管道加载到游戏中。此外,还会涉及自定义类型读取器和写入器的实现。 ... [详细]
  • 深入解析JVM垃圾收集器
    本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版,详细探讨了JVM中不同类型的垃圾收集器及其工作原理。通过介绍各种垃圾收集器的特性和应用场景,帮助读者更好地理解和优化JVM内存管理。 ... [详细]
  • Python 异步编程:深入理解 asyncio 库(上)
    本文介绍了 Python 3.4 版本引入的标准库 asyncio,该库为异步 IO 提供了强大的支持。我们将探讨为什么需要 asyncio,以及它如何简化并发编程的复杂性,并详细介绍其核心概念和使用方法。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 微软Exchange服务器遭遇2022年版“千年虫”漏洞
    微软Exchange服务器在新年伊始遭遇了一个类似于‘千年虫’的日期处理漏洞,导致邮件传输受阻。该问题主要影响配置了FIP-FS恶意软件引擎的Exchange 2016和2019版本。 ... [详细]
  • 本文详细介绍了 GWT 中 PopupPanel 类的 onKeyDownPreview 方法,提供了多个代码示例及应用场景,帮助开发者更好地理解和使用该方法。 ... [详细]
  • andr ... [详细]
  • 深入理解Java泛型:JDK 5的新特性
    本文详细介绍了Java泛型的概念及其在JDK 5中的应用,通过具体代码示例解释了泛型的引入、作用和优势。同时,探讨了泛型类、泛型方法和泛型接口的实现,并深入讲解了通配符的使用。 ... [详细]
  • 在Java中,this是一个引用当前对象的关键字。如何通过this获取并显示其所指向的对象的属性和方法?本文详细解释了this的用法及其背后的原理。 ... [详细]
  • 本文深入探讨了HTTP请求和响应对象的使用,详细介绍了如何通过响应对象向客户端发送数据、处理中文乱码问题以及常见的HTTP状态码。此外,还涵盖了文件下载、请求重定向、请求转发等高级功能。 ... [详细]
author-avatar
手机用户2602916737
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有