更多内容,前往 IT-BLOG

锁主要分为两种:乐观锁和悲观锁,而 synchronized 就属于一种悲观锁,每次在操作数据前都会加锁。乐观锁是指:乐观的认为自己在操作数据时,别人不会对当前数据进行修改,因此不会加锁。如果有人对数据进行了修改,则重新获取修改后的数据,进行操作。直到成功为止。而乐观锁的这种机制就是CAS(compare and swap)比较并交换。

一、什么是 CAS


CAS(Compare And Swap | Compare And Set)比较并交换,CAS 是解决多线程并行情况下使用锁造成性能消耗的一种机制。CAS 操作包含三个操作数:内存位置(V)、预期值(A)、新值(B)。如果内存位置的值(V)与逾期原值(A)相同,处理器会将该位置的值更新为新值(B)则 CAS 操作成功。否则,处理器不做任何更改,只需要将当前位置的值进行返回即可。在 Java 可以通过锁和循环 CAS 的方式来实现原子操作。Java 中 java.util.concurrent.atomic 包相关类就是 CAS 的实现。我们就举一个整数的例子:
【1】代码解析:AtomicInteger:通常情况下,在 Java中,i++ 等类似操作并不是线程安全的,因为 i++ 可分为三个独立的操作:获取变量当前值,为该值+1,然后写回新的值。在多线程的情况下 +1000 得到的值往往是不正确的。即使变量被 volatile 修饰,但可以用原子方式 AtomicInteger 自增,这样可以保证数据的原子性。代码如下:

建议:必须具备 volatile 的基本知识,因为 AtomicInteger是 volatile 不具备原子性的解决方案之一。

getAndIncrement 方法:如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。

1 public class CAS {
2 public static void main(String[] args) {
3 //创建一个原子整数,当前值为默认值0
4 AtomicInteger atomicInteger = new AtomicInteger();
5 //调用 CAS 方法进行自增
6 atomicInteger.getAndIncrement();
7 }
8 }

【2】进入 getAndIncrement() 方法,发现底层调用的是 Unsafe 类的 getAndIncrement 方法

Unsafe 是 CAS 的核心类,由于 Java 方法无法直接访问底层系统,需要通过本地方法(native)方法来访问。Unsafe 相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe 类存在于 sun.misc 包中,其内部方法操作可以像 C的指针一样直接操作内存,因为 Java 中 CAS 的操作依赖于 Unsafe 类的方法。注意 Unsafe 类中的所有方法都是 native 修饰的,也就是说 Unsafe 类中的方法都直接调用操作系统底层资源执行相应任务。这种操作时不可分割的,具有原子性。

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

1 public final int getAndIncrement() {
2 return unsafe.getAndAddInt(this, valueOffset, 1);
3 }

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

【3】进入Unsafe 类的 getAndAddInt 方法:我们发现其通过无限循环去解决锁的问题,也称为 “循环锁”,直到修改成功。代码及注释说明如下:

1 public final int getAndAddInt(Object var1, long var2, int var4){
2 int var5;
3 do{
4 //根据对象和地址偏移量获取内存中的值
5 var5 = this.getIntVolatile(var1, var2);
6 //将获取到的值 var5 传入,此方法内部会先比较var2地址的值是否等于 var5,相等则修改var5值并返回,否则重新进入循环。
7 }while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
8 return var5;
9 }

二、CAS 缺点


【1】循环时间长开销很大:自旋 CAS 如果长时间不成功,会给 CPU 带来非常大的执行开销。如果 JVM 能支持处理器提供的 pause 指令,那么效率会有一定的提升,pause 指令有两个作用:第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起 CPU 流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
【2】只能保证一个共享变量的原子操作:只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量 i=2,j=a,合并一下 ij=2a,然后用CAS 来操作 ij。从 Java1.5 开始 JDK 提供了 AtomicReference<Clazz> 类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作。
【3】ABA 问题:因为 CAS 需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用 CAS 进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA 问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

从 Java1.5 开始 JDK 的 atomic 包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。这个类的 compareAndSet 方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

三、实战应用


Netty 中的 ByteBuf 的内存回收使用了一种引用计数法的算法,判断当前对象的引用是否为零,如果为零则对对象进行回收。在引用计数的加法的操作,使用到了CAS,代码实例如下:

 1 public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {
2 //管理 AbstractReferenceCountedByteBuf 对象中的 refCnt 属性
3 private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater
4 = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
5 private volatile int refCnt = 1;
6 private ByteBuf retain0(int increment) {
7 int refCnt;
8 int nextCnt;
9 do {
10 refCnt = this.refCnt;
11 nextCnt = refCnt + increment;
12 if(nextCnt <= increment) {
13 throw new IllegalReferenceCountException(refCnt, increment);
14 }
15 } while(!refCntUpdater.compareAndSet(this, refCnt, nextCnt));
16
17 return this;
18 }
19 }
 

