JUC多并发编程-->CAS和原子操作类

文章目录

  • CAS是什么
    • unsafe
    • 自旋锁 spinlock
    • CAS缺点
  • 原子操作类
    • 分类
    • LongAdder为什么快

CAS是什么

类似于乐观锁
compare and swap,比较与交换,实现并发算法时常用的一种技术。
包含三个操作数— 内存位置、预期原值以及更新值。
执行CAS操作的时候,将内存位置的值与预期原值比较:

  • 如果匹配,那么处理器会自动将该位置的值更新为新值。
  • 如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功

CAS有3个操作数,位置内存值V,旧的预期值A,要修改的更新值B。当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来当它重来重试的这种行为成为----自旋! !
在这里插入图片描述

public class CASDemo {public static void main(String[] args) {AtomicInteger atomicInteger =new AtomicInteger(5);System.out.println(atomicInteger.compareAndSet(5, 2022)+"\t" +atomicInteger.get());System.out.println(atomicInteger.compareAndSet(5, 2022)+"\t" +atomicInteger.get());// true	2022// false 2022}
}
======调用的方法 unsafe类中
public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}

硬件级别保证:
在这里插入图片描述

unsafe

  1. unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native) 方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
    注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe 类中的方法都直接调用作系统底层资源执行相应任务
  2. valueOffset:表示变量值在内存中的偏移地址,因为unsafe就是根据内存偏移地址获取数据的。
  3. 变量value用valatile修饰,保证了多线程之间的内存可见性。

atomicInteger.getAndIncrement()是如何保证安全的

  • CAS十一条CPU的并发原语。他的功能是判断某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的
  • AtomicInteger类主要是利用CAS+volatile和native方法来保证原子操作,避免sychornized的高开销,执行效率大为提升。
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);}=====
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;}

CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若于条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

JDK提供的CAS机制,在汇编层级会禁止变量两侧的指令优化,然后使用cmpxchg指令比较并更新变量值(原子性)
Atomic::cmpxchg(x,addr,e)) ==e;
不同的操作系统会调用不同的cmpxchg重载函数
因此,CAS是靠硬件实现的从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性

自旋锁 spinlock

CAS 是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,至于自旋呢,看字面意思也很明白,自己旋转。是指尝试获取锁的线程不会立即阻寒,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

CAS缺点

循环时间长,开销很大
在这里插入图片描述
引出来ABA问题
在这里插入图片描述
解决方案:
可以加版本号AtomicStampedReference

原子操作类

在这里插入图片描述
在这里插入图片描述

分类

  • 基本类型原子类

  • 数组类型原子类

  • 引用类型原子类

  • 对象的属性修改原子类

  • 原子操作增强类原理

  • 基本类型原子类
    AtomicIntegerAtomicBooleanAtomicLong
    CountDownLatch保证所有线程操作完成之后再读取结果。

