首页 > 编程语言 >Java并发编程之CAS原理分析

Java并发编程之CAS原理分析

时间:2024-03-09 18:22:44浏览次数:27  
标签:Java 变量 CAS 编程 并发 线程 自旋 操作

Java并发编程之CAS原理分析

在并发编程中,我们经常需要处理多线程对共享资源的访问和修改。那么如何解决并发安全呢?


一. 解决并发安全问题的方案

最粗暴的方式就是使用 synchronized 关键字了,但它是一种独占形式的锁,属于悲观锁机制,性能会大打折扣。olatile 貌似也是一个不错的选择,但 volatile 只能保持变量的可见性,并不保证变量的原子性操作。

相比之下,CAS(Compare And Swap)是一种乐观锁机制。它不需要加锁,是一种无锁的原子操作。

二. 悲观锁和乐观锁
1. 悲观锁
对于悲观锁来说,它总是认为每次访问共享资源时会发生冲突,所以必须对每次数据操作加上锁,以保证临界区的程序同一时间只能有一个线程在执行。

2. 乐观锁
对于乐观锁来说,它总是假设对共享资源的访问没有冲突,线程可以不停地执行,无需加锁也无需等待。一旦多个线程发生冲突,乐观锁通常使用一种称为 CAS 的技术来保证线程执行的安全性。

由于乐观锁假想操作中没有锁的存在,因此不太可能出现死锁的情况,换句话说,乐观锁天生免疫死锁。

3. 使用场景

乐观锁多用于“读多写少“的环境,避免频繁加锁影响性能;
悲观锁多用于”写多读少“的环境,避免频繁失败和重试影响性能。

三、什么是CAS
CAS 是什么,它的英文全称是 Compare-And-Swap,中文叫做“比较并交换”,它是一种思想、一种算法。

CAS算法有3个基本操作数:

内存地址V
旧的预期值A
要修改的新值B
在并发场景下,各个代码的执行顺序不能确定,为了保证并发安全,我们可以使用普通的互斥锁,比如Java的 synchronized, ReentrantLock等。而CAS的特点是避免使用互斥锁,当多个线程并发使用CAS更新同一个变量时,只有一个可以操作成功,其他都会失败。而且用CAS更新失败的线程并不会阻塞,会快速失败并返回一个失败的状态,允许你再次尝试。


   四、CAS的原理
   CAS 的思想很简单:三个参数,一个当前内存值 V、旧的预期值 A、即将更新的值 B,当且仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true,否则什么都不做,并返回 false。

   CAS操作是原子的,即在同一时刻只有一个线程能够成功执行CAS操作。

 


  CAS的基本步骤如下:

  读取共享变量的当前值。
比较当前值与期望值。
如果相等,则使用新的值替换当前值。
如果比较失败,则说明其他线程已经修改了共享变量,需要重新读取当前值并重试操作。
CAS操作是一种无锁的操作,避免了传统锁机制中的竞争和阻塞,从而提高了并发性能。

java.util.concurrent 包很多功能都是建立在 CAS 之上,如 ReenterLock(可重入锁) 内部的 AQS,各种原子类,其底层都用 CAS来实现原子操作。

 

五、CAS的问题

CAS和锁都解决了原子性问题,和锁相比没有阻塞、线程上下文你切换、死锁,所以CAS要比锁拥有更优越的性能,但是CAS同样存在缺点。

1. 只能保证一个共享变量原子操作

CAS只能针对一个共享变量使用,如果多个共享变量就只能使用锁了,当然如果有办法把多个变量整成一个变量,利用CAS也不错,例如读写锁中state的高低位。

2. 自旋时间太长
当一个线程获取锁时失败,不进行阻塞挂起,而是间隔一段时间再次尝试获取,直到成功为止,这种循环获取的机制被称为自旋锁(spinlock)。
自旋锁的优点:
持有锁的线程在短时间内释放锁,那些等待竞争锁的线程就不需进入阻塞状态(无需线程上下文切换/无需用户态与内核态切换),它们只需要等一等(自旋),等到持有锁的线程释放锁之后即可获取,这样就避免了用户态和内核态的切换消耗。
自旋锁的缺点:
线程在长时间内持有锁,等待竞争锁的线程一直自旋,即CPU一直空转,资源浪费在毫无意义的地方,所以一般会限制自旋次数。
最后来说自旋锁的实现,实现自旋锁可以基于CAS实现,先定义lockValue对象默认值1,1代表锁资源空闲,0代表锁资源被占用,代码如下:

 1 public class SpinLock {
 2     
 3     //lockValue 默认值1
 4     private AtomicInteger lockValue = new AtomicInteger(1);
 5     
 6     //自旋获取锁
 7     public void lock(){
 8 
 9         // 循环检测尝试获取锁
10         while (!tryLock()){
11             // 空转
12         }
13 
14     }
15     
16     //获取锁
17     public boolean tryLock(){
18         // 期望值1,更新值0,更新成功返回true,更新失败返回false
19         return lockValue.compareAndSet(1,0);
20     }
21     
22     //释放锁
23     public void unLock(){
24         if(!lockValue.compareAndSet(1,0)){
25             throw new RuntimeException("释放锁失败");
26         }
27     }
28 
29 }

 

