首页 > 其他分享 >CAS-并发

CAS-并发

时间:2023-04-04 19:46:04浏览次数:36  
标签:变量 修改 CAS 并发 num 线程 数组

CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。

一、前言

  了解了一下JDK1.8ConcurrentHashMap的实现,发现它实现的主要思想就是依赖于CAS机制。

JDK并发包里推出了一个ConcurrentHashMap,他默认实现了线程安全性
在JDK 1.7以及之前的版本里,分段
[数组1] , [数组2],[数组3] -> 每个数组都对应一个锁,分段加锁

JDK 1.8以及之后,做了一些优化和改进,锁粒度的细化
[一个大的数组],数组里每个元素进行put操作,都是有一个不同的锁,刚开始进行put的时候,如果两个线程都是在数组[5]这个位置进行put,这个时候,对数组[5]这个位置进行put的时候,采取的是CAS的策略
同一个时间,只有一个线程能成功执行这个CAS,就是说他刚开始先获取一下数组[5]这个位置的值,null,然后执行CAS,线程1,比较一下,put进去我的这条数据,同时间,其他的线程执行CAS,都会失败

分段加锁,通过对数组每个元素执行CAS的策略,如果是很多线程对数组里不同的元素执行put,大家是没有关系的,如果其他人失败了,其他人此时会发现说,数组[5]这位置,已经给刚才又人放进去值了
就需要在这个位置基于链表+红黑树来进行处理,synchronized(数组[5]),加锁,基于链表或者是红黑树在这个位置插进去自己的数据
如果你是对数组里同一个位置的元素进行操作,才会加锁串行化处理;如果是对数组不同位置的元素操作,此时大家可以并发执行的

