JMM与问题引入

为啥先说JMM,因为CAS的实现类中维护的变量都被volatile修饰, 这个volatile 是遵循JMM规范(不是百分百遵循,下文会说)实现的保证多线程并发访问某个变量实现线程安全的手段

一连串的知识点慢慢缕

首先说什么是JMM, JMM就是大家所说的java的内存模型, 它是人们在逻辑上做出的划分, 或者可以将JMM当成是一种规范, 有哪些规范呢? 如下

  1. 可见性: 某一个线程对内存中的变量做出改动后,要求其他的线程在第一事件内马上马得到通知,在CAS的实现中, 可见性其实是通过不断的while循环读取而得到的通知, 而不是被动的得到通知
  2. 原子性: 线程在执行某个操作的时,要么一起成功,要么就一起失败
  3. 有序性: 为了提高性能, 编译器处理器会进行指令的重排序, 源码-> 编译器优化重排 -> 处理器优化重排 -> 内存系统重排 -> 最终执行的命令

JVM运行的实体是线程, 每一个线程在创建之后JVM都会为其创建一个工作空间, 这个工作空间是每一个线程之间的私有空间, 并且任何两条线程之间的都不能直接访问到对方的工作空间, 线程之间的通信,必须通过共享空间来中转完成

JMM规定所有的变量全部存在主内存中,主内存是一块共享空间,那么如果某个线程相对主内存中共享变量做出修改怎么办呢? 像下面这样:

  1. 将共享变量的副本拷贝到工作空间中
  2. 对变量进行赋值修改
  3. 将工作空间中的变量写回到内存中

JMM还规定如下:

  • 任何线程在解锁前必须将工作空间的共享变量立即刷新进内存中
  • 线程在加锁前必须读取主内存中的值更新到自己的工作空间中
  • 加锁和解锁是同一把锁

问题引入

这时候如果多个线程并发按照上面的三步走去访问主内存中的共享变量的话就会出现线程安全性的问题, 比如说 现在主内存中的共享变量是c=1, 有AB两个线程去并发访问这个c变量, 都想进行c++, 现在A将c拷贝到自己的工作空间进行c++, 于是c=2 , 于此同时线程B也进行c++, c在B的工作空间中=2, AB线程将结果写回工作空间最终的结果就是2, 而不是我们预期的3

相信怎么解决大家都知道, 就是使用JUC,中的原子类就能规避这个问题

而原子类的底层实现使用的就是CAS技术


什么是CAS

CAS(compare and swap) 顾名思义: 比较和交换,在JUC中原子类的底层使用的都是CAS无锁实现线程安全,是一门很炫的技术

如下面两行代码, 先比较再交换, 即: 如果从主内存中读取到的值为4就将它更新为2019

  1. AtomicInteger atomicInteger = new AtomicInteger(4);
  2. atomicInteger.compareAndSet(4,2019);

跟进AtomicInteger的源码如下, 底层维护着一个int 类型的 变量, (当然是因为我选择的原来类是AtomicInteger类型), 并且这个int类型的值被 volatile 修饰

  1. private volatile int value;
  2. /**
  3. * Creates a new AtomicInteger with the given initial value.
  4. *
  5. * @param initialValue the initial value
  6. */
  7. public AtomicInteger(int initialValue) {
  8. value = initialValue;
  9. }

什么是volatile

volatile是JVM提供的轻量的同步机制, 为什么是轻量级别呢? , 刚才在上面说了JMM规范中提到了三条特性, 而JVM提供的volatile仅仅满足上面的规范中的 2/3, 如下:

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排序

单独的volatile是不能满足原子性的,即如下代码在多线程并发访问的情况下依然会出现线程安全性问题

  1. private volatile int value;
  2. public void add(){
  3. value++;
  4. }

那么JUC的原子类是如何实现的 可以满足原子性呢? 于是就不得不说本篇博文的主角, CAS

CAS源码跟进

我们跟进AtomicInteger中的先递增再获取的方法 incrementAndGet()

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

通过代码我们看到调用了Unsafe类来实现

什么是Unsafe类?

