Java CAS底层原理
Java CAS底层原理,这一篇就够了!!!
CAS全称(Conmpare And Swap)比较并交换,是一种用于在多线程环境下实现同步功能的机制。CAS 操作包含三个操作数 – 内存地址、预期值和新值。CAS 的实现逻辑是将内存地址的数值与预期数值想比较,若相等,则将内存位置处的值替换为新值。若不相等,则不做任何操作。
JAVA中CAS是通过自旋操作完成赋值,若值不相等再更新预期值、重新计算新值,接着进行CAS操作,直到成功为止。底层是JVM调用操作系统原语指令unsafe
,并由CPU完成原子操作,你要知道并发/多线程环境下如果CPU没有原子操作我们是无法完成。
- JAVA1.5开始引入了CAS,主要代码都放在JUC的atomic包下,如下图:
- JUC包下源码如下:
- 每一个操作都是调用
unsafe
方法实现结果。这是java自旋完成CAS源码:
CAS优点
- 没有引用锁的概念,并发量不高情况下提高效率
- 减少线程上下文切换
CAS缺点
- cpu开销大,在高并发下,许多线程,更新一变量,多次更新不成功,循环反复,给cpu带来大量压力。
- 只是一个变量的原子性操作,不能保证代码块的原子性。
- ABA问题
ABA问题
CAS带来最大问题就是ABA问题。有A、B两个线程,A线程运行10s,B线程运行2s,两个线程同一时间开始运行都修改同一变量m,假设m初始值为5,B线程修改m值5改为10,再修改m值10改为5。此时A线程修改m值5改为6,修改成功,满足CAS操作原理,而A线程认为m一直都是5,它并不知道m的值已经被修改过。
解决该问题通过添加版本号来解决,每次修改都带着本次修改的版本号,发现版本不是当前版本此修改失败,否则修改成功。如果自旋同理重新获得版本号计算旧值、新值等。
public class ABA {
//ABA问题产生与解决
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
//==========ABA问题的产生==========
System.out.println("==========ABA问题的产生==========");
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start();
new Thread(() -> {
//保证t1线程完成
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
}, "t2").start();
//暂停一会
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//==========ABA问题的解决==========
System.out.println("==========ABA问题的解决==========");
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + stamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t第二次版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第三次版本号:" + atomicStampedReference.getStamp());
}, "t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + stamp);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改成功否" + result + "\t当前最前版本号:" + atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t当前实际值:" + atomicStampedReference.getReference());
}, "t4").start();
}
}
- 运行结果: