Java并发编程之CAS二源码追根溯源

在上一篇文章中,我们知道了什么是CAS以及CAS的执行流程,在本篇文章中,我们将跟着源码一步一步的查看CAS最底层实现原理。

本篇是《凯哥(凯哥Java:kagejava)并发编程学习》系列之《CAS系列》教程的第二篇:从源码追根溯源查看CAS最底层是怎么实现的。

本文主要内容:CAS追根溯源,彻底找到CAS的根在哪里。

一:查看AtomicInteger.compareAndSet源码

通过上一篇文章学习,我们知道了AtomicInteger.compareAndSet方法不加锁可以保证原子性(其原理就是unsafe+cas实现的),我们来看看其源码:

思考1:变量可见性

AtomicInteger对象(下文凯哥简称:atoInteger)怎么保证变量内存可见性呢?

查看源码:

思考2:为什么上一篇13行的i.compareAndSet(1,1024)是false

我们来看看atoInteger的compareAndSet方法。凯哥在上面添加了注释。

在调用unsafe的compareAndSwapInt这个方法的时候,unsafe是什么?this指的是什么?valueOffset又是什么呢?

我们接着查看atoInteger源码:

我们发现Unsafe以及valueOffset都是从一个对象中获取到的。

那么this指的是什么?其实this就是当前atoInteger对象。

那么Unsafe对象在哪里呢?

我们想要看源码,怎么查看呢?发现不能看源码啊。别急,这个文件的源码可以从openJdk的源码中查到。

接着,我们来查看OpenJdk8的源码:

(PS:下载OpenJdk8源码凯哥这里就不赘述了。在文章最后,凯哥给出)

下载完,解压之后,文件位置:openjdk\jdk\src\share\classes\sun\misc。如下图:

我们来看看Unsafe类上面的注解:

A collection of methods for performing low-level, unsafe operations.

什么意思呢?用于执行底层的(low-level,)、不安全操作的方法的集合。

就是说,这个类可以直接操作底层数据的。

需要说明的是:在这个对象中大量的方法使用了native来修饰(据网友统计高达82个)

我们知道,Java的方法使用native关键字修饰的,说明这个方法不是Java自身的方法(非Java方法),可能调用的是其他语言的。如C或C++语言的方法。

我们再来看看:unsafe.objectFieldOffse()中的

这个方法就是返回一个内存中访问偏移量。

return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

在unsafe类中compareAndSwapInt方法也是native的。我们在来看看这个方法调用操作系统底层C++的代码:

说明:

jint *addr:主内存中的变量值

old:对象工作区域的值

new_val:将要改变的值。

这三个是不是很熟悉,对。就是CAS的三个参数。

分析第13行为什么返回false:

在11行的时候,设置主内存的变量值V=1.

在12行后,更新为V=2020了。

当执行到第13行的时候,

主内存:V=2020

程序工作区变量值jint *addr A的值:A=1

new_val:1024。

从调用C++代码我们可以分析到:

在第5行的时候,因为1!=2020,所以return 的result就是false.

所以第13行输出的是false.

思考3:atoInteger.getAndIncrement()是怎么保证数据一致性的

调用的是getAndAddInt方法。接着查看unsafe的源码,就会发现CAS保证原子性的终极代码。

CAS保证原子性终极方法,如下图:

看看:getObjectVolatile。方法发现是native.如下图:

再来看看compareAndSwapObject:

发现是native修饰的方法。说明不是Java的方法。这个我们等会再细说。

先来研究getAndSetObject:

源码:

我们来模拟:atoInteger.getAndIncrement();

假设默认值是0. 主内存的值是0

在调用getAndSetObject方法的几个参数说明:

Var1:当前atoInteger对象

Var2:当前偏移量(内存地址所在位置。如:三排四列)

Vart4:默认就是1

Var5:获取到的主内存的值

Var5+var4:将要更新的值。

从源码,我们看到是do while语句。为什么不是while语句呢?因为先要获取到主内存中变量最新的值,然后再判断。所以选用了do while语句。

我们来看看当CPU1线程1和CPU2线程B来执行的时候:

两个线程都从主内存copay了i的值到自己工作内存空间后,进行+1的操作。

假设线程1再执行+1操作后,准备往主内存回写数据的时候,CPU1被挂起。然后CPU2竞争到资源之后,也操作i+1后,将更新后的值回写到了主内存中。然后切换到CPU1了,CPU1接着执行。对比代码分析:

线程1在执行do后得到的值var5=1而不是0

然后while里面执行:var1和var2运算后的结果是0(工作区的值)。

因为0!=5 .所以this.comparAndSwapInt的值是false.

又因为前面有个! 非得符号。也就是!false。我们知道!false就是true.

也就是while(true)。While(true)后,接着循环执行。线程会放弃原有操作,重新从主内存中获取到最新数据(此时就是1了),然后再进行操作后。

又到了do,获取在主内存最新数据是1.接着走while()

因为,var1,var2获取到工作区的值是1 var5也等于1.1=1,成立了,执行var5+var5=1+1=2,来更新主内存的数据后返回true.

又因为前面有个!非的符号。所以就是while(!true),也就是while(false)。退出循环,返回var5的值。

结论:

