目录
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
的步骤很多,有没有可能在判断 V
和 E
相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致呢?
答案是否定的,因为 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