参考链接:https://segmentfault.com/a/1190000040042588

 

标签:Java,变量,CAS,编程,并发,线程,自旋,操作
From: https://www.cnblogs.com/hld123/p/18063098

相关文章

  • Maxwell启动问题java.lang.RuntimeException: error: unhandled character set ‘utf8
    使用Maxwell碰到问题,查看日志后显示大概是这个问题java.lang.RuntimeException:error:unhandledcharacterset‘utf8mb3‘。网上查找,看了经验贴https://blog.csdn.net/weixin_44943845/article/details/126860077,知道原因是这个:但是不太想重新下载源码进行打包,于是决定按......
  • Java登陆第三十三天——ES6(二)reset、spread、Class类语法糖
    所谓ECMAScript6也就是JS6。这次更新带来了大量的新特性,使JS代码更简洁,更强大。复习JS请走:JS入门JS6文档请走:JS6菜鸟教程reset同Java中的可变参数。publicstaticvoidtell(String...info){System.out.println(info);}在JS中,叫做reset因为箭头函数中......
  • java8特性-lambda表达式
    Lambda表达式的使用1.举例:(o1,o2)->Integer.compare(o1,o2);格式:->:lambda操作符或箭头操作符->左边:lambda形参列表(其实就是接口中的抽象方法的形参列表)->右边:labbda体(其实就是重写抽象方法中的方法体)3.lambda表达式的使用:(分为六种情况)方式一:无参,无返回值......
  • JavaEE35个系统源码
    01.基于javaEE_大学生就业信息管理系统设计与实现02.基于javaEE_企业车辆管理系统设计与实现03.基于javaEE_BS架构微博系统设计与实现04.基于javaEE健康管理系统设计与实现05.基于javaEE_医院在线挂号系统设计与实现06.基于javaEE_商品供应管理系统设计与实现07.基于javaEE_......
  • ModbusTCP协议简介与编程流程图
      本文主要目的是为了写一个简单的ModbusTCP服务器-客户端程序而记录的知识点,里面包含了编程所需要的必要背景知识和协议解析流程图。Modbus基本数据类型  Modbus有四种基本数据类型:离散量输入:客户端只能读取它,由服务器提供,占1个比特位,可以传输现实中的开关量输入,比如接近......
  • 面试准备不充分,被Java守护线程干懵了,面试官主打一个东西没用但你得会
    写在开头面试官:小伙子请聊一聊Java中的精灵线程?我:什么?精灵线程?啥时候精灵线程?面试官:精灵线程没听过?那守护线程呢?我:守护线程知道,就是为普通线程服务的线程嘛。面试官:没了?守护线程的特点,怎么使用,需要注意啥,Java中经典的守护线程都有啥?我:不知道。。。这的天,面试一个10K的工作,......
  • java List<HashMap<String,Object>> 遍历修改
    List<HashMap<String,Object>>DataSource;inti=0;for(Map<String,Object>item:DataSource){for(Stringk:item.keySet()){if(k.equals("input_date")){//此句为判断条件可去掉Objectb=item.get(k);......
  • java.util.ConcurrentModificationException
    用for循环遍历删除不会报错但是结果未达到预期解决方法......
  • Java多线程
    1.java线程实现/创建的方式(主要是两种)继承Thread类本质是通过实现Runnable接口的一个实例,代表一个线程的实例publicclassMyThreadextendsThread{publicvoidrun(){System.out.println("run()")}}MyThreadthread=newMyThread();thread.st......
  • Java8方法引用简化Lambda
    静态方法引用importlombok.AllArgsConstructor;importlombok.Data;importjava.lang.reflect.Array;importjava.util.Arrays;importjava.util.Comparator;importjava.util.zip.DeflaterOutputStream;/***@authorPickle*@versionV1.0*@date2024/3/823:3......