CountDownLatch countDownLatch = new CountDownLatch(Size);
try{
}finally{
countDownLatch.caountDown();
}
countDownLatch.await();
  • 数组类型原子类
    AtomicIntegerArrayAtomicReferenceArrayAtomicLongArray
    在这里插入图片描述
  • 引用类型原子类
    AtomicReferenceAtomicStampedReferenceAtomicMarkableReference
    AtomicStampedReference携带版本号的引用类型原子类,解决修改过几次。
    AtomicMarkableReference原子更新带有标记位的引用类型对象,解决是否修改过(false–true)(一次性的)
  • 对象的属性修改原子类
    AtomicIntegerFieldUpdater、AtomicReferenceFieldUpdaterAtomicLongFieldUpdater`
    AtomicIntegerFieldUpdater:原子更新对象中int类型字段的值。基于反射的实用程序,可对指定类的指定volatile int字段进行原子更新
    目的:以一种线程安全的方式操作非线程安全对象内的某些字段
    使用要求
    • 更新的对象属性必须使用 public volatile 修饰符。
    • 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater)创建一个更新器,并且需要设置想要更新的类和属性
class BankAccount{String bankName="hhh";public volatile int money =0;public void  add(){money++;}AtomicIntegerFieldUpdater<BankAccount> fieldUpdater =AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");public void transMoney(BankAccount bankAccount){fieldUpdater.getAndIncrement(bankAccount);}
}
public class CASDemo {public static void main(String[] args) throws InterruptedException {BankAccount bankAccount = new BankAccount();CountDownLatch countDownLatch = new CountDownLatch(10);for (int i = 1; i <=10; i++) {new Thread(()->{try{for (int j = 1; j <= 1000; j++) {
//                        bankAccount.add();bankAccount.transMoney(bankAccount);}}finally {countDownLatch.countDown();}},String.valueOf(i)).start();}countDownLatch.await();System.out.println(Thread.currentThread().getName()+"\t"+"result:"+bankAccount.money);}
}

在这里插入图片描述

  • 原子操作增强类原理
    DoubleAccumulatorDoubleAdderLongAccumulatorLongAdder

LongAdder: 只能用于加法,而且初始值必须为0. 当多个线程更新用于收集统计信息但不用于细粒度同步控制的目的的公共和时,此类通常优于AtomicLong 。在低更新争用下,这两个类具有相似的特征。但在高争用的情况下,这一类的预期吞吐量明显更高,但代价是空间消耗更高。
LongAccumulator:提供了自定义的函数方法。自定义的。
在这里插入图片描述

LongAdder为什么快

  • 有多个窗口,分散热点。LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
  • sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。
  • LongAdder在无竞争的情况,跟AtomicLong一样,对同一个base进行操作,当出现竞争关系时则是采用化整为零分散热点的做法,用空间换时,用一个数组cels,将一个value拆分进这个数组cels。多个线程需要同时对value进行操作时候,可以对线和id进行hash得到hash值,再根据hash值映射到这个数组cels的某个下标,再对该下标所对应的值进行自增换作。当所有线程操作完毕。将数组cels的所有值和base都加起来作最终结果
    在这里插入图片描述

在这里插入图片描述
striped64比较重要的成员函数
在这里插入图片描述
在这里插入图片描述
源码

    public void add(long x) {Cell[] as; long b, v; int m; Cell a;if ((as = cells) != null || !casBase(b = base, b + x)) {//是否竞争boolean uncontended = true;if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value, v + x)))longAccumulate(x, null, uncontended);}}

二者区别
在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/442122.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决VSCode使用Remote SSH连接远程服务器免密登陆

&#x1f4d5;作者简介&#xff1a; 过去日记&#xff0c;致力于Java、GoLang,Rust等多种编程语言&#xff0c;热爱技术&#xff0c;喜欢游戏的博主。 &#x1f4d7;本文收录于杂项系列&#xff0c;大家有兴趣的可以看一看 &#x1f4d8;相关专栏Rust初阶教程、go语言基础系列…

Redis -- 常用数据结构,认识数据类型和编码方式

"人生就像骑自行车&#xff0c;要保持平衡&#xff0c;就必须保持前进。" — 爱因斯坦 说到数据结构&#xff0c;或许就能想到哈希表&#xff0c;列表集合等数据结构。对于redis来说对应的key的value的形式也可以是这些数据结构&#xff0c;如下&#xff1a; 针对上面…

微信小程序如何实现实时显示输入内容

如下所示&#xff0c;在许多场景中需要实时显示用户输入&#xff0c;具体实现见下文。 .wxml <input type"text" placeholder"请输入{{item.value}}(必填)" style"width:80%;" bindinput"get_required_value" data-info"{{it…

dom监听元素 从display: none到页面中

其实业务中还是会碰见这样的需求的&#xff0c;特别是一些框架内不&#xff0c;这个并不是很复杂&#xff0c;我们可以考虑如何去监听到 dom元素样式属性的变化就可以 很多童鞋可能对原生js的不够熟悉&#xff0c;现在大多数同学 只要会写简单的vue操作 就可以 做一些基础的前…

springBoot+Vue汽车销售源码

源码描述: 汽车销售管理系统源码基于spring boot以及Vue开发。 针对汽车销售提供客户信息、车辆信息、订单信息、销售人员管理、 财务报表等功能&#xff0c;提供经理和销售两种角色进行管理。 技术架构&#xff1a; idea(推荐)、jdk1.8、mysql5.X(不能为8驱动不匹配)、ma…

[C#]对c#剪切板Clipboard占用的问题解决方法

前几天做一个程序&#xff0c;其中有一个剪切板的操作&#xff0c;具体代码&#xff1a; Clipboard.SetText(“ABC”); 来完成一个复制字符串的操作。 自己调试通过&#xff0c;完全正常&#xff0c;然后就交给一位朋友做测试。但是他告诉我这个复制操作总是引起崩溃。并弹出…

DevOps落地笔记-04|看板方法:成员工作内容清楚明白方法

上一讲主要介绍了用户故事以及如何通过讲好用户故事解决团队沟通的问题&#xff0c;争取达成共识。当团队都理解了用户需求之后&#xff0c;就进入到后续的产品设计、代码开发、功能测试、直到生产部署等环节了。作为软件从业人员都知道&#xff0c;后续的步骤不太可能一帆风顺…

【Leetcode】2670. 找出不同元素数目差数组

文章目录 题目思路代码结果 题目 题目链接 给你一个下标从 0 开始的数组 nums &#xff0c;数组长度为 n 。 nums 的 不同元素数目差 数组可以用一个长度为 n 的数组 diff 表示&#xff0c;其中 diff[i] 等于前缀 nums[0, …, i] 中不同元素的数目 减去 后缀 nums[i 1, …, …

Mac下手动源码编译安装Swig

使用Homebrew安装 这个方式最简单&#xff0c;但是一般都是安装的最新版&#xff1a; brew install swig如果按照特定版本&#xff0c;需要看一个当前支持的列表&#xff1a; brew search swig brew install swig3源码编译安装 swig依赖pcre库&#xff0c;需要先安装pcre …

第二十回 虔婆醉打唐牛儿 宋江怒杀阎婆惜-FreeBSD改变分区大小

阎婆找到宋江&#xff0c;劝宋江和阎婆惜和解。 宋江无奈跟阎婆惜喝酒&#xff0c;想趁阎婆下楼之机离开&#xff0c;但被阎婆用门锁拦住。宋江无奈留宿&#xff0c;但是两人还是不愉快&#xff0c;宋江五更天就起来了。 宋江走的匆忙&#xff0c;没有带招文袋。阎婆惜拿到招文…

搭建幻兽帕鲁需要什么样的服务器

作为一个开放世界生存制造类游戏《幻兽帕鲁》收获了空前绝后的热度&#xff0c;玩家们在游戏中通过在地图上捕捉收集到的“帕鲁”进行训练&#xff0c;合理利用他们的能力进行战斗&#xff0c;建立自己的家园、开辟新的世界、解锁新的冒险情节&#xff0c;获取更多游戏信息增加…

在哪里申请Digicert证书

Digicert收购Symantec数字证书业务后&#xff0c;已经是全球首屈一指的互联网安全品牌。在网站安全、电子邮件安全、数据泄露防护和SSL证书领域&#xff0c;没有任何一品牌可以替代Digicert/Symantec的地位。所有Digicert SSL证书&#xff0c;都支持在有效期内免费重新签发服务…