14.CAS原理

文章目录

  • CAS原理
    • 1.什么是CAS
    • 2.Unsafe类中的CAS方法
      • 2.1.获取UnSafe实例
      • 2.2.调用UnSafe提供的CAS方法
      • 2.3.调用Unsafe提供的偏移量相关
      • 2.4.CAS无锁编程
      • 2.4.1.使用cas进行无锁安全自增案例

CAS原理

由于JVM的synchronized重量级锁设计操作系统内核态下的互斥锁的使用,其线程的阻塞和唤醒在内核态和用户态频繁切换,导致重量级锁的开销很大,性能低。JVM为synchronized提供了轻量级锁,通过CAS(compare and swap)比较交换,进行自旋抢锁,CAS是CPU指令级别的原子操作,并且处于用户状态下,开销比较小。

下面我们来了解一下什么是CAS

1.什么是CAS

JDK 5 所增加的 JUC(java.util.concurrent)并发包对操作系统的底层的CAS原子操作进行了封装,为上层Java程序提供了CAS操作的API

CAS(Compare and Swap)是一种并发编程中的原子操作,用于实现多线程环境下的无锁同步。CAS操作包含三个参数:内存位置(或者说是要操作的变量的引用)、期望值新值

CAS操作的执行过程如下:

  1. 首先,读取内存位置的当前值,这是期望值。
  2. 然后,将期望值与内存位置的当前值进行比较。如果相等,则说明内存位置的值没有被其他线程修改,可以进行更新操作。
  3. 如果相等,将内存位置的当前值修改为新值。如果不相等,说明内存位置的值已经被其他线程修改,CAS操作失败。
  4. 最后,CAS操作返回执行结果,通常是一个布尔值,表示操作是否成功。

CAS操作是原子的,意味着整个操作在执行期间不会被其他线程中断。如果多个线程同时执行CAS操作,只有一个线程会成功,其他线程会根据操作结果进行重试或进行其他处理。

2.Unsafe类中的CAS方法

Unsafe是位于sum.misc包下面的一个类,主要提供一些执行级别低,不安全的底层操作逻辑。如直接访问系统内存资源,自主管理内存资源等。

Unsafe中的大量方法都是原生(naticve)方法,基于C++语言实现,这些方法在提升Java运行效率上起了很大作用,但是在一般的开发中不会涉及此类,Java官方也不建议直接在应用程序中使用。

操作系统层面的CAS是一条CPU原子指令(compxchg)指令,UnSafe提供的CAS方法直接通过native方式(封装的C++代码)调用了底层CPU指令的compxchg。

在Java应用层,CAS操作主要是通过调用sun.misc.Unsafe类中的方法来实现的。Unsafe类提供了一些底层的、直接操作内存和执行CAS操作的方法。下面是使用Unsafe类进行CAS操作的一般流程:

  1. 获取Unsafe实例
    Unsafe类的构造函数是私有的,因此无法直接实例化它。通常可以通过反射或者调用Unsafe.getUnsafe()方法来获取Unsafe的实例。
  2. 获取要操作的变量的偏移量
    在执行CAS操作之前,需要获取要操作的变量在内存中的偏移量。偏移量表示变量相对于对象头的位置。可以使用Unsafe.objectFieldOffset()方法来获取变量的偏移量。
  3. 执行CAS操作
    通过调用Unsafe.compareAndSwapXXX()方法来执行CAS操作,其中XXX表示要操作的数据类型(如IntLongObject等)。compareAndSwapXXX()方法接收四个参数:要操作的对象、变量的偏移量、期望值和新值。方法会比较对象内存中偏移量处的值与期望值是否相等,如果相等,则将该位置的值更新为新值,并返回操作是否成功的布尔值。
  4. 锁定和解锁
    在执行CAS操作期间,不需要使用锁来保护共享资源,因为CAS操作本身是原子的。它使用底层硬件指令实现的原子性保证。因此,CAS操作可以避免传统的锁机制所带来的开销和竞争。

