Java中的CAS

CAS 思想产生的背景

jdk1.5 之前 Java 语言是靠 synchronized 关键字保证同步的,而 synchronized 的锁机制存在以下问题

  • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,效率低下
  • 一个线程持有锁会导致其它所有需要此锁的线程阻塞,等待
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险

volatile 是不错的机制,但是 volatile 不能保证原子性。因此对于同步最终还是要回到锁机制上来。在此情况下,CAS 思想就产生了

CAS 的无锁思想

在谈论无锁概念时,总会关联起乐观锁与悲观锁

  • 对于乐观锁而言,他们认为事情总会往好的方向发展,总是认为坏的情况发生的概率特别小,可以无所顾忌地做事
  • 对于悲观锁而言,他们总会认为发展事态如果不及时控制,以后就无法挽回了,即使无法挽回的局面几乎不可能发生

这两种锁机制在并发编程中就如同加锁与无锁的策略,即加锁是一种悲观策略,无锁是一种乐观策略

  • 对于加锁的并发程序来说,它们总是认为每次访问共享资源时总会发生冲突,因此必须对每一次数据操作实施加锁策略
  • 无锁则总是假设对共享资源的访问没有冲突,线程可以不停执行,无需加锁,无需等待,一旦发现冲突,无锁策略则采用一种称为 CAS 的技术思想来保证线程执行的安全性,这项 CAS 技术思想就是无锁策略实现的关键

CAS 详解

CAS 的全称是 Compare-and-Swap,中文翻译成比较并交换。是并发编程中常用的算法,算法核心思想是函数 CAS(V,E,B),包含 3 个参数

  • V:内存中的值
  • E:旧的预期值
  • B:要修改的值

如果 V 值等于 E 值,则将 V 的值设为 B。若 V 值和 E 值不同,则说明已经有其他线程做了更新,则当前线程什么都不做

通俗的理解就是 CAS 操作需要我们提供一个预期值,当预期值与当前线程的变量值相同时,说明还没线程修改该值,当前线程可以进行修改,也就是执行 CAS 操作,但如果期望值与当前线程不符,则说明该值已被其他线程修改,此时不执行更新操作,但可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作

在这里插入图片描述
由于 CAS 操作属于乐观派,它总认为自己可以成功完成操作,当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅仅是被告知失败,并且 允许再次尝试,当然也允许失败的线程放弃操作。所以,CAS 也被称为自旋锁

基于这样的原理,CAS 操作即使没有锁,同样知道其他线程对共享资源操作影响,并执行相应的处理措施。同时从这点也可以看出,由于无锁操作中没有锁的存在,因此不可能出现死锁的情况,也就是说无锁操作天生免疫死锁

CPU 指令对 CAS 的支持

或许我们可能会有这样的疑问,假设存在多个线程执行 CAS 操作并且 CAS 的步骤很多,有没有可能在判断 VE 相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致呢?

答案是否定的,因为 CAS 是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS 是一条 CPU 的原子指令,不会造成所谓的数据不一致问题

Unsafe

Unsafe 类存在于 sun.misc 包中,其内部方法操作可以像 C 的指针一样直接操作内存,单从名称看来就可以知道该类是非安全的,毕竟 Unsafe 拥有着类似于 C 的指针操作,因此总是不应该首先使用 Unsafe 类,Java 官方也不建议直接使用的 Unsafe

但我们还是很有必要了解该类,Unsafe 类是 Java 实现 CAS 的基石,但它的作用不仅仅是实现 CAS,还有操作直接内存等作用,实际上 Unsafe 类也是 java.util.concurrent 包的实现的基石。关于 Unsafe 的详细理解,可以看这篇文章:Unsafe类的原理详解与使用案例

注意 Unsafe 类中的所有方法都是 native 修饰的,也就是说 Unsafe 类中的方法都直接调用操作系统底层资源执行相应任务

内存管理,Unsafe 类中存在直接操作内存的方法

// 分配内存指定大小的内存
public native long allocateMemory(long bytes);

// 根据给定的内存地址address设置重新分配指定大小的内存
public native long reallocateMemory(long address, long bytes);

// 用于释放allocateMemory和reallocateMemory申请的内存
public native void freeMemory(long address);

// 将指定对象的给定offset偏移量内存块中的所有字节设置为固定值
public native void setMemory(Object o, long offset, long bytes, byte value);

// 设置给定内存地址的值
public native void putAddress(long
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值