目录

写在前面

一、初识CAS(比较并交换)

二、CAS原理(自旋锁、unsafe类)

三、CAS是什么

四、CAS缺点

五、ABA问题


写在前面

相信很多小伙伴对乐观锁、悲观锁都不陌生,但是说到java的cas,就蒙圈了。

那么到底什么是CAS呢?

一、初识CAS(比较并交换)

/**
* CAS
*/
public class CASDemo {public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(5);/* compareAndSet(int expect, int update)两个参数 期望值,修改值假如说主物理内存的值与expect的值相等,则修改成功,否则修改失败。*/System.out.println(atomicInteger.compareAndSet(5, 2021) + "现在值是" + atomicInteger.get());/*因为主物理内存的值已经变成了2021,再由5修改为2022,会执行失败返回false*/System.out.println(atomicInteger.compareAndSet(5, 2022) + "现在值是" + atomicInteger.get());}
}

二、CAS原理(自旋锁、unsafe类)

1.之前解决i++不安全的问题,用到了一个这个方法:

public final int getAndIncrement() {// this:当前对象。// valueOffset:内存偏移量(内存地址)// 1 加1return unsafe.getAndAddInt(this, valueOffset, 1);
}

2.源码:

3.源码解析

①Unsafe

是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。

注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。

②变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

③变量value用volatile修饰,保证了多线程之间的内存可见性。

三、CAS是什么

1.    CAS的全称为Conpare-And-Swap,它是一条CPU并发原语。

它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。

CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓我的数据不一致问题。

2.源码解读:

//一直循环,1.先得到当前数据,2.再进行比较并交换,3.如果交换成功就退出,交换失败就重新比较交换,直至成功。4.返回的是原值相当于i++

3.var解释:

var1:AtomicInteger对象本身。

var2:该对象值的引用地址。

var4:需要变动的数量。

var5:是用var1 var2找出的主内存中真实的值。

用该对象当前的值与var5比较:

如果相同,更新var5+var4并且返回true,如果不同,继续取值然后比较,直到更新完成。

为什么用CAS而不用synchronized?

synchronized加锁,每次只有一个线程能访问资源。并发性下降。

CAS使用自旋锁,不需要手动加锁。

4.CAS线程时冲突执行步骤:假设线程A和线程B两个线程同时执行getAndAddInt操作(分别跑在不同CPU上):

1.AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存。

2.线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。

3.线程B也通过getIntVolatile(var1, var2)方法获取到value值3,此时刚好线程B没有被挂起并执行compareAbdSwapInt方法比较内存值也为3,成功修改内存值为4,线程B打完收工一切OK。

4.此时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值3和主内存的值4不一致,说明该值已经被其他线程抢先一步修改过了,那A线程本次修改失败,只能重新读取重新来一遍了。

5.线程A重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较交换,直到成功。

5.

6.CAS小总结

CAS(CompareAndSwap)比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。

CAS应用:CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

四、CAS缺点

1.循环时间长,开销大

我们可以看到getAndAddInt方法执行时,有个do-while。

如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

2.只能保证一个共享变量的原子操作。

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

3.引出来ABA问题。

五、ABA问题

1.ABA问题是怎么产生的

CAS会导致“ABA问题”。

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程1从内存位置V中取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作将值变成了B,然后线程2又将值变成A。此时线程1进行CAS操作发现内存中仍然是A,然后线程1操作成功。

尽管线程1的CAS操作成功,但是不代表这个过程就是没有问题的。

中间过程如果不介意别人动过,那无所谓。

中间过程别人不能动,那就有问题了。

2.解决ABA问题(加版本号)

①原子引用

import java.util.concurrent.atomic.AtomicReference;
/**
* 原子引用
* 如果想包装某个类,就用原子引用
*/
public class AtomicReferenceDemo {public static void main(String[] args) {User u1 = new User("zhangsan", 14);User u2 = new User("lisi", 15);AtomicReference<User> atomicReference = new AtomicReference<>();atomicReference.set(u1);// true 设置为lisiSystem.out.println(atomicReference.compareAndSet(u1, u2) + "当前值" +atomicReference.get().getName());// false 还是lisiSystem.out.println(atomicReference.compareAndSet(u1, u2) + "当前值" +atomicReference.get().getName());}
}class User{private String name;private Integer age;public User(String name, Integer age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}
}

②时间戳的原子引用    ABA问题的彻底解决

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;/**
* ABA问题的解决
*/
public class ABADemo {static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);// 初始化值、版本号static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);public static void main(String[] args) {// 1.以下是ABA问题的产生new Thread(() ->{atomicReference.compareAndSet(100, 101);atomicReference.compareAndSet(101, 100);}, "t1").start();new Thread(() -> {try {// t2线程暂停1秒,确保t1线程完成了ABA操作Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 只认值,不认是否经历过ABASystem.out.println(atomicReference.compareAndSet(100, 2019)+ ",t2当前值" + atomicReference.get());}, "t2").start();// 2.以下是ABA问题的解决new Thread(() -> {int stamp = atomicStampedReference.getStamp();// 版本号System.out.println("t3版本号" + stamp);// 暂停1秒钟t3线程,确保t4拿到相同的版本号try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 期望值、修改值、目前版本号、修改后版本号atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);}, "t3").start();new Thread(() -> {int stamp = atomicStampedReference.getStamp();// 版本号System.out.println("t4版本号" + stamp);// 暂停3秒钟t4线程,确保t3完成ABAtry {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}// 因为版本号已经被t3修改,所以这里比较并替换失败boolean b = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);System.out.println(b + ",t4" + atomicStampedReference.getStamp() + "," + atomicStampedReference.getReference());}, "t4").start();}
}

