简单了解JavaCAS的相关知识原理

JMM与问题引入

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

一连串的知识点慢慢缕

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

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

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

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

下面这样:

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

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

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

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

 private volatile int value;

 /**
  * Creates a new AtomicInteger with the given initial value.
  *
  * @param initialValue the initial value
  */
 public AtomicInteger(int initialValue) {
  value = initialValue;
 }

什么是volatile

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

  • 保证可见性
  • 不保证原子性
  • 禁止指令重排序

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

private volatile int value;

public void add(){
 value++;
}

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

CAS源码跟进

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

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

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

什么是Unsafe类?

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

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

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

var1: 上一个方法传递进来的: this,即当前对象
var2: 上一个方法传递进来的valueOffset, 就是内存地址偏移量
  通过这个内存地址偏移量我能精确的找到要操作的变量在内存中的地址

var4: 上一个方法传递进来的1, 就是每次增长的值
var5: 通过this和内存地址偏移量读取出来的当前内存中的目标值
public final int getAndAddInt(Object var1, long var2, int var4) {
  int var5;
  do {
   var5 = this.getIntVolatile(var1, var2);
  } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

  return var5;
 }

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

小总结

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

  • volatile
  • 自旋锁
  • unsafe类

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

CAS的优缺点

优点:

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

缺点:

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

ABA问题

什么是ABA问题

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

A线程中的代码如下:

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

B线程中的代码如下:

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

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

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

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

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

原子引用

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

