前言

听说经常面试被问到~今天同事说了这个问题,就查了一下这问题,觉得挺有意思的,就整理出来跟大家分享下。主要思考下面几个问题:

1、什么是CAS?

2、什么是CAS的ABA问题?

3、怎么解决这个问题?

一、什么是CAS?

CAS是compare and swap的缩写,也有说是compare and set的缩写,即我们所说的比较交换。cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。

二、什么是CAS的ABA问题?

并发的情况下:

1、线程1获取参数A

2、线程2获取参数A,并获取cpu的执行权,修改参数A为参数B。

3、线程2执行完毕,线程3获取cpu的执行权,修改参数B为参数A,并放入内存中。

4、线程1开始执行,判断原A和内存中的A相等,线程1把执行的结果存入该内存地址。

虽然原A和内存中的A相等,但是内存中的A已经不是以前的A了。。这样就会有问题

CAS造成CPU利用率增加。CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。

三、怎么解决这个问题?

解决办法就是增加一个版本号,每次操作,版本号+1,版本号不同,不允许修改。在java5中,已经提供了AtomicStampedReference来解决问题。

看AutoInteger的实现源码

其实AutoInteger就是使用了CAS来实现加1,我们知道如果有一个共享变量count=1,开5个线程,每个线程加20次,结果一般来说都会小于100.

@Testpublic void test20() throws InterruptedException { for (int i = 1; i <= 5; i++) { MyThrend thrend = new MyThrend("thead" + i); Thread thread = new Thread(thrend); thread.start(); } Thread.sleep(2000); System.out.println(MyCount1.count);}static class MyThrend implements Runnable { private String name; MyThrend(String threadName) { this.name = threadName; } @Override public void run() { for (int i=0;i<20;i++) MyCount1.count++; }}private static class MyCount1 { static int count = 0;}

运行结果小于100

现在修改一个代码,将int变成AtomicInteger