通过上面的运行分析,我们发现atoInteger的getAndIncrement方法保证原子性是unsafe+CAS来保证变量原子性的(其中do while语句就是后面我们将要学到的自旋)

Java并发编程之CAS二源码追根溯源的更多相关文章

  1. Java并发编程之CAS第三篇-CAS的缺点及解决办法

    Java并发编程之CAS第三篇-CAS的缺点 通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理.那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论 本篇是<凯 ...

  2. Java并发编程之CAS第一篇-什么是CAS

    Java并发编程之CAS第一篇-什么是CAS 通过前面几篇的学习,我们对并发编程两个高频知识点了解了其中的一个—volatitl.从这一篇文章开始,我们将要学习另一个知识点—CAS.本篇是<凯哥 ...

  3. Java并发编程之CAS

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

  4. 并发编程之:AQS源码解析

    大家好,我是小黑,一个在互联网苟且偷生的农民工. 在Java并发编程中,经常会用到锁,除了Synchronized这个JDK关键字以外,还有Lock接口下面的各种锁实现,如重入锁ReentrantLo ...

  5. Java并发编程之set集合的线程安全类你知道吗

    Java并发编程之-set集合的线程安全类 Java中set集合怎么保证线程安全,这种方式你知道吗? 在Java中set集合是 本篇是<凯哥(凯哥Java:kagejava)并发编程学习> ...

  6. Java高性能编程之CAS与ABA及解决方法

    Java高性能编程之CAS与ABA及解决方法 前言 如果喜欢暗色调的界面或者想换换界面,可以看看我在个人博客发布的 Java高性能编程之CAS与ABA及解决方法. CAS概念 CAS,全称Compar ...

  7. [Java并发] AQS抽象队列同步器源码解析--独占锁释放过程

    [Java并发] AQS抽象队列同步器源码解析--独占锁获取过程 上一篇已经讲解了AQS独占锁的获取过程,接下来就是对AQS独占锁的释放过程进行详细的分析说明,废话不多说,直接进入正文... 锁释放入 ...

  8. [Java并发] AQS抽象队列同步器源码解析--锁获取过程

    要深入了解java并发知识,AbstractQueuedSynchronizer(AQS)是必须要拿出来深入学习的,AQS可以说是贯穿了整个JUC并发包,例如ReentrantLock,CountDo ...

  9. Java并发编程之ThreadLocal解析

    本文讨论的是JDK 1.8中的ThreadLocal ThreadLocal概念 ThreadLocal多线程间并发访问变量的解决方案,为每个线程提供变量的副本,用空间换时间. ThreadLocal ...

随机推荐

  1. (二)Java数组的使用

    Java数组 无序数组插入删除查询操作: public class ArrayList { private static int[] intArray; private int nElems; pub ...

  2. python标准库:ftplib模块

    ftplib模块包含了文件传输协议(FTP)客户端的实现. 下面的例子展示了如何登入和获取进入目录的列表,dir函数传入一个回调函数,该回调函数在服务器相应时每一行调用一次.ftplib模块默认的回调 ...

  3. jQuery的html(),text()和val()比较

    .html()用为读取和修改元素的HTML标签: .text()用来读取或修改元素的纯文本内容: .val()用来读取或修改表单元素的value值: 一看黑体的部分,所以把text和html分为一组, ...

  4. 使用Xshell进行vi编辑时,按下end、home和Delete不能使用,解决解决办法

    使用Xshell连接到Linux进行vi编辑时,进入编辑模式,按下end键,光标无法移到行位,home也不能到行首,其它的Delete键也是不能使用,如何解决? Xshell选项设置如下: 文件→属性 ...

  5. 算法拾遗[4]——STL用法

    主要bb一下优先队列和字符串吧. 哦还有 bitset. 优先队列 定义很容易: priority_queue<int> pq; 内部是一个堆. 基本操作 pq.top() 取堆顶元素; ...

  6. 通过git shell 在Github上传本地项目

    首先现在github上新建一个库,再进行如下操作,过程不赘述 1.打开git shell 2.cd到项目位置       // cd archives-vue 3.git init 4.Get add ...

  7. DIY电压基准测万用表

    | 分类 日志  | 手里有三个常用的手持表,UT61E四位半,优利德明星产品:福禄克F117C,换挡快,单手操作还带LoZ:UT210E小钳表能够通过修改EEPROM更改电表配置,已经刷了6000字 ...

  8. 生鲜电商的两极战:巨头VS地头

    ​ ​ "九月蟹黄满,十月蟹肉香",螃蟹年年相似,总是美味无边,但购买渠道却随着互联网普及而变得愈发多样起来.此前,大闸蟹礼券风靡就是最佳代表之一.虽然也引发诸多问题,但消费者也越 ...

  9. 极验验证码破解之selenium

    这一篇写完很久了,因为识别率一直很低,没办法拿出来见大家,所以一直隐藏着,今天终于可以拿出来见见阳光了. 哈喽,大家好,我是星星在线,我又来了,今天给大家带来的是极验验证码的selenium破解之法, ...

  10. Java入门教程四(字符串处理)

    Java 语言的文本数据被保存为字符或字符串类型.字符及字符串的操作主要用到 String 类和 StringBuffer 类,如连接.修改.替换.比较和查找等. 定义字符串 直接定义字符串 直接定义 ...