一、前言

  今天花了点时间了解了一下JDK1.8ConcurrentHashMap的实现,发现它实现的主要思想就是依赖于CAS机制。CAS机制是并发中比较重要的一个概念,所以今天这篇博客就来详细介绍一下CAS机制以及Java中对CAS的适用。

二、正文

 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

// 声明一个AtomicInteger对象
AtomicInteger num = new AtomicInteger(0);
// 线程1
new Thread(()->{
for (int i = 0; i < 100; i++) {
// num++
num.incrementAndGet();
}
}).start();
// 线程2
new Thread(()->{
for (int i = 0; i < 100; i++) {
// num++
num.incrementAndGet();
}
}).start(); Thread.sleep(1000);
System.out.println(num);

  我们看看incrementAndGet方法的源码:

public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

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

public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

  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的修改,从头开始执行。有人可能想问,修改回去之后,不就和没修改一样吗,有什么影响呢?乍一看确实如此,但是我们考虑实际的应用场景,就会发现有些情况下会出现问题,举个简单的例子:

假设有一个栈,多个线程同时对栈进行操作,使用CAS机制进行线程同步,将栈顶元素作为预期值进行判断。假设一个线程需要对栈顶元素进行修改,再它修改期间,另一个线程进行了出栈操作,假设在栈中存在重复元素,第二个线程执行完出栈后,新的栈顶元素与原来的相等,而第一个线程检查栈顶元素与预期值相同,于是将修改同步到了栈中。但是,此时发生了ABA问题,当前的栈顶已经不是原来的栈顶了。

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

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

(1)优点

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

(2)缺点

  CAS的缺点主有两点:

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

(3)适用场景

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

三、总结

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

四、参考

  推荐两篇描述CAS的博客,这两篇博客通过漫画对CAS进行了非常详细的描述:

并发——详细介绍CAS机制的更多相关文章

  1. 并发编程的基石——CAS机制

    本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 Java中提供了很多原子操作类来保证共享变量操作的原子性 ...

  2. 【C# 线程】并发编程的基石——CAS机制

    其实Java并发框架的基石一共有两块,一块是本文介绍的CAS,另一块就是AQS,后续也会写博客介绍. 什么是CAS机制 CAS机制是一种数据更新的方式.在具体讲什么是CAS机制之前,我们先来聊下在多线 ...

  3. Java 并发专题 : Executor详细介绍 打造基于Executor的Web服务器

    转载标明出处:http://blog.csdn.net/lmj623565791/article/details/26938985 继续并发,貌似并发的文章很少有人看啊~哈~ 今天准备详细介绍java ...

  4. Java CAS同步机制 原理详解(为什么并发环境下的COUNT自增操作不安全): Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性。

    精彩理解:  https://www.jianshu.com/p/21be831e851e ;  https://blog.csdn.net/heyutao007/article/details/19 ...

  5. 线程安全之CAS机制详解(分析详细,通俗易懂)

    背景介绍:假设现在有一个线程共享的变量c=0,让两个线程分别对c进行c++操作100次,那么我们最后得到的结果是200吗? 1.在线程不安全的方式下:结果可能小于200,比如当前线程A取得c的值为3, ...

  6. Session机制详细介绍

    Session机制详细介绍  

  7. Kafaka详细介绍机制原理

    1.       kafka介绍 1.1.       主要功能 根据官网的介绍,ApacheKafka®是一个分布式流媒体平台,它主要有3种功能: 1:It lets you publish and ...

  8. mysql数据库中锁机制的详细介绍

    悲观锁与乐观锁: 悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁.传统的关系型数据库里边就用到了很多这 ...

  9. 图解Janusgraph系列-并发安全:锁机制(本地锁+分布式锁)分析

    图解Janusgraph系列-并发安全:锁机制(本地锁+分布式锁)分析 大家好,我是洋仔,JanusGraph图解系列文章,实时更新~ 图数据库文章总目录: 整理所有图相关文章,请移步(超链):图数据 ...

随机推荐

  1. 19 JPQL

    使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询 @Qu ...

  2. 数据挖掘入门系列教程(四点五)之Apriori算法

    目录 数据挖掘入门系列教程(四点五)之Apriori算法 频繁(项集)数据的评判标准 Apriori 算法流程 结尾 数据挖掘入门系列教程(四点五)之Apriori算法 Apriori(先验)算法关联 ...

  3. 在vue中实现锚点定位功能

    场景如下: 今天早上看到需求方新提的一个需求,这是一份网上答卷,点击题数要实现滚动到对应题目的位置: 注意点:每题题目的高度是不受控制的,你可以取到想跳转的index:(我再循环题目时做了index+ ...

  4. [CSP初赛] 组合数学的三个技巧以及从另一方面思考组合类问题

    也不知道老师讲不讲 话说好久没有水博客了,看了一点\(python\)然后就去搞文化课了 正好网课讲到组合数学,然后觉得还蛮难的(其实是我变菜了),就想到了以前的\(csp\)的组合数学基础 果然被我 ...

  5. (转)GNU风格ARM汇编语法指南(非常详细)2

    原文地址:http://zqwt.012.blog.163.com/blog/static/120446842010111481828392/ 2.GNU汇编程序中的标号symbol(或label) ...

  6. layui的弹出层的title的自定义html

       layui的弹出层的title的自定义html //在这里面输入任何合法的js语句 layer.open({ type: 1 //Page层类型 ,area: ['500px', '300px' ...

  7. 基于.NetCore3.1搭建项目系列 —— 使用Swagger做Api文档 (上篇)

    前言 为什么在开发中,接口文档越来越成为前后端开发人员沟通的枢纽呢? 随着业务的发张,项目越来越多,而对于支撑整个项目架构体系而言,我们对系统业务的水平拆分,垂直分层,让业务系统更加清晰,从而产生一系 ...

  8. 对tf.nn.softmax的理解

    对tf.nn.softmax的理解 转载自律者自由 最后发布于2018-10-31 16:39:40 阅读数 25096  收藏 展开 Softmax的含义:Softmax简单的说就是把一个N*1的向 ...

  9. 玩转控件:封装Dev的LabelControl和TextEdit

    俗话说的好:"工欲善其事必先利其器",作为软件攻城狮也是同样道理,攻城狮开发的软件目的是简化客户的操作,让客户动动手指就可以完成很多事情,减少人力成本.这也是系统/软件存在的目的. ...

  10. 一明单词本持续更新ing...

    introductionshuffingdeployspecifyingreliableclusters programming scalemachinesdeliveringsubmarineadd ...