JUC并发编程——CAS 介绍及底层源码分析
1、什么是 CAS
前言:使用锁的代价
Java并发处理中锁非常重要,但是使用锁会带来下面几个问题:
- 加锁、释放锁会需要操作系统进行上下文切换和调度延时,在上下文切换的时候,cpu之前缓存的指令和数据都将失效,这个过程将增加系统开销。
- 多个线程同时竞争锁,锁竞争机制本身需要消耗系统资源。没有获取到锁的线程会被挂起直至获取锁,在线程被挂起和恢复执行的过程中也存在很大开销。
- 等待锁的线程会阻塞,影响实际的使用体验。如果被阻塞的线程优先级高,而持有锁的线程优先级低,将会导致优先级反转(Priority Inversion)。
乐观锁和悲观锁
乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
悲观锁:总是假设最坏的情况,认为线程每次访问共享资源的时候,总会发生冲突,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。宁愿牺牲性能(时间)来保证数据安全。synchronized 关键字的实现就是悲观锁。
乐观锁:顾名思义,就是很乐观,每次线程访问共享资源不会发生冲突,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,一旦检测到冲突产生,就重试当前操作直到没有冲突为止。CAS机制(Compare And Swap)就是一种乐观锁。
CAS (Compare And Swap) 比较并交换
CAS (Compare And Swap) 比较并交换是一种现代 CPU 广泛支持的CPU指令级的操作,只有一步原子操作,所以非常快。而且CAS避免了请求操作系统来裁定锁的问题,不用麻烦操作系统,直接在CPU内部就搞定了。
CAS的功能是判断内存某个位置的值是否为预期值。如果是则更改为新的值,这个过程是原子的。CAS可以将read- modify - write这类的操作转换为原子操作。
read- modify - write类型的操作,如i++,i++自增操作包括三个子操作:
● 从主内存读取i变量值
● 对i的值加1
● 再把加1之后 的值保存到主内存
CAS原理:在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样就更新。
CAS 有三个操作参数:
- 内存位置 V(它的值是我们想要去更新的)
- 预期原值 A(上一次从内存中读取的值)
- 新值 B(应该写入的新值)
CAS的操作过程:将内存位置V的值与A比较(compare),如果相等,则说明没有其它线程来修改过这个值,所以把内存V的的值更新成B(swap),如果不相等,说明V上的值被修改过了,不更新,而是返回当前V的值,再重新执行一次任务再继续这个过程。
2、JDK 对 CAS 的支持
JDK 提供了对CAS 操作的支持,具体在sun.misc.unsafe类中,声明如下:
public final native boolean compareAndSwapObject(Object o, long offset,
Object expected,
Object x);
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
public final native boolean compareAndSwapLong(Object o, long offset,
long expected,
long x);
参数说明:
- o:表示要操作的字段对象
- offset:字段在对象内的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值
- expected:字段的期望值
- x:如果该字段的值等于字段的期望值,用于更新字段的新值
Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(Native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存数据,Unsafe类存在sun.misc包中,其内部方法操作可以像C指针一样直接操作内存,Java中的CAS操作的执行依赖于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 address, long x);
//获取指定内存地址的值
public native long getAddress(long address);
//设置给定内存地址的long值
public native void putLong(long address, long x);
//获取指定内存地址的long值
public native long getLong(long address);
//其他基本数据类型(long,char,float,double,short等)的操作与putByte及getByte相同
public native byte getByte(long address);
public native void putByte(long address, byte x);
//操作系统的内存页大小
public native int pageSize();
注意Unsafe类的所有方法都是native修饰的,也就是说unsafe类中的方法都直接调用操作系统底层资源执行相应的任务。
Java中CAS的操作:

本文深入探讨了Java并发编程中的CAS(Compare And Swap)机制,解释了CAS如何避免锁带来的开销,以及其在乐观锁中的作用。通过JDK的Unsafe类了解了CAS操作的实现,并通过AtomicInteger的源码分析展示了CAS的原子性。此外,文章还讨论了CAS的ABA问题及其解决方案——AtomicStampedReference。
最低0.47元/天 解锁文章
1501

被折叠的 条评论
为什么被折叠?