需要注意的是,使用Unsafe类进行CAS操作需要谨慎,因为直接操作内存可能会导致不安全的结果。此外,Unsafe类在Java 9中被标记为不推荐使用,并且在未来的版本中可能会被移除。因此,在实际应用中,建议使用更高级的并发工具和类库,如java.util.concurrent.atomic包中的原子类来实现线程安全的操作

2.1.获取UnSafe实例

@Test
@DisplayName("获取UnSafe实例")
public void test() {
    try {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        log.error("unsafe : {}",unsafe);
    } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

在这里插入图片描述

2.2.调用UnSafe提供的CAS方法

Unsafe类中的提供了三个CAS操作方法:compareAndSwapObject()compareAndSwapInt()compareAndSwapLong()

这三个方法的作用是原子地更新Java变量的值,只有在当前值等于期望值时才进行更新。它们的共同特点是具有volatile读写的内存语义

  • compareAndSwapObject()方法用于原子地更新对象引用类型的变量。它接收四个参数:要操作的对象、变量的偏移量、期望值和新值。如果对象内存中偏移量处的值等于期望值,则将该位置的值更新为新值,并返回操作是否成功的布尔值。

    • @ForceInline
      public final boolean compareAndSwapObject(Object o, long offset,
                                                Object expected,
                                                Object x) {
          return theInternalUnsafe.compareAndSetReference(o, offset, expected, x);
      }
      
  • compareAndSwapInt()方法用于原子地更新int类型的变量。它的参数和行为与compareAndSwapObject()方法类似,只是操作的数据类型不同。

    • @ForceInline
      public final boolean compareAndSwapInt(Object o, long offset,
                                             int expected,
                                             int x) {
          return theInternalUnsafe.compareAndSetInt(o, offset, expected, x);
      }
      
  • compareAndSwapLong()方法用于原子地更新long类型的变量。它的参数和行为与compareAndSwapObject()方法类似,只是操作的数据类型不同。

    • @ForceInline
      public final boolean compareAndSwapLong(Object o, long offset,
                                              long expected,
                                              long x) {
          return theInternalUnsafe.compareAndSetLong(o, offset, expected, x);
      }
      

这些CAS操作方法在底层都使用了Unsafe类内部的方法,如compareAndSetReference()compareAndSetInt()compareAndSetLong(),这些方法是基于硬件指令实现的原子操作。

UnSafe的CAS操作会将 第一个参数(对象的指针,地址)和第二个参数(字段偏移量)组合一起,计算出最终内存操作地址

2.3.调用Unsafe提供的偏移量相关

Unsafe类提供了两个方法来获取字段的偏移量:staticFieldOffset()objectFieldOffset()

  1. staticFieldOffset()

    @ForceInline
    public long staticFieldOffset(Field f) {
        if (f == null) {
            throw new NullPointerException();
        }
        Class<?> declaringClass = f.getDeclaringClass();
        if (declaringClass.isHidden()) {
            throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f);
        }
        if (declaringClass.isRecord()) {
            throw new UnsupportedOperationException("can't get field offset on a record class: " + f);
        }
        return theInternalUnsafe.staticFieldOffset(f);
    }
    // 最终调用 
    private native long staticFieldOffset0(Field f);
    
    

    staticFieldOffset()方法用于获取静态字段的偏移量。它接收一个Field对象作为参数,表示要获取偏移量的字段。该方法返回一个long类型的值,表示字段在内存中的偏移量。

    在使用staticFieldOffset()方法之前,需要确保传入的字段对象不为null。方法内部还会进行一些额外的检查,例如检查字段所在的类是否是隐藏类或记录类。

  2. objectFieldOffset()

    @ForceInline
    public long objectFieldOffset(Field f) {
        if (f == null) {
            throw new NullPointerException();
        }
        Class<?> declaringClass = f.getDeclaringClass();
        if (declaringClass.isHidden()) {
            throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f);
        }
        if (declaringClass.isRecord()) {
            throw new UnsupportedOperationException("can't get field offset on a record class: " + f);
        }
        return theInternalUnsafe.objectFieldOffset(f);
    }
    
    // 最终调用 
    private native long objectFieldOffset0(Field f);
    

    objectFieldOffset()方法用于获取对象字段的偏移量。它的使用方式和staticFieldOffset()方法类似,接收一个Field对象作为参数,并返回字段的偏移量。

    同样,使用objectFieldOffset()方法之前,需要确保传入的字段对象不为null。方法内部也会进行类的隐藏性和是否为记录类的检查。

