Java并发编程之CAS

在Java并发编程的世界里,synchronized 和 Lock 是控制多线程并发环境下对共享资源同步访问的两大手段。其中 Lock 是 JDK 层面的锁机制,是轻量级锁,底层使用大量的 自旋+CAS操作实现的。

学习并发推荐《Java并发编程的艺术》

那什么是CAS呢? CAS,compare and swap,即比较并交换,什么是比较并交换呢?在Lock锁的理念中,采用的是一种乐观锁的形式,即多线程去修改共享资源时,不是在修改之前就加锁,而是乐观的认为没有别的线程和自己争锁,就是通过CAS的理念去保障共享资源的安全性的。CAS的基本思想是,拿变量的原值和内存中的值进行比较,如果相同,则原值没有被修改过,那么就将原值修改为新值,这两步是原子的,能够保证同一时间只有一个线程修改成功。这就是CAS的理念。

Java中要想使用CAS原子的修改某值,怎么做呢?幸运的是Java提供了这样的API,就是在sun.misc.Unsafe.java类中。Unsafe,中文名不安全的,也被称为魔术类,魔法类。

Unsafe类介绍

Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,一旦能够直接操作内存,这也就意味着
(1)不受JVM管理,意思就是使用Unsafe操作内存无法被JVM GC,需要我们手动GC,稍有不慎就会出现内存泄漏。
(2)Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,并且偏移量要自己计算(其提供的有计算偏移量的方法),所以一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉。
(3)直接操作内存,所以速度更快,在高并发的条件之下能够很好地提高效率。

因此,从上面三个角度来看,虽然在一定程度上提升了效率但是也带来了指针的不安全性。这也是它被取名为Unsafe的原因吧。

下面我们深入到源码中看看,提供了什么方法直接操作内存。

打开Unsafe这个类,我们会发现里面有大量的被native关键字修饰的方法,这意味着这些方法是C语言提供的实现,底层调的是C语言的库函数,我们无法直接看到他的源码实现,需要去从OpenJDK去看了。另外还有一些基于native方法封装的其他方法,整个Unsafe中的方法大致可以归结为以下几类:
(1)初始化操作
(2)操作对象属性
(3)操作数组元素
(4)线程挂起和恢复
(5)CAS机制

CAS的使用

如果你学过java并发编程的话,稍微阅读过JUC并发包里面的源码的话,对这个Unsafe类一定不陌生,因为整个java并发包底层实现的核心就是靠它。JUC并发包中主要使用它提供的CAS(compare and swap,比较并交换)操作,原子的修改锁的状态和一些队列元素。

首先,使用这个类我们第一个要做的事情就是拿到这个类的实例,下面我们自定义了一个Util类用来获取Unsafe的实例

这个工具类通过反射的方式拿到Unsafe类中的一个名为theUnsafe字段,该字段是Unsafe类型,并在static块中new一个Unsafe对象初始化这个字段(单例模式)。

然后我们定义了一个AtomicState类,这个类很简单,有一个int型的state字段,还有一个Unsafe的常量,以及int型的offsetState,用来记录state字段在AtomicState对象中的偏移量。具体代码如下:

我们定义了一个 compareAndSetState方法,需要传两个参数,分别是state的旧值和新值,也就是读到的state的之前的值,以及想要把它修改成什么值,该方法内部调用的是Unsafe类的 compareAndSwapInt方法,它有四个参数,分别是要修改的类实例对象、要修改的值的偏移量、旧值、新值。解释一下偏移量,刚才我们提到Unsafe提供给我们直接访问内存的能力,那么访问内存肯定是要知道内存的地址在哪才能去修改其相应的值吧,我们看,第一个参数是对象实例引用,也就是说,已经知道这个对象的地址了,那么我们想修改这个对象里的state的值,就只需要计算出state在这个对象的偏移量就能找到state所在的内存地址,那就可以修改它了。

然后,我们通过一个测试类来验证Unsafe的CAS操作。这个测试类我来解释下大致的思想,我们弄5个线程,让这个5个线程一个个启动,我们无法保证线程同时开始启动,那么我们有办法保证这个5个线程同时执行我们的代码,就是使用JUC包里的 CyclicBarrier工具来实现的,这个工具初始化时需要传入一个int值n,我们在线程的run方法内部在业务代码执行之前调用 CyclicBarrier的await方法,当指定数量n的线程都调用了这个方法那么这n个线程将同时往下执行,就像设置了一个屏障,所有人都达到这个屏障后,一起通过屏障,依次来模拟多线程并发

cyclicBarrier.await();之后我们调用 AtomicStatecompareAndSetState方法传入旧值0和新值,新值就是线程名t-n中的n,哪个线程修改成功,最后state值就是线程名中的数字。
至于 CountDownLatch使用它的目的是让mian线程等到t-1到t-5的线程全部执行完后打印state的值。我们的重点不是 CyclicBarrierCountDownLatch,知道它们是干什么的就行。

然后我们运行这个测试程序:

可以看到只有一个线程执行成功,这就是CAS的基本使用。

CAS的ABA问题