二、正文

 2.1 乐观锁与悲观锁

  在讲CAS之前,先来理解两个概念,即乐观锁和悲观锁:

  • 乐观锁:在并发下对数据进行修改时保持乐观的态度,认为在自己修改数据的过程中,其他线程不会对同一个数据进行修改,所以不对数据加锁,但是会在最终更新数据前,判断一下这个数据有没有被修改,若没有被修改,才将它更新为自己修改的值;
  • 悲观锁:在并发下对数据进行修改时保持悲观的态度,认为在自己修改数据的过程中,其他线程也会对数据进行修改,所以在操作前会对数据加锁,在操作完成后才将锁释放,而在释放锁之前,其他线程无法操作数据;

  CAS其实就是乐观锁的一种实现方式,而悲观锁比较典型的就是Java中的synchronized。下面我就来详细介绍一下CAS的相关概念。


 2.2 什么是CAS?

  CAS全称compare and swap——比较并替换,它是并发条件下修改数据的一种机制,包含三个操作数:

  • 需要修改的数据的内存地址(V);
  • 对这个数据的旧预期值(A);
  • 需要将它修改为的值(B);

  CAS的操作步骤如下:

  1. 修改前记录数据的内存地址V;
  2. 读取数据的当前的值,记录为A;
  3. 修改数据的值变为B;
  4. 查看地址V下的值是否仍然为A,若为A,则用B替换它;若地址V下的值不为A,表示在自己修改的过程中,其他的线程对数据进行了修改,则不更新变量的值,而是重新从步骤2开始执行,这被称为自旋

  通过以上四个步骤对内存中的数据进行修改,就可以保证数据修改的原子性。CAS是乐观锁的一种实现,所以这里介绍的步骤和乐观锁的定义差不多,还是很好理解的。

 

 2.3 Java中CAS的使用

  Java中大量使用的CAS,比如,在java.util.concurrent.atomic包下有很多的原子类,如AtomicIntegerAtomicBoolean......这些类提供对intboolean等类型的原子操作,而底层就是通过CAS机制实现的。比如AtomicInteger类有一个实例方法,叫做incrementAndGet,这个方法就是将AtomicInteger对象记录的值+1并返回,与i++类似。但是这是一个原子操作,不会像i++一样,存在线程不一致问题,因为i++不是原子操作。比如如下代码,最终一定能够保证num的值为200

  我们看看incrementAndGet方法的源码:

  这里使用了一个unsafe对象,而unsafe对象是什么呢?我们知道,Java并不能像C或C++一样,直接操作内存,但是JVM为我们提供了一个后门,就是sun.misc.Unsafe类,这个类为我们实现了很多硬件级别的原子方法,当然,这些方法都是native方法,使用其他语言实现,而不是Java方法。而上面的另外一个变量valueOffset就是我们需要修改的变量在内存中的偏移量。也许上面这个方法并不能让你感觉使用了CAS,那再看看下面这个方法:

  compareAndSetAtomicInteger的另一个方法,它的作用就是给定一个预期的旧值expect,以及需要更新为的值update,若当前变量的值是expect,就将其修改为update,否则不修改(这不就是CAS的思想吗)。而它底层调用了unsafe对象的compareAndSwapInt方法,从这个名字可以看出,它的实现使用的就是CAScompareAndSwapInt的三个参数valueOffsetexpect以及update,刚好对应了CAS操作的三个操作数。

 

 


 2.4 CAS机制的ABA问题

  CAS机制虽然简单,但是也存在一些缺陷,其中比较典型的就是ABA问题。什么是ABA问题,我简单介绍一下:

  1. 假设有三个线程T1T2T3,它们都要对一个变量num的值进行修改,且使用的都是CAS机制进行同步,假设num的初始值为100
  2. 线程T1首先读取了num的值,将它记录为旧预期A1 = 100,然后它想要将num的值修改为80,记录B2 = 80,在执行num = B2前,线程发生了切换,切换到线程T2
  3. 假设T2毫无阻碍地修改了num的值,将它从100修改为80,然后线程再度切换,T3开始执行;
  4. T3也是毫无阻碍地修改了num,将它从80重新修改为100,线程再次切换回T1
  5. T1从上次运行的断点恢复,也就是准备用B1的值覆盖num,但是由于CAS机制,它需要先检测num的值是否等于它记录的预期值A1,然后它发现A1 = num = 100,认为num没有被修改过,于是用B1覆盖了num

  上面这种情况就是CASABA问题:一个变量被修改,但是又被改了回去,在CAS机制中,将无法察觉这种错误的现象。在线程T1被中断的过程中,num的值被修改,按照CAS的原则,T1应该放弃对num的修改,从头开始执行。有人可能想问,修改回去之后,不就和没修改一样吗,有什么影响呢?

        ??????

  对于ABA问题的解决方案也非常简单,那就是再添加一个变量——版本号。每个变量都加上一个版本号,在它被修改时,也同步修改版本号,而CAS操作在修改前记录版本号,若在最后更新变量时,记录的版本号与当前版本号一致,表示没有被修改,可直接更新。


 2.5 CAS的优缺点以及适用场景

(1)优点

  前面也提到过,CAS是一种乐观锁,其优点就是不需要加锁就能进行原子操作;

(2)缺点

  CAS的缺点主有两点:

  • CAS机制只能用在对某一个变量进行原子操作,无法用来保证多个变量或语句的原子性(synchronized可以);
  • 假设在修改数据的过程中经常与其他线程修改冲突,将导致需要多次的重新尝试;

(3)适用场景

  由上面分析的优缺点可以看出,CAS适用于并发冲突发生频率较低的场合,而对于并发冲突较频繁的场合,CAS由于不断重试,反倒会降低效率。


三、总结

  CAS是一种在并发下实现原子操作的机制,但是只能用来保证一个变量的原子性,适用于并发冲突频率较低的场合。


四、参考

  推荐描述CAS的博客

标签:变量,修改,CAS,并发,num,线程,数组
From: https://www.cnblogs.com/huigui-mint/p/17287677.html