这些偏移量可以在CAS操作中使用,通过偏移量可以直接访问和修改字段的值,而无需通过对象引用。但是需要注意的是,直接使用偏移量进行字段操作需要非常小心,因为它绕过了Java语言的访问控制机制,可能会导致不安全或破坏封装性的操作。在正常情况下,应该使用正式的访问方法(getter和setter)来访问和修改字段的值。

2.4.CAS无锁编程

CAS是一种无锁算法,该算法依赖两个关键值,期望值新值,底层的CPU利用原子操作判断内存的原值和期望的值是否相等,如果相等,就会给内存的地址赋上新值,否则不做任何操作。

对于CAS操作,可以通过以下三个步骤来说明其流程:

  1. 获取期望值
    CAS操作首先从内存中读取变量的当前值作为期望值。这个期望值是在进行CAS操作之前由应用程序指定的。它用于比较内存中的原值是否与期望值相等。
  2. 计算需要替换值
    如果内存中的原值与期望值相等,说明当前变量的值满足CAS操作的条件。在这种情况下,应用程序可以计算出需要替换的新值。这个新值可能基于当前的变量值和其他相关的计算逻辑。
  3. 更新值
    在CAS操作中,如果内存中的原值与期望值相等,CAS操作会将计算得到的新值尝试写入内存。这个更新操作是原子的,它通过底层的硬件指令保证了操作的原子性和线程安全性。如果更新成功,说明CAS操作成功,变量的值已被更新为新值;否则,说明在CAS操作期间其他线程修改了变量的值,CAS操作失败,需要重新尝试或执行其他的处理逻辑。

下面我们通过一个简单案例来了解一下CAS的一个流程

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.4.1.使用cas进行无锁安全自增案例

package com.hrfan.java_se_base.base.thread.cas.unsafe;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.Unsafe;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class SpinLockTest {
    private static final Logger log = LoggerFactory.getLogger(SpinLockTest.class);

    private static final Unsafe unsafe = getUnsafe();
    private static final long valueOffset;
    private volatile int value = 0;
    private AtomicInteger failCount = new AtomicInteger(0);

    static {
        try {
            valueOffset = unsafe.objectFieldOffset(SpinLockTest.class.getDeclaredField("value"));
            log.error("cas偏移量:{}", valueOffset);
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        }
    }

    private static Unsafe getUnsafe() {
        try {
            java.lang.reflect.Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            throw new RuntimeException("Failed to obtain Unsafe instance", e);
        }
    }



    // 自旋进行等待 直到赋值成功!
    public void incrementAndPrint() {
        // 使用Unsafe的compareAndSwapInt方法进行自增操作
        int oldValue, newValue;
        do {
            // 获取旧的值
            oldValue = unsafe.getIntVolatile(this, valueOffset);
            // 设置新的值
            newValue = oldValue + 1;
        } while (!unsafe.compareAndSwapInt(this, valueOffset, oldValue, newValue));
    }

    public static void main(String[] args) throws InterruptedException {
        SpinLockTest lock = new SpinLockTest();
        CountDownLatch latch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    lock.incrementAndPrint();
                }
                latch.countDown();
            }).start();
        }

        // 等待全部线程执行完毕
        latch.await();
        // 输出最终结果值
        log.error("最终累加值:{}", lock.value);
    }
}

在这里插入图片描述

注意 为什么每次的偏移量都是12呢?
在 Java 中,每个对象的开头都有一个称为 Mark Word 的特殊字段,用于存储对象的一些标记和状态信息。Mark Word 的大小通常是机器字大小的整数倍,它的确切大小会随着 JVM 的具体实现而有所不同。

在大多数情况下,Mark Word 包含了对象的哈希码、锁状态、GC 信息等。在使用 Unsafe 类进行对象操作时,通过 objectFieldOffset 方法获取到的偏移量实际上是指向了对象中某个字段的相对位置,而这个相对位置是相对于对象起始地址的偏移量。