何为ABA问题呢?举个例子,小明和小花合伙卖煎饼,不就后攒了10万元,他们一起去银行把钱存在他们公共的账户里,但是小明听说最近牛市来了,就偷偷的把钱转移到了股票市场,公共账户余额是0。1个月后股票赚了一笔钱,然后小明把之前转移的10万元又存到他们的公共账户。小明和小花一个月后又去存钱,去查账户余额是10万。这就是ABA问题,简单来说就是一个值本来是A,两个线程同时都看到是A,然后线程1把A改成B后又改成A,线程1结束了。然后线程2去修改时,看到的是A,无法感知到这个过程中值发生过变化,对于线程2来说就发生了ABA的问题。

模拟ABA问题:

怎么解决CAS的ABA问题呢?
那就是基于版本号去解决,增加一个版本号的概念,每次被修改这个版本号就加1,版本号是一直向前的,版本号变了,就说明被修改过。

JUC包中提供了解决ABA问题的工具:

运行结果:

t2存款时就发现账户异常,因为版本号已经变成了3,和t2刚开始拿到的不一样,说明已经被别人修改过,从而解决ABA问题。

到这里CAS就完啦。别忘了 点赞,转发。

往期热文:

欢迎关注公众号,谢谢支持。

Original: https://www.cnblogs.com/ibigboy/p/13553400.html
Author: 问北
Title: Java并发编程之CAS

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/599113/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • MySQL之事务隔离级别和MVCC

    事务隔离级别 事务并发可能出现的问题 脏写 事务之间对增删改互相影响 脏读 事务之间读取其他未提交事务的数据 不可重复读 一个事务在多次执行一个select读到的数据前后不相同。因…

    数据库 2023年5月24日
    074
  • 线程简介

    线程简介以多线程在Windows操作系统中的运行模式为例:Windows操作系统是 多任务操作系统,它以进程为 单位。每个独立执行的程序都被称为 进程( 比如正在运行的QQ是一个进…

    数据库 2023年6月16日
    0115
  • mysql范式

    mysql范式: mysql建表的规范格式 第一范式:保证每列的原子性(字段不能再分解) 第一种范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,则数据库满足第一…

    数据库 2023年5月24日
    070
  • 获取本机MAC地址

    需添加引用:System.Management.dll //////public string GetMacAddress(){string mac =””…

    数据库 2023年6月11日
    072
  • 相同执行计划,为何有执行快慢的差别

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。 GreatSQL是MySQL的国产分支版本,使用上与MySQL一致。 前言 今天遇到一个很神奇的现象,…

    数据库 2023年5月24日
    075
  • 多态:向上转型和向下转型

    1)本质:父类的引用指向了子类的对象 2)语法:父类类型 引用名 = new 子类类型(); 3)特点:编译类型看左边,运行类型看右边。 可以调用父类中的所有成员(需遵守访问权限)…

    数据库 2023年6月11日
    080
  • 分库分表真的适合你的系统吗?聊聊分库分表和NewSQL如何选择

    曾几何时,”并发高就分库,数据大就分表”已经成了处理 MySQL 数据增长问题的圣经。 面试官爱问,博主爱写,考生爱背诵,似乎形成了一个闭环。 [En] I…

    数据库 2023年5月24日
    0101
  • 二叉树遍历的常用方法

    概述 二叉树的遍历可以说是解决二叉树问题的基础。我们常用的遍历方式无外乎就四种 前序遍历、 …

    数据库 2023年6月11日
    072
  • MySQL InnoDB缓存

    1. 背景 对于各种用户数据、索引数据等各种数据都是需要持久化存储到磁盘,然后以”页”为单位进行读写。 相对于直接读写缓存,磁盘IO的成本相当高昂。 对于读…

    数据库 2023年6月14日
    0141
  • Redis集群(三)集群模式

    一、 集群的作用 集群,即Redis Cluster,是Redis 3.0开始引入的分布式存储方案。 集群由多个节点(Node)组成,Redis的数据分布在这些节点中。集群中的节点…

    数据库 2023年6月11日
    077
  • 23种设计模式之状态模式和策略模式的区别

    文章目录 概述 状态模式 策略模式 区别 总结 概述 在行为类设计模式中,状态模式和策略模式是亲兄弟,两者非常相似,我们先看看两者的通用类图,把两者放在一起比较一下 ; 状态模式 …

    数据库 2023年6月6日
    0112
  • SQL练习六–More JOIN operations

    Field nameTypeNotes id INTEGER An arbitrary unique identifier title CHAR(70) The name of t…

    数据库 2023年6月16日
    075
  • MySQL Bug:No suitable ‘keyring_component_metadata_query’ service implementation found to fulfill the request

    监控发现公司一台MySQL 8.0.26 的错误日志中出现大量下面告警信息: [Warning] [MY-013712] [Server] No suitable ‘k…

    数据库 2023年5月24日
    078
  • Hadoop生态二—Hadoop资源管理调度平台Yarn

    Yarn是一个资源调度平台,负责为运算程序提供服务器运算资源,相当于一个分布式的操作系统平台,而mapreduce等运算程序则相当于运行于操作系统之上的应用程序Yarn是一个资源调…

    数据库 2023年6月6日
    0106
  • 存储过程procedure、触发器trigger

    一、存储过程procedure MySQL 5.0 版本开始支持存储过程。 存储过程(Stored Procedure)是一种在数据库中存储复杂程序,以便外部程序调用的一种数据库对…

    数据库 2023年6月9日
    071
  • Golang 接口(interface)

    Go 语言的接口遵守LSP(里氏替换原则),即 一个类型可以自由地被另一个满足相同接口的类型替换。 接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的…

    数据库 2023年6月16日
    070
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球