使用实例:

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java多线程CAS操作原理代码实例解析

    CAS操作号称无锁优化,也叫作自旋:对于一些常见的操作需要加锁,然后jdk就提供了一些以Atomic开头的类,这些类内部自动带了锁,当然这里的锁并非是用synchronized来实现的,而是通过CAS操作来实现的: 一.下面是 AtomicInteger 的使用: package com.designmodal.design.juc01; import java.util.ArrayList; import java.util.List; import java.util.concurrent.

  • Java原子操作CAS原理解析

    一.CAS(Compare And Set) Compare And Set(或Compare And Swap),CAS是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数--内存位置(V).预期原值(A).新值(B).如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值.否则,处理器不做任何操作.无论哪种情况,它都会在CAS指令之前返回该位置的值.CAS有效地说明了"我认为位置V应该包含值A:如果包含该值,则将B放到这个位置:否则,不要更改该位置,只

  • Java CAS基本实现原理代码实例解析

    一.前言 了解CAS,首先要清楚JUC,那么什么是JUC呢?JUC就是java.util.concurrent包的简称.它有核心就是CAS与AQS.CAS是java.util.concurrent.atomic包的基础,如AtomicInteger.AtomicBoolean.AtomicLong等等类都是基于CAS. 什么是CAS呢?全称Compare And Swap,比较并交换.CAS有三个操作数,内存值V,旧的预期值E,要修改的新值N.当且仅当预期值E和内存值V相同时,将内存值V修改为N

  • Java switch case数据类型原理解析

    这篇文章主要介绍了Java switch case数据类型原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Java 中 switch case 语句用来判断一个变量与一系列值中某个值是否相等,每个值称为一个分支. 语法格式如下: switch(expression){ case value : //语句 break; //可选 case value : //语句 break; //可选 //你可以有任意数量的case语句 default

  • Java CAS底层实现原理实例详解

    这篇文章主要介绍了Java CAS底层实现原理实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.CAS(compareAndSwap)的概念 CAS,全称Compare And Swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制. CAS(V, A, B),V为内存地址.A为预期原值,B为新值.如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值.否则,说明已经被其他线程更新,处理器不做任何操作:无论哪种情

  • Java并发之CAS原理详解

    目录 开端 1.代码 1.1修改后的代码 1.2代码改进:CAS模仿 2.CAS分析 2.1Java对CAS的支持 2.2CAS实现原理是什么? 2.3CAS存在的问题 2.3.1什么是ABA问题? 2.3.2如何解决ABA问题 总结 开端 在学习源码之前我们先从一个需求开始 需求 我们开发一个网站,需要对访问量进行统计,用户每发送一次请求,访问量+1.如何实现?我们模拟有100个人同时访问,并且每个人对咱们的网站发起10次请求,最后总访问次数应该是1000次 1.代码 package day0

  • 简单了解JavaCAS的相关知识原理

    JMM与问题引入 为啥先说JMM,因为CAS的实现类中维护的变量都被volatile修饰, 这个volatile 是遵循JMM规范(不是百分百遵循,下文会说)实现的保证多线程并发访问某个变量实现线程安全的手段 一连串的知识点慢慢缕 首先说什么是JMM, JMM就是大家所说的java的内存模型, 它是人们在逻辑上做出的划分, 或者可以将JMM当成是一种规范, 有哪些规范呢? 如下 可见性: 某一个线程对内存中的变量做出改动后,要求其他的线程在第一事件内马上马得到通知,在CAS的实现中, 可见性其实

  • 简单说说JVM堆区的相关知识

    一.堆概述 一个jvm实例(进程)只存在一个堆内存,堆也是java内存管理的核心区域. java 堆区在jvm启动时即被创建,其空间大小也就被确定了 <java虚拟机规范>规定,堆可以处于物理上不连续的内存空间,但在逻辑上它应该被称为连续的 所有线程共享java堆,在这里和可以划分线程私有的缓冲区(tlab) 所有对象实例以及数组都应在运行时分配在堆中 方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集时候才会被移除 堆是gc执行垃圾回收的重点区域 1.1 堆内存细分 现代垃圾收集器大部分基

  • 简单了解springboot中的配置文件相关知识

    这篇文章主要介绍了简单了解springboot中的配置文件相关知识,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 springboot中可以有多个配置文件,配置文件可以是.properties或则yml结尾的文件,并且配置文件有优先级,相同种类的配置application.properties 的优先级比较高,不同种类配置同时都会生效. 也可以自定义配置文件,随意命名,但是后缀名必须按照要求来!后台也可以通过@Value("${key}&quo

  • 简单了解Spring Cloud Alibaba相关知识

    这篇文章主要介绍了简单了解Spring Cloud Alibaba相关知识,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 官方github地址 Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案.此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务. 主要功能 服务限流降级:默认支持 WebServlet.WebFlux, OpenFeign

  • 简单谈谈Python面向对象的相关知识

    一.私有化 上篇说过封装,既将我们不想让别人看到代码的内容,但是又需要用到的内容,通过类内部调用来实现调用. 说到这里却不得不提一下上篇的: class Person(object): def __init__(self, name, age): self.xxx = name self.xxxx = age 这里面self后面的名字,是可以自己随意命名的,上一篇和后面一样只是为了好记忆罢了 只要你记得住,便是颠倒也是无事 1.1 属性私有化 何为属性私有? 举个例子便是:你的私房钱,你的手机电

  • K近邻法(KNN)相关知识总结以及如何用python实现

    1.基本概念 K近邻法(K-nearest neighbors,KNN)既可以分类,也可以回归. KNN做回归和分类的区别在于最后预测时的决策方式. KNN做分类时,一般用多数表决法 KNN做回归时,一般用平均法.  基本概念如下:对待测实例,在训练数据集中找到与该实例最邻近的K个实例(也就是上面所说的K个邻居), 这K个实例的多数属于某个类,就把该输入实例分类到这个类中 2. KNN算法三要素 KNN算法主要考虑:k值的选取,距离度量方式,分类决策规则. 1) k值的选取.在应用中,k值一般选

  • Java 自旋锁(spinlock)相关知识总结

    一.前言 谈到『自旋锁』,可能大家会说,这有啥好讲的,不就是等待资源的线程"原地打转"嘛.嗯,字面理解的意思很到位,但能深入具体点吗?自旋锁的设计真就这么简单? 本文或者说本系列的目的,都是让大家不要停留在表面,而是深入分析,做到: 灵活使用 掌握原理 优缺点 二.锁的优化:自旋锁 当多个线程想同时访问同一个资源时,就存在资源冲突,这时,大家最直接想到的就是加锁来互斥访问,加锁会有这么几个问题: 等待资源的线程进入睡眠,发生用户态向内核态的切换,有一定的性能开销: 占用资源的线程很快就

  • 简述SQL Server 2005数据库镜像相关知识

    SQL Server 数据库中,数据库镜像是用于提高数据库可用性的主要软件解决方案.数据库镜像基于每个数据库实现,并且只适用于使用完整恢复模式的数据库.简单恢复模式和大容量日志恢复模式不支持数据库镜像,数据库镜像不能镜像master.msdb.tempdb 或 model 数据库.本文我们主要就介绍一下数据库镜像的相关知识,接在来就让我们来一起了解一下吧! 数据库镜像维护一个数据库的两个副本,这两个副本必须驻留在不同的SQL Server 数据库引擎实例(服务器实例)上.通常,这些服务器实例驻留

  • JS闭包、作用域链、垃圾回收、内存泄露相关知识小结

    补充: 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 闭包的特性 闭包有三个特性: 1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会被垃圾回收机制回收 闭包的定义及其优缺点 闭包 是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量 闭包的缺点就是常驻内存,会增大内存使用量,使用不当很容易造成内存泄露. 闭包是javascript

  • python框架django项目部署相关知识详解

    这篇文章主要介绍了python框架django项目部署相关知识详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一:项目部署的框架 nginx和uWSGI在生产服务器上进行的部署 二:什么是nginx? nginx是一个web服务器. 什么是web服务器? web服务器则主要是让客户可以通过浏览器进行访问,处理HTML文件,css文件,js文件,图片等资源.web服务器一般要处理静态文件.对接服务器. 什么是静态文件? css,js,html

随机推荐