理会CAS和CAS:

  有时候面试官面试问你的时候,会问,谈谈你对CAS的理解,这时应该有很多人,就会比较懵,当然,我也会比较懵,当然我和很多人的懵不同,很多人可能,并不知道CAS是一个什么东西,而在我看来我是不知道他问的是那个CAS

  我一般会问面试官,问他问的CAS是"原子操作",还是"单点登录"

  因为在JAVA并发中的原子操作是称为CAS的,也就是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。

  但是在企业应用中CAS也被称为企业级开源单点登录解决方案,是 Central Authentication Service 的缩写 —— 中央认证服务,一种独立开放指令协议,是 Yale 大学发起的一个企业级开源项目,旨在为 Web 应用系统提供一种可靠的 SSO 解决方案。

CAS(Compare And Swap):

  我们先要学习的是并发编程中的CAS,也就是原子操作

  那么,什么是原子操作?如何实现原子操作?

什么是原子操作:

  原子,也是最小单位,是一个不可再分割的单位,不可被中断的一个或者一系列操作

  CAS是以一种无锁的方式实现并发控制,在实际情况下,同时操作一个对象的概率非常小,所以多数加锁操作做的基本是无用功

  CAS以一种乐观锁的方式实现并发控制

如何实现原子操作:

  Java可以通过锁和循环CAS的方式实现原子操作

为什么要有CAS:  

  CAS就是比较并且替换的一个原子操作,在CPU的指令级别上进行保证

  为什么要有CAS:

    Sync是基于阻塞的锁的机制,

      1:被阻塞的线程优先级很高

      2:拿到锁的线程一直不释放锁则么办

      3:大量的竞争,消耗CPU,同时带来死锁或者其他线程安全

    因为通过锁实现原子操作时,其他线程必须等待已经获得锁的线程运行完车之后才能获取锁,这样就会占用系统大量资源

CAS原理:

  从CPU指令级别保证这是一个原子操作

CAS包含哪些参数:

  三个运算符:

    一个内存地址V

    一个期望的值A

    一个新值B

  基本思路:

    如果地址V上的值和期望的值A相等,就给地址V赋值新值B,如果不是,不做任何操作

  循环CAS:

    在一个(死)循环中[for(;;)]里不断进行CAS操作,直到成功为止(自旋操作即死循环)

CAS问题:

  ABA问题:

    那么什么是ABA问题?就是内存中原本是A,然后通过CAS变成了B,然后再次通过CAS变成了A,这个过程中,相对于结果来说,是没有任何改变的,但是相对于内存来说,至少发生过两次变化,这就是ABA问题

    生活中:

      就像你接了一杯水,这时水是满的,但是这个时候,你的同时很渴,过来拿你的水直接喝掉了一半,这时水剩下了一半,接着,你的同事又重新把你的水帮你接满了,那么这时你的水还是满的,相对于水来说,他还是满的,但是相对于杯子来说,他已经被用过了两次,一次是喝水,一次是接水,这就是ABA问题

    从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

    生活中:

      你接了一杯水,然后旁边放上一张登记表,这个时候你同事过来,直接喝掉了一半,然后登记上,XXX喝掉了一半的水,然后去给你接满了,再次登记上,我给你接满了,这时,ABA的问题就得到了解决,你一看这个表就知道了一切

  开销问题:

    在自旋或者死循环中不断进行CAS操作,但是长期操作不成功,CPU不断的循环,带来的开销问题

    自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

  只能保证一个共享变量的原子操作

    当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

CAS的目的:

  利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。

JDK中相关原子操作类的使用:

  更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong

  更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArrat

  更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference

  原子更新字段类:AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater

理论已经理解的差不多了,接下来写写代码

使用AtomicInteger

package org.dance.day3;

import java.util.concurrent.atomic.AtomicInteger;

/**
* 使用原子类int类型
* @author ZYGisComputer
*/
public class UseAtomicInt { static AtomicInteger atomicInteger = new AtomicInteger(10); public static void main(String[] args) {
// 10->11 10先去再增加
System.out.println(atomicInteger.getAndIncrement());
// 11->12 12先增加再取
System.out.println(atomicInteger.incrementAndGet());
// 获取
System.out.println(atomicInteger.get());
} }

返回值:

10
12
12

通过返回值可以看到,第一个是先获取返回值后累加1,第二个是先累加1后再返回,第三个是获取当前值

使用AtomicIntegerArray

package org.dance.day3;

import java.util.concurrent.atomic.AtomicIntegerArray;

/**
* 使用原子类int[]
* @author ZYGisComputer
*/
public class UseAtomicIntegerArray { static int[] values = new int[]{1,2}; static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(values); public static void main(String[] args) {
//改变的第一个参数是 数组的下标,第二个是新值
atomicIntegerArray.getAndSet(0,3);
// 获取原子数组类中的下标为0的值
System.out.println(atomicIntegerArray.get(0));
// 获取源数组中下标为0的值
System.out.println(values[0]);
} }

返回结果:

3
1