进入Unsafe类,可以看到他里面存在大量的 native方法,这些native方法全部是空方法,

这个unsafe类其实相当于一个后门,他是java去访问调用系统上 C C++ 函数类库的方法 如下图

继续跟进这个方法incrementAndGet() 于是我们就来到了我们的主角方法, 关于这个方法倒是不难理解,主要是搞清楚方法中的var12345到底代表什么就行, 如下代码+注释

  1. var1: 上一个方法传递进来的: this,即当前对象
  2. var2: 上一个方法传递进来的valueOffset, 就是内存地址偏移量
  3. 通过这个内存地址偏移量我能精确的找到要操作的变量在内存中的地址
  4. var4: 上一个方法传递进来的1, 就是每次增长的值
  5. var5: 通过this和内存地址偏移量读取出来的当前内存中的目标值
  6. public final int getAndAddInt(Object var1, long var2, int var4) {
  7. int var5;
  8. do {
  9. var5 = this.getIntVolatile(var1, var2);
  10. } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  11. return var5;
  12. }

注意它用的是while循环, 相对if(flag){} 这种写法会多一次判断, 整体的思路就是 在进行修改之前先进行一次比较,如果读取到的当前值和预期值是相同的,就自增,否则的话就继续轮询修改

小总结

通过上面的过程, 其实就能总结出CAS的底层实现原理

  • volatile
  • 自旋锁
  • unsafe类

补充: CAS通过Native方法的底层实现,本质上是操作系统层面上的CPU的并发原语,JVM会直接实现出汇编层面的指令,依赖于硬件去实现, 此外, 对于CPU的原语来说, 有两条特性1,必定连续, 2.不被中断

CAS的优缺点

优点:

它的底层我们看到了通过do-while 实现的自旋锁来实现, 就省去了在多个线程之间进行切换所带来的额外的上下文切换的开销

缺点:

  1. 通过while循环不断的尝试获取, 省去了上下文切换的开销,但是占用cpu的资源
  2. CAS只能保证一个共享变量的原子性, 如果存在多个共享变量的话不得不加锁实现
  3. 存在ABA问题

ABA问题

什么是ABA问题

我们这样玩, 还是AB两个线程, 给AtomicInteger赋初始值0

A线程中的代码如下:

  1. Thread.sleep(3000);
  2. atomicInteger.compareAndSet(0,2019);

B线程中的代码如下:

  1. atomicInteger.compareAndSet(0,1);
  2. atomicInteger.compareAndSet(1,0);

AB线程同时启动, 虽然最终的结果A线程能成果的将值修改成2019,,但是它不能感知到在他睡眠过程中B线程对数据进行过改变, 换句话说就是A线程被B线程欺骗了

ABA问题的解决--- AtomicStampedRefernce.java

带时间戳的原子引用, 实现的机制就是通过 原子引用+版本号来完成, 每次对指定值的修改相应的版本号会加1, 实例如下

  1. // 0表示初始化, 1表示初始版本号
  2. AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(0, 1);
  3. reference.getStamp(); // 获取版本号
  4. reference.attemptStamp(1,2); // 期待是1, 如果是1就更新为2

原子引用

JUC中我们可以找到像AtomicInteger这样已经定义好了实现类, 但是JUC没有给我们提供类似这样 AtomicUser 或者 AtomicProduct 这样自定义类型的原子引用类型啊, 不过java仍然是提供了后门就是 原子引用类型

使用实例:

  1. User user = getUserById(1);
  2. AtomicReference<User> userAtomicReference = new AtomicReference<User>();
  3. user.setUsername("张三");
  4. userAtomicReference.compareAndSet(user,user);

欢迎关注我, 会继续更新笔记

