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. Redis:slave flush old data造成实例不可用

    一.问题描述 2019-02-22凌晨02:42分前后,收到集群中 [10.32.52.8:6500] 实例不可用告警,登陆管理界面查看此实例在正常运行状态,期间未出现机器宕机或实例直接挂掉的现象. ...

  2. 11--PHP中的类和对象

    PHP类和对象 类是面向对象程序设计的基本概念,通俗的理解类就是对现实中某一个种类的东西的抽象, 比如汽车可以抽象为一个类,汽车拥有名字.轮胎.速度.重量等属性,可以有换挡.前进.后退等操作方法. 通 ...

  3. C语言链表的基本操作

    */ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:text.cpp * 作者:常轩 * 微信公众号:Worldhe ...

  4. LeetCode 题解 | 237. 删除链表中的节点

    题目描述: 请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点. 现有一个链表 -- head = [4,5,1,9],它可以表示为: 示例 1: 输入: hea ...

  5. git指令-版本回退

    git指令-版本回退 回顾: 1. 修改文件 2. 添加到暂存区并提交 回顾对readme共三次修改: 1. 版本1:wrote a readme file Git is a version cont ...

  6. py基础之模块与包

    '''模块:当代码越来越多时,将所有代码放入一个文件中便会无法维护,所以放到多个文件中去,这样每一个py文件就是一个模块 包:模块越来越多时容易重名,便将模块放入不同的包中,每个包中必须有一个__in ...

  7. 前端、HTML+CSS+JS编写规范(终极版)

    HTMLCSS文档规范 HTML和CSS文档必须采用UTF-8编码格式: HTML文档必须使用HTML5的标准文档格式: HTMLCSS编写规范 HTML和CSS的标签.属性.类名.ID都必须使用小写 ...

  8. clientWidth offsetWidth等视窗尺寸

    clientWidth和offsetWidth clientWidth 是一个只读属性,返回元素的内部宽度,该属性包括内边距,但不包括垂直滚动条(如果有).边框和外边距. offsetWidth 是一 ...

  9. PySide2的This application failed to start because no Qt platform plugin could be initialized解决方式

    解决PySide2的This application failed to start because no Qt platform plugin could be initialized问题 今天在装 ...

  10. xml模块介绍

      # xml 是一门可拓展的语言 # xml 语法 是用<>包裹的起来的<>就是标签, xml可以由多个<>组成 也可以由单个<>组成, # < ...