通过返回结果我们可以看到,源数组中的值并没有改变,只有引用中的值发生了改变,这是则么回事?

/**
* Creates a new AtomicIntegerArray with the same length as, and
* all elements copied from, the given array.
*
* @param array the array to copy elements from
* @throws NullPointerException if array is null
*/
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}

通过看源码我们得知他是调用了数组的克隆方法,克隆了一个一模一样的

使用AtomicReference

package org.dance.day3;

import java.util.concurrent.atomic.AtomicReference;

/**
* 使用原子类引用类型
* @author ZYGisComputer
*/
public class UseAtomicReference { static AtomicReference<UserInfo> atomicReference = new AtomicReference<>(); public static void main(String[] args) { UserInfo src = new UserInfo("彼岸舞",18); // 使用原子引用类包装一下
atomicReference.set(src); UserInfo target = new UserInfo("彼岸花",19); // 这里就是CAS改变了,这个应用类就好像一个容器也就是内存V,而src就是原值A,target就是新值B
// 期望原值是src,如果是的话,改变为target,否则不变
atomicReference.compareAndSet(src,target); System.out.println(atomicReference.get()); System.out.println(src); } static class UserInfo{
private String name;
private int age; @Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", age=" + age +
'}';
} public UserInfo() {
} public UserInfo(String name, int age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
}
} }

返回结果:

UserInfo{name='彼岸花', age=19}
UserInfo{name='彼岸舞', age=18}

通过返回结果可以直观的看到,原子引用类中的值发生了改变,但是源对象src却没有改变,因为原子引用类和原对象本身是两个东西,CAS后就可以理解为内存中的东西变了,也可以说是引用变了,他只能保证你在改变这个引用的时候保证是原子性的

记得之前上面说的ABA问题吧,在这里就是解决代码

JDK中提供了两种解决ABA问题的类

  AtomicStampedReference

    AtomicStampedReference,里面是用int类型,他关心的是被人动过几次

  AtomicMarkableReference

    AtomicMarkableReference,里面是用boolean类型,他只关心这个版本有没有人动过

两个类关心的点不一样,侧重的方向不一样,就像之前说的喝水问题,AtomicStampedReference关心的是,被几个人动过,而AtomicMarkableReference关心的是有没有人动过

使用AtomicStampedReference解决ABA问题

package org.dance.day3;

import java.util.concurrent.atomic.AtomicStampedReference;

/**
* 使用版本号解决ABA问题
* @author ZYGisComputer
*/
public class UseAtomicStampedReference { /**
* 构造参数地第一个是默认值,第二个就是版本号
*/
static AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<>("src",0); public static void main(String[] args) throws InterruptedException { // 获取初始版本号
final int oldStamp = atomicStampedReference.getStamp(); // 获取初始值
final String oldValue = atomicStampedReference.getReference(); System.out.println("oldValue:"+oldValue+" oldStamp:"+oldStamp); Thread success = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+",当前变量值:"+oldValue+"当前版本号:"+oldStamp);
// 变更值和版本号
/**
* 第一个参数:期望值
* 第二个参数:新值
* 第三个参数:期望版本号
* 第四个参数:新版本号
*/
boolean b = atomicStampedReference.compareAndSet(oldValue, oldValue + "java", oldStamp, oldStamp + 1);
System.out.println(b);
}
}); Thread error = new Thread(new Runnable() {
@Override
public void run() {
// 获取原值
String sz = atomicStampedReference.getReference();
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+",当前变量值:"+sz+"当前版本号:"+stamp);
boolean b = atomicStampedReference.compareAndSet(oldValue, oldValue + "C", oldStamp, oldStamp + 1);
System.out.println(b);
}
}); success.start();
success.join();
error.start();
error.join();
System.out.println(atomicStampedReference.getReference()+":"+atomicStampedReference.getStamp());
} }

返回结果:

oldValue:src oldStamp:0
Thread-0,当前变量值:src当前版本号:0
true
Thread-1,当前变量值:srcjava当前版本号:1
false
srcjava:1

通过返回结果可以观察到,原始值是src,版本是0,然后使用join方法使我们的正确线程确保咋错误线程之前执行完毕,当正确线程执行完毕后,会把值改为srcjava,版本改为+1,然后执行错误的线程,错误的线程在尝试去改值的时候,发现期望的值是src,但是值已经被改变成srcjava了,并且期望的版本是0,但是版本已经被改为1了,所以他无法修改,在两个线程都执行完毕之后,打印的值是 srcjava,版本是1,成功的解决了ABA问题,当然在这里面我的期望值是还是src,也可以改为src+java但是因为版本不一样也是无法修改成功的;亲测没问题

原子更新字段类就不写了,那个使用比较麻烦,如果多个字段的话,就直接使用AtomicReference类就可以了

作者:彼岸舞

时间:2020\10\04

内容关于:并发编程

本文来源于网络,只做技术分享,一概不负任何责任