由于 Mark Word 是对象的头部信息,它的大小会影响到对象中其他字段的偏移量。具体来说,由于 Mark Word 的存在,对象中第一个字段的偏移量通常会是 Mark Word 的大小,因此每次获取对象中字段的偏移量时,相对于对象起始地址的偏移量会是固定的,也就是 Mark Word 的大小。

因此,每次获取的偏移量都是固定的值,是 Mark Word 的大小。其实就是 value属性的内存位置紧挨着Object Header之后,所以value属性的相对偏移量都是 12

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/619606.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多剖面土壤墒情监测仪

TH-GTS04在农业生产中&#xff0c;土壤墒情是影响作物生长的关键因素之一。为了更好地了解土壤的水分状况&#xff0c;为农业生产提供科学依据&#xff0c;多剖面土壤墒情监测仪应运而生。这种先进的监测设备具有多项功能优势&#xff0c;为土壤水分的精准监测提供了有力支持。…

火绒安全原理、用法、案例和注意事项

火绒安全是一款功能强大的安全软件&#xff0c;它采用了先进的安全技术和算法&#xff0c;通过实时监测、恶意代码识别、防火墙功能、沙箱技术和网络保护等多种手段&#xff0c;为用户提供全面的计算机安全防护。 1.为什么选用火绒安全&#xff1f; 火绒安全是一款优秀的安全软…

通过内网穿透实现远程访问个人电脑资源详细过程(免费)(NatApp + Tomcat)

目录 1. 什么是内网穿透 2. 内网穿透软件 3. NatApp配置 4. 启动NatApp 5. 通过内网穿透免费部署我们的springboot项目 通过内网穿透可以实现远程通过网络访问电脑的资源&#xff0c;本文主要讲述通过内网穿透实现远程访问个人电脑静态资源的访问&#xff0c;下一章节将讲…

实验10配置 IPv4 和 IPv6 静态和 默认路由(课内实验)

上面这个是实验描述 下面是给的实验图 接下来我们跟着实验一步一步进行下去 第 1 部分&#xff1a;配置 IPv4 静态和 浮动静态默认路由配置ipv4静态路由&#xff1a;配置 IPv4静态和 浮动静态默认路由 步骤 1&#xff1a;配置一条 IPv4 静态 默认路由。在 Edge_Router 上&am…

Leetcode经典题目之用队列实现栈

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 目录 1、题目展示2、题目分析3、完整代码演示4、结语 1、题目展示 前面我们了解过如何实现队列…

使用 Flask Blueprint 实现模块化 Web 应用

文章目录 1. 什么是 Flask Blueprint&#xff1f;2. 为什么要使用 Flask Blueprint&#xff1f;3. 如何使用 Flask Blueprint&#xff1f;4. 在 Blueprint 之间进行通信5. 结合 Flask 插件系统进行功能拓展结语 当构建大型 Flask Web 应用时&#xff0c;保持代码的组织结构清晰…

深度缓冲技术在AI去衣中的神奇作用

引言&#xff1a; 随着人工智能技术的飞速发展&#xff0c;其在图形处理和视觉领域的应用日益增多。AI去衣技术便是其中一个颇具争议但又技术上引人入胜的话题。今天&#xff0c;我们将深入探讨一项关键技术——深度缓冲&#xff08;Depth Buffering&#xff09;&#xff0c;它…

Ubuntu 24 换国内源及原理 (阿里源 清华源 中科大源 网易源)

备份原文件 sudo cp /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.bak 编辑源文件 sudo gedit /etc/apt/sources.list.d/ubuntu.sources 粘贴到文本&#xff08;其中一个即可&#xff09;&#xff1a; &#xff08;阿里源&#xff09…