相关文章

  • JVM——JVM级别下的高并发问题
    摘要本文将深入的学习与分析JVM虚拟机的原理和相关的调优的相关实例。JVM级别下的高并发问题Java内存模型与线程“让计算机并发执行若干个运算任务”与“更充分地利用计算机处理器的效能”之间的因果关系,看起来顺理成章,实际上它们之间的关系并没有想象中的那么简单,其中一个重......
  • case的穿透优化
    importjava.util.Scanner;publicclasspenetrate{publicstaticvoidmain(String[]args){}publicstaticvoidswitchTest1(){//键盘录入一个数值,其中1-5表示工作日,6-7表示休息日Scannersc=newScanner(System.in);System.out......
  • 并发编程——JUC并发大厂面试问题
    摘要现如今,不管是应届毕业生还是工作了三五年之内的工程师,在面试招聘的时候JUC并发编程的是必须掌握的一个技能,否者你将会被面试官玩弄。本博文将整理有关于的JUC的大厂面试问题和答案。帮助大家在面试过程中能够回答面试官问题的一二。同时本人也总结相关的面试问题的在相关文档中......
  • 学了这么久的高并发编程,连Java中的并发原子类都不知道?
    摘要:保证线程安全是Java并发编程必须要解决的重要问题,本文和大家聊聊Java中的并发原子类,看它如何确保多线程的数据一致性。本文分享自华为云社区《学了这么久的高并发编程,连Java中的并发原子类都不知道?这也太Low了吧》,作者:冰河。今天我们一起来聊聊Java中的并发原子类。在 j......
  • 高并发系统设计——API网关技术选型
    摘要你的垂直电商系统在经过微服务化拆分之后,已经运行了一段时间了,系统的扩展性得到了很大的提升,也能够比较平稳地度过高峰期的流量了。不过最近你发现,随着自己的电商网站知名度越来越高,系统迎来了一些“不速之客”,在凌晨的时候,系统中的搜索商品和用户接口的调用量,会有激剧的上升,持......
  • 高并发系统设计——负载均衡技术选型
    摘要高并发系统设计的三个通用方法:缓存、异步和横向扩展,不过在实际的工作中,你经常使用的负载均衡的组件应该算是Nginx,它的作用是承接前端的HTTP请求,然后将它们按照多种策略,分发给后端的多个业务服务器上。这样,我们可以随时通过扩容业务服务器的方式,来抵挡突发的流量高峰。与DNS......
  • Tomcat 9.0.26 高并发场景下DeadLock问题排查与修复
    vivo互联网技术微信公众号 作者:黄卫兵、陈锦霞一、Tomcat容器9.0.26版本Deadlock问题1.1问题现象1.1.1 发生Deadlock的背景某接口/get.do压测,3分钟后,成功事务数TPS由1W骤降至0。1.1.2 Tomcat服务器出现大量的CLOSE_WAIT被压测服务器,出现TCPCLOSE_WAIT状态个数在200~......
  • 高并发系统设计——注册中心技术选型
    摘要服务注册和发现不是一个新的概念,你在之前的实际项目中也一定了解过,只是你可能没怎么注意罢了。比如说,你知道Nginx是一个反向代理组件,那么Nginx需要知道,应用服务器的地址是什么,这样才能够将流量透传到应用服务器上,这就是服务发现的过程。那么Nginx是怎么实现的呢?它是把应......
  • 高并发系统设计——系统架构的微服务化选型
    摘要现在,你的系统运行稳定,好评不断,每天高峰期的流量,已经达到了10000/s请求,DAU也涨到了几十万。CEO非常高兴,打算继续完善产品功能,以便进行新一轮的运营推广,争取在下个双十一可以将DAU冲击过百万。这时,你开始考虑,怎么通过技术上的优化改造,来支撑更高的并发流量,比如支撑过百万的......
  • 高并发系统设计——RPC框架的技术选型
    摘要服务拆分单独部署后,引入的服务跨网络通信的问题;在拆分成多个小服务之后,服务如何治理的问题。如果想要解决这两方面问题,你需要了解,微服务化所需要的中间件的基本原理,和使用技巧,那么本节课,我会带你掌握,解决第一点问题的核心组件:RPC框架。你的垂直电商系统的QPS已经达到了每秒......