CAS 原子操作的更多相关文章

  1. CAS原子操作实现无锁及性能分析

    CAS原子操作实现无锁及性能分析 Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn.net/chen19870707 ...

  2. 3.CAS原子操作

    什么是原子性操作,按照官方的解析:原子操作不能在一个中间操作中停止,要么全部成功要么全部失败.(An atomic action cannot stop in the middle: it eithe ...

  3. 原子操作cas

    一.概念, 基于处理器指令,把比较和交换合成一个指令完成,保证了原子性: 因为是针对一个内存地址值的,一个内存地址指向一个变量,所以只对一个共享变量能保证原子性: 二.原子操作类 锁只有synchro ...

  4. 锁&锁与指令原子操作的关系 & cas_Queue

    锁 锁以及信号量对大部分人来说都是非常熟悉的,特别是常用的mutex.锁有很多种,互斥锁,自旋锁,读写锁,顺序锁,等等,这里就只介绍常见到的, 互斥锁 这个是最常用的,win32:CreateMute ...

  5. Java锁与CAS

    一.加锁与无锁CAS 在谈论无锁概念时,总会关联起乐观派与悲观派,对于乐观派而言,他们认为事情总会往好的方向发展,总是认为坏的情况发生的概率特别小,可以无所顾忌地做事,但对于悲观派而已,他们总会认为发 ...

  6. 转:Java中的cas

    引自:https://blog.csdn.net/mmoren/article/details/79185862 本篇的思路是先阐明无锁执行者CAS的核心算法原理然后分析Java执行CAS的实践者Un ...

  7. java并发编程(十三)----(JUC原子类)引用类型介绍(CAS和ABA的介绍)

    这一节我们将探讨引用类型原子类:AtomicReference, AtomicStampedRerence, AtomicMarkableReference.AtomicReference的使用非常简 ...

  8. 基于 CAS 无锁实现的 Disruptor.NET 居然慢于 BlockingCollection,是真的吗?

    StackOverflow 有人说自己的Disruptor.NET 代码比 BlockingCollection 还有慢 2 倍,并且把完整代码贴出,楼下几个老外也的回复说了一堆,但是没研究出个所以然 ...

  9. Java性能 -- CAS乐观锁

    synchronized / Lock / CAS synchronized和Lock实现的同步锁机制,都属于悲观锁,而CAS属于乐观锁 悲观锁在高并发的场景下,激烈的锁竞争会造成线程阻塞,而大量阻塞 ...

随机推荐

  1. 专为seo新手准备的百度分享工具教程

    http://www.wocaoseo.com/thread-178-1-1.html 百度分享工具是目前seo站长最为常用的工具之一,主要用来让用户分享来提高网站的流量,同时他也有很多实际有效的方式 ...

  2. opencv 截图并保存

    opencv 截图并保存(转载) 代码功能:选择图像中矩形区,按S键截图并保存,Q键退出. #include<opencv2/opencv.hpp> #include<iostrea ...

  3. Photon PUN 二 大厅 & 房间

    一, 简介 玩过 LOL , dota2, 王者荣耀 等MOBA类的游戏,就很容易理解大厅和房间了. LOL中一个服务器就相当与一个大厅; 什么电一,电二 ,,, 联通一区等 每一个区就相当于一个大厅 ...

  4. 3D人物移动控制实现方案

    要控制3D人物在3D世界中进行正常的移动.转向,一般有两种情况: 1.使用人物动画控制人物 的移动 转向 2.使用脚本控制人物 的移动.转向 对方案一: Animator 组件勾选上 Apply Ro ...

  5. Roads in the North (树的直径)

    Building and maintaining roads among communities in the far North is an expensive business. With thi ...

  6. HTTP协议中的提交方式和解释POST、GET、HEAD、PUT等

    最基本的方法有4种,分别是GET,POST,PUT,DELETE HTTP中的GET,POST,PUT,DELETE就对应着对这个资源的查,改,增,删4个操作 GET一般用于获取/查询资源信息,而PO ...

  7. 使用kubernetes-event-exporter将k8s的事件导出到elasticsearch日志系统中

    使用kubernetes-event-exporter将k8s的事件导出到elasticsearch日志系统中 前提 版本 kubernetes v1.17.9 kubernetes-event-ex ...

  8. 水仙花数的条件:1.是一个三位数,2.个百千位数字的3次方加起来的和等于当前的三位数。如果,想要完美一点可以在外部加while循环

    #!/usr/bin/env python# -*- coding: utf-8 -*-print("请输入三位数:")num = input()# 定义常量SumNum = 0# ...

  9. ASP.NET Core 进程内与进程外的性能对比

    ASP.NET Core 进程内与进程外的性能对比 本文内容是<深入去浅出ASP.NET Core>提供的扩展内容,毕竟在书里说进程内外的性能说明对比,对于初学者而言,稍微复杂了点. 我在 ...

  10. LAMP环境之编译安装httpd服务

    “Apache HTTP Server”是开源软件项目的杰出代表,它基于标准的 HTTP 网络协议提供网页浏览服务. 在配置 Apache 网站服务之前,需要正确安装好 httpd 服务器软件.htt ...