HTML静态网页成品作业(HTML+CSS+JS)——华为商城网页(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;使用Javacsript代码实现首页图片切换轮播效果&#xff0c;共有1个页面…

SQL-递归查询

运行环境&#xff1a; Mysql8以上&#xff0c;递归查询功能在8以上版本被正式引入 一、SQL递归查询的概念 递归指的是通过调用函数或过程或自身来解决问题的方法&#xff0c;常用于一些具有规律性循环的操作。SQL递归查询是基于一组初始数据&#xff0c;通过递归查询&#xf…

Tableau学习2.0版——复习

官网下载链接&#xff1a;https://www.tableau.com/zh-cn/support/releases 学生账户申请链接&#xff1a;https://www.tableau.com/zh-cn/academic/students。直接去学信网下载学籍在线验证作为申请证明。 目录 1、可视化原理 2、基础图表制作 2.1 对比分析&#xff08;比…

【持续更新中,图像分割数据集】字节发布 COCONut 入选 CVPR 2024,立即体验 Segment Anything 分割万物!|持续更新中!

随着计算机视觉技术的不断发展&#xff0c;图像分割在诸多领域展现出重要的应用价值。近年来&#xff0c;各种图像分割数据集如雨后春笋般涌现。上个月&#xff0c;字节跳动发布了首个大规模全景图像分割数据集「COCONut」&#xff0c;为这一领域的研究注入了新鲜血液。 HyperA…

【网络编程】Servlet的前后端练习 | 表白墙 | 前后端交互 | 提交消息 | 获取消息

文章目录 一、Servlet的前后端练习1.表白墙服务器要实现的逻辑&#xff1a;1.获取消息 &#xff1a;2.提交消息&#xff1a;完整前端代码&#xff1a;完整后端代码&#xff1a; 一、Servlet的前后端练习 1.表白墙 服务器要实现的逻辑&#xff1a; 1.页面加载时&#xff0c;网…

OBS直播二次开发_OBS直播软件介绍

OBS工作室版 免费且开源的用于视频录制以及直播串流的软件。 下载以在Windows, Mac以及Linux上简单且快速的开始串流。 功能 实时高性能的视频/音频捕捉与混合,以及无限的场景模式使您可以通过自定义实现无缝转换。为视频源设计的滤镜例如图片蒙版,色彩校正,色度/色彩键控…

java中的变量、数据类型、人机交互

变量 变量要素 1、类型&#xff1b;每一个变量都需要定义类型&#xff08;强类型&#xff09;其它语言有弱类型&#xff08;js&#xff09; 2、变量名&#xff1b; 3、存储的值&#xff1b; 声明方式&#xff1a; 数据类型 变量名 变量值&#xff1b; public static vo…

UDP怎么端口映射?

在网络通信中&#xff0c;TCP和UDP是两种常用的传输协议。UDP&#xff08;User Datagram Protocol&#xff09;是一种无连接的传输协议&#xff0c;相较于TCP协议来说&#xff0c;它更为轻量级且不可靠。UDP协议在某些场景下仍然有其独特的优势&#xff0c;尤其是在需要快速传输…

如何训练一个大模型:LoRA篇

目录 写在前面 一、LoRA算法原理 1.设计思想 2.具体实现 二、peft库 三、完整的训练代码 四、总结 写在前面 现在有很多开源的大模型&#xff0c;他们一般都是通用的&#xff0c;这就意味着这些开源大模型在特定任务上可能力不从心。为了适应我们的下游任务&#xff0c;…

用python写算法——队列笔记

1.队列定义 队列是一种特殊的线性表&#xff0c;它只允许在表的前端进行删除操作&#xff0c;在表的后端进行插入操作&#xff0c;和栈一样&#xff0c;队列是一种操作受限制的线性表。进行插入操作的端称为队尾&#xff0c;进行删除操作的端称为队头。队列中没有元素时&#…

C控制语句:分支和跳转

1.1if语句 //colddays.c --找出0摄氏度以下的天数占总天数的百分比 #include <stdio.h>int main(void) {const int FREEZING 0;float temperature;int cold_days 0;int all_days 0;printf("Enter the list of daily low temperature.\n");printf("Use…

Kexp 动态展示 k8s 资源对象依赖关系

kexp[1] 旨在以可视化的方式帮助用户理解和探索 Kubernetes 的能力。 适用场景&#xff1a; 学习和探索 Kubernetes 的功能。 应用开发&#xff0c;提供每个应用的对象图预设。 控制器和操作器的开发&#xff0c;支持动态对象图。 即将推出类似 Postman 的 Kubernetes API …