@Testpublic void test20() throws InterruptedException { for (int i = 1; i <= 5; i++) { MyThrend thrend = new MyThrend("thead" + i); Thread thread = new Thread(thrend); thread.start(); } Thread.sleep(2000); System.out.println(MyCount.count.get());}static class MyThrend implements Runnable { private String name; MyThrend(String threadName) { this.name = threadName; } @Override public void run() { for (int i=0;i<20;i++) MyCount.count.getAndIncrement(); //加1方法 }}private static class MyCount { static AtomicInteger count = new AtomicInteger(0);}

每次结果都是100,怎么做到的呢?这里是没有直接加锁的,看源码。

public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); //第一个参数当前对象地址,第二个参数数据偏移量,第三个参数每次指定默认加1}public final int getAndAddInt(Object var1, long var2, int var4) { //这个方法使用的就是CAS,核心在于循环比较内存里面的值和当前值是否相等,如果相等就用新值覆盖 int var5;  do { var5 = this.getIntVolatile(var1, var2); //如果a,b线程同时执行这个方法,a线程拿到值1后cpu执行时间到了挂起,b开始执行,也拿到1,但是没有挂起,接着将值变成了2 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); //这个时候a线程恢复执行,去比较的时候发现手上的1 和内存里面的值2不等,这个时候他要进行下一个循环,看出来了占用cpu吧 return var5;}

java.util.current.atomic包下面,里面有很多类采用了CAS机制来实现加锁,可以自己研究下。

四、扩展和思考

一个商品库存为10个, 并发的情况下,A和B两个用户同时购买,他们查出来的数据都是10个,A用户购买5个,B用户购买3个。如果根据主键去更新数据,A用户把数据修改为5个,而B用户把数据修改7个,这样数据就不正确了。

思考一:

我们可以根据主键和商品数量是否等于查出来原始数量去跟新

update stock set num=$num_new where sid=$sid升级为:update stock set num=$num_new where sid=$sid and num=$num_old

但是,并发情况下,在B修改之前,C又把库存改为了10,这个时间就是典型的ABA问题了,虽然都是10,但是含义已经不一样,直接修改可能会影响别的业务。

思考二:

添加一个版本号,每次修改数据的同时修改版本号。例如

旧版本“值”比对CASupdate stock set num=$num_new where sid=$sid and num=$num_old升级为“版本号”比对CASupdate stock set num=$num_new, version=$version_new where sid=$sid and version=$version_old

我觉得上面得这个例子更能让大家理解,就添加进来了。

java购买同一件商品时加锁_java中CAS的ABA问题思考和整理(不看后悔系列)相关推荐

  1. c++某商店开展买一送一活动,购买两件商品时,只需支付价格较高的商品的金额。要求程序在输入两个商品的价格后,输出所应支付的金额,请根据裁判程序编写函数cut,将代码补充完整。

    #include using namespace std; //请在此处添加代码 int cut(float x, float y) { float max; max = (x > y) ? x ...

  2. java动态加载jar时,jar中还有第三方jar无法加载的解决方法

    java动态加载jar时,jar中还有第三方jar无法加载的解决方法 当java插件化开发时,即一个java程序在运行的情况下动态加载另一个jar,网上大多数的方法如下 public static v ...

  3. java.lang类在电脑哪个位置_Java中的java.lang.Class API 详解

    且将新火试新茶,诗酒趁年华. 概述 Class是一个位于java.lang包下面的一个类,在Java中每个类实例都有对应的Class对象.类对象是由Java虚拟机(JVM)自动构造的. Class类的 ...

  4. java可比较的和比较器的区别_Java中Compareable和Comparator两种比较器的区别

    对于JDK8而言,有三种实现对象比较的方法: 1.在需要比较的对象类中覆写Object类的equals()方法: 2.需要比较的类继承Comparable接口,然后在其类内部实现compareTo() ...

  5. java 抽象类与接口区别是什么_JAVA中抽象类与接口的区别,分别在什么情况下使用它们...

    在网上看到很多人问关于"抽象类与接口的区别",因此本人想通过自己多年对JAVA开发的经验来总结一下抽象类与接口的区别以及分别在什么情况下使用它们. 在Java语言中, abstra ...

  6. Java传统的io和nio区别_Java中IO和NIO的本质和区别

    简介 终于要写到java中最最让人激动的部分了IO和NIO.IO的全称是input output,是java程序跟外部世界交流的桥梁,IO指的是java.io包中的所有类,他们是从java1.0开始就 ...

  7. java 继承 实现 会重写 方法吗_java 中继承,组合,重载,重写的实现原理 (转)...

    我们知道,继承,组合,重载,重写是java语言的面向对象实现的基本特征. 那么在java内部,究竟是如何实现这些面对对象的基本特征的呢? 继承和组合是面向对象中代码复用的主要实现方式,他们可以达到类似 ...

  8. Java所有函数都是动态的_Java中的函数动态调用

    //------------------------------------- //类ArgumentHolder //用于调用参数的封装,实现变长参数及 //不同类型参数的统一形式地传递 //成员变 ...

  9. java中怎么给方法加锁_Java中,我会用ArrayList,怎么还要会用CopyOnWriteArrayList

    前言 之前的文章已经说过了java开发中,保存集合数据是用ArrayList还是LinkedList,了解下:Java编程中我该用ArrayList还是LinkedList? 平常面试过程中问的最多的 ...

最新文章

  1. 阿里妈妈技术团队 6 篇论文入选 CIKM 2021
  2. STL源码剖析面试问题
  3. 织梦缩略图自动补齐绝对路径_[教程]织梦CMS缩略图和文章内容图片自动转化为带域名的绝对路径...
  4. 反向传播网络(BP 网络)
  5. 样本打散后计算单特征 NDCG
  6. sqlite如何与mysql连接数据库连接_c#中怎么连接到sqlite数据库?
  7. 【礼仪大赛常识】 女人宴会搭配必学的礼服文化
  8. 凸优化第九章无约束优化 9.4最速下降方法
  9. 11.收货地址模块-新增收货地址①
  10. C++ 字符编码转换之UTF-8/UTF-16/UTF-32
  11. 信捷PLC远程上下载程序,远程控制
  12. 学计算机办公文员软件,办公文员必须掌握的办公软件有哪些
  13. fop生成pdf的中文乱码问题
  14. 【开学了】整理各种常用功能的实现 | 小游戏源码分享
  15. 如何用本地连接共享无线网络连接
  16. 计算机毕业设计(17)python毕设作品之鲜花水果销售系统
  17. 【美股】详解美股中的几种交易单-限价单、市价单、止损单、止损限价单、跟踪止损单
  18. python字符串知识点_python字符串的相关知识点
  19. 好用的虚拟光驱Alcohol120%序列号
  20. 那十月,我们一起追过的c++代码

热门文章

  1. mvn -DskipTests和-Dmaven.test.skip=true区别
  2. Linux集群和自动化维1.3 如何根据服务器应用选购服务器
  3. JavaScript面试大全(二)
  4. CentOS6.5安装python2.7.6(慎重升级)
  5. 淘宝技术发展(引言)、技术发展(个人网站)
  6. C#语言-NPOI.dll导入Excel功能的实现
  7. Coherence装载数据的研究-PreloadRequest
  8. 今日早上出来还是阴天
  9. [CoffeeScript]使用Yield功能
  10. windows linux—unix 跨平台通信集成控制系统