我知道乐观锁,但是我的确不知道CAS啊,到底什么是CAS相关推荐

  1. 乐观锁和悲观锁,可重入锁和不可重入锁(1)

    乐观锁和悲观锁,可重入锁和不可重入锁(1) 前言 感觉有一段时间没有写博客了呢.还是再接再厉吧,适当程度的总结能让我自己能够更加深入地巩固和理解自己所学习的一切. 还有,我很懒,而且我还是比较喜欢写日 ...

  2. 乐观锁的两种实现方式

    什么场景下需要使用锁? 在多节点部署或者多线程执行时,同一个时间可能有多个线程更新相同数据,产生冲突,这就是并发问题.这样的情况下会出现以下问题: 更新丢失:一个事务更新数据后,被另一个更新数据的事务 ...

  3. MySql(16)——Spring data jpa mysql 乐观锁 与 AtomicInteger

    场景: 某对象被访问,并累计访问次数 特点: 1.表中该对象初始没有纪录 2.该对象首次被访问后,为其建立一条纪录 3.此后每次被访问,访问次数++ 4.该对象在表中有且仅有一条纪录 分析一下这个场景 ...

  4. 一文彻底理解乐观锁与悲观锁

    通过阅读本文可以获得什么 1.什么是乐观锁? 2.乐观锁实现方式都有什么? 3.乐观锁优缺点有哪些? 4.乐观锁适用场景? 5.什么是悲观锁? 6.悲观锁实现方式有哪几种? 7.悲观锁优缺点? 8.悲 ...

  5. 多线程与高并发基础一(超发--多线程悲观锁,乐观锁、类数据库悲观锁乐观锁)

    PS:看完文章后对自己以前所做过的并发和锁机制有了深入原理的了解. 知其然和知其所以然! 遂以记之! 关键词: 线程,同步,单例,高并发,高访问,死锁 一.大规模并发带来的挑战 在过去的工作中,我曾经 ...

  6. 乐观锁和悲观锁的区别(最全面的分析)

    悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁.传统的关系型数据 ...

  7. mysql 乐观锁 脏读_mysql 丢失更新1和2、脏读、不可重复读和幻读 事务隔离级别 悲观锁 乐观锁...

    事务是现代关系型数据库的核心之一.在多个事务并发操作数据库(多线程.网络并发等)的时候,如果没有有效的避免机制,就会出现以下几种问题: ( 第一类丢失更新 A事务撤销时,把已经提交的B事务的更新数据覆 ...

  8. mybatisplus的详细使用(自动填充,乐观锁,分页,条件查询)

    1.自动填充 @Data @EqualsAndHashCode(callSuper = false) @TableName("t_user") public class User ...

  9. (13) 悲观锁和乐观锁解决hibernate并发(转)

    前言:  做项目时由于业务逻辑的需要,必须对数据表的一行或多行加入行锁,举个最简单的例子,图书借阅系统.假设 id=1 的这本书库存为 1 ,但是有 2 个人同时来借这本书,此处的逻辑为 Select ...

最新文章

  1. [SDOI2008]沙拉公主的困惑 线性筛 素数+欧拉
  2. C#窗体控件更新(五)
  3. C#字节数组与值类型变量的互相转换,以及注意事项
  4. 悲催的CamShift
  5. 【期望】彩色圆环(金牌导航 期望-5)
  6. leetcode 263. 丑数
  7. BZOJ2302 [HAOI2011]Problem c 【dp】
  8. 物联网安全白皮书_天翼物联网安全白皮书发布 有方科技参与编纂
  9. php中__FILE__常量用法简介
  10. 洛谷3916 图的遍历
  11. JS中的函数,Array对象,for-in语句,with语句,自定义对象,Prototype
  12. 蓝屏:微软撤回 Windows 8.1 八月更新等4个补丁
  13. Springboot入门1
  14. java程序设计基础_陈国君版第五版_第十章习题
  15. Windows 7 系统的旧版IE浏览器升级到IE11
  16. .net中使用ckeditor4+ckfinder上传图片
  17. 解读国内外园艺机器人的应用现状
  18. PDF如何翻译?看完这个方法就学会了
  19. VB程序界面设计经验点滴
  20. Android人脸检测方案汇总

热门文章

  1. php 对接 asp,PHP模拟asp中response类实现方法
  2. linux fedro版本查看命令,Fedora查看内核及发行版本号
  3. matlab矩阵对某一列求和,将矩阵中的每一列与另一列中的对应行相乘,然后在Matlab中求和...
  4. 机器学习cae_CAE工程分析技术年会记
  5. python 实现简单查询页面_python web 实现简易天气查询
  6. php修改html,关于html:用PHP设置innerHTML?
  7. python tableview绑定字典_TableView索引的添加
  8. firewall-cmd命令管理防火墙
  9. 如果服务器开机显示NObootable,电脑不能开机提示No bootable device怎么办?
  10. 网络安全等级保护测评高风险判定指引_等保知识|测评高风险项详解:安全管理中心...