串烧 JavaCAS相关知识的更多相关文章

  1. 【转】java NIO 相关知识

    原文地址:http://www.iteye.com/magazines/132-Java-NIO Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的 ...

  2. 电路相关知识--读<<继电器是如何成为CPU的>>

    电路相关知识–读<<继电器是如何成为CPU的>> */--> *///--> *///--> 电路相关知识–读<<继电器是如何成为CPU的> ...

  3. HTML入门基础教程相关知识

    HTML入门基础教程 html是什么,什么是html通俗解答: html是hypertext markup language的缩写,即超文本标记语言.html是用于创建可从一个平台移植到另一平台的超文 ...

  4. 引用 字库编码Unicode相关知识

    引用 weifeng.shen 的 字库编码Unicode相关知识 1.      各地编码 首先说明一下现在常用的一些编码方案: 1.         在中国,大陆最常用的就是GBK18030编码, ...

  5. X86 寻址方式、AT&T 汇编语言相关知识、AT&T 与 Intel 汇编语言的比较、gcc 嵌入式汇编

    注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料如<linux内核完全剖析>.<linux c 编程一站式学习>等,只是为了更好 ...

  6. 04StringBuffer相关知识、Arrays类、类型互换、正则、Date相关

    04StringBuffer相关知识.Arrays类.类型互换.正则.Date相关-2018.7.12 1.StringBuffer A:StringBuffer的构造方法: public Strin ...

  7. 03匿名内部类、eclipse快捷键、String相关知识

    03匿名内部类.eclipse快捷键.String相关知识-2018.7.11 1.匿名内部类(只针对重写一个方法时候使用,不能向下转型,因为没有子类类名) new Inter(){ public v ...

  8. Golang(十)TLS 相关知识(一)基本概念原理

    0. 前言 最近参与一个基于 BitTorrent 协议的 Docker 镜像分发加速插件的开发,主要参与补充 https 协议 学习了 TLS 相关知识,下面对之前的学习做一下简单总结 参考文献:T ...

  9. 【Python五篇慢慢弹(5)】类的继承案例解析,python相关知识延伸

    类的继承案例解析,python相关知识延伸 作者:白宁超 2016年10月10日22:36:57 摘要:继<快速上手学python>一文之后,笔者又将python官方文档认真学习下.官方给 ...

随机推荐

  1. python常用算法(6)——贪心算法,欧几里得算法

    1,贪心算法 贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,他所做出的的时在某种意义上的局部最优解. 贪心算法并不保证会得到最优解,但 ...

  2. MyBatis 示例-类型处理器

    MyBatis 提供了很多默认类型处理器,参考官网地址:链接,除了官网提供的类型处理器,我们也可以自定义类型处理器. 具体做法为:实现 org.apache.ibatis.type.TypeHandl ...

  3. 深copy

    更好的对一个对象进行复制 using System; using System.Collections.Generic; using System.Linq; using System.Text; u ...

  4. C# 表达式树 Expression

    表达式树是定义代码的数据结构. 它们基于编译器用于分析代码和生成已编译输出的相同结构. 几种常见的表达式 BinaryExpression 包含二元运算符的表达式 BinaryExpression b ...

  5. 增强for循环遍历HashSet

    package cn.bdqn.chatpterone.keben; import java.util.*; public class TestHanshSet { public static voi ...

  6. WebApp 滚动列表的实现

    实现效果: 实现技术:overflow,flex,element::-webkit-scrollbar 实现步骤: //html:代码<div id="slider"> ...

  7. Java8系列 (五) Optional类

    概述 在Java8之前, 如果需要对一个变量做一次 null 检查, 通常会像下面这样写 T t = service1.query(); if (t != null) { K k = service2 ...

  8. IE8下Extjs报缺少':'符号错误

    先介绍下这个问题的由来: 上午其他项目组人员在rtx上问,求帮忙解决ie8兼容性问题. 然后快到饭点,知道这个bug肯定不是那么好解决,肯定不能耽误吃饭时间. 果断说,下午来弄. 下午3点开始去看这个 ...

  9. PowerBI开发 第十六篇:PowerBI Service基本概念

    从总体上来看,PowerBI Service 有4个主要的构建模块,分别是dashboards.reports.workbooks 和 datasets,这四个模块都是目录,位于workspaces目 ...

  10. PHP函数preg_match()

    部分内容来自:http://www.nowamagic.net/librarys/veda/detail/1054 preg_match — 进行正则表达式匹配. 语法:int preg_match ...