CAS乐观锁(原子操作)的更多相关文章

  1. Java性能 -- CAS乐观锁

    synchronized / Lock / CAS synchronized和Lock实现的同步锁机制,都属于悲观锁,而CAS属于乐观锁 悲观锁在高并发的场景下,激烈的锁竞争会造成线程阻塞,而大量阻塞 ...

  2. 并发之ATOMIC原子操作--CAS乐观锁原理(二)

    1.乐观锁介绍 程序完成并发操作时,访问数据时每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止.就是当去做某个修改或其他操作的时候它认为不会有其他线程来做同样的操作(竞争) ...

  3. [数据库锁机制] 深入理解乐观锁、悲观锁以及CAS乐观锁的实现机制原理分析

    前言: 在并发访问情况下,可能会出现脏读.不可重复读和幻读等读现象,为了应对这些问题,主流数据库都提供了锁机制,并引入了事务隔离级别的概念.数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务 ...

  4. Java:CAS(乐观锁)

    本文讲解CAS机制,主要是因为最近准备面试题,发现这个问题在面试中出现的频率非常的高,因此把自己学习过程中的一些理解记录下来,希望能对大家也有帮助. 什么是悲观锁.乐观锁?在java语言里,总有一些名 ...

  5. JAVA多线程学习四 - CAS(乐观锁)

    本文讲解CAS机制,主要是因为最近准备面试题,发现这个问题在面试中出现的频率非常的高,因此把自己学习过程中的一些理解记录下来,希望能对大家也有帮助. 什么是悲观锁.乐观锁?在java语言里,总有一些名 ...

  6. redis的高级事务CAS(乐观锁)

    Optimistic locking using check-and-set(乐观锁) 乐观锁介绍:watch指令在redis事物中提供了CAS的行为.为了检测被watch的keys在是否有多个cli ...

  7. CAS(乐观锁)以及ABA问题

    https://blog.csdn.net/wwd0501/article/details/88663621独占锁是一种悲观锁,synchronized就是一种独占锁:它假设最坏的情况,并且只有在确保 ...

  8. CAS(乐观锁)与ABA问题

    cas是什么 CAS 全称 compare and swap 或者compare and exchange  比较并且交换.用于在没有锁的情况下,多个线程对同一个值的更新. cas原理 例如,我们对一 ...

  9. java 乐观锁CAS

    乐观锁是一种思想,本身代码里并没有lock或synchronized关键字进行修饰.而是采用一种version. 即先从数据库中查询一条记录得到version值,在更新这条记录时在where条件中对这 ...

  10. 五分钟学会悲观乐观锁-java vs mysql vs redis三种实现

    1 悲观锁乐观锁简介 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果 ...

随机推荐

  1. Study python_04

    数组 a = [1,2,3] print(a) 数组替换 a = [1,2,3] a[0] = 100 print(a) 数组去重复 def delete_chong(): a = [1,1,2,2, ...

  2. Study python_02

    分支结构 简单的使用if语句 使用if-else import random# 调用一个随机数包(只看if的情况可忽略) n1 = random.randrange(100) n2 = random. ...

  3. eureka注册中心增加登录认证

    https://www.cnblogs.com/gxloong/p/12364523.html 开启Eureka注册中心认证   1.目的描述 Eureka自带了一个Web的管理页面,方便我们查询注册 ...

  4. Jmeter添加Plugins Manager插件管理器后增加常用base类函数

    路径为/lib/ext/jmeter-plugins-manager-1.7.jar 放置即可打开插件管理器: 搜索Custom JMeter Functions后自动下载安装即可:

  5. vue后台管理系统——权限管理模块

    电商后台管理系统的功能--权限管理模块 1. 权限管理业务分析 通过权限管理模块控制不同的用户可以进行哪些操作,具体可以通过角色的方式进行控制,即每个用户分配一个特定的角色,角色包括不同的功能权限. ...

  6. 前端element ui 文件base64加密字符串 上传

    <el-form-item label="附件" prop="attachment"> <el-upload :multiple=" ...

  7. Debug --> python中的True False 0 1

    今天看了下python中的一些基础知识,以offer64为例叭! 求 1+2+...+n ,要求不能使用乘除法.for.while.if.else.switch.case等关键字及条件判断语句(A?B ...

  8. 学Java的第5天,今天做了个双色球系统

    今天是学JAVA的第5天,刚刚把方法学完,然后就在这做黑马的题. 用了一个多小时时间,把他的 这些题都做完了 但是最后一道题,这个双色球系统我感觉挺有意思的 我看到这个题,分析后感觉需要4种方法: 1 ...

  9. 第一个程序,Hello,World!

    Hello World 创建一个文件夹,存放代码 新建一个java文件 后缀名为.java 编写代码 public class Hello{    public static void main(st ...

  10. Ubuntu系统update时提示源不安全被禁用的一种解决办法

    参考自这篇文章Ubuntu系统update时提示源不安全被禁用 - 知乎 (zhihu.com). 安装好Ubuntu18.04并更换清华源后,在运行 sudo apt update 更新源时报错如下 ...