是什么
CAS的全稱為Compare-And-Swap,它是一條CPU並發原語,中文翻譯成比較並交換,實現並發算法時常用到的一種技術,它包含三個操作數——內存位置、預期原值及更新值。執行CAS操作的時候,將內存位置的值與預期原值比較:如果相匹配,那么處理器會自動將該位置值更新為新值,如果不匹配,處理器不做任何操作,多個線程同時執行CAS操作只有一個會成功,這個過程是原子的。
由於CAS是一種系統原語,原語屬於操作系統用語范疇,是由若干條指令組成的,用於完成某個功能的一個過程,並且原語的執行必須是連續的,在執行過程中不允許被中斷,也就是說CAS是一條CPU的原子指令,不會造成所謂的數據不一致問題。
原理
CAS (CompareAndSwap) CAS有3個操作數,內存位置值V,舊的預期值A,要修改的更新值B。當且僅當舊的預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做或重來 。
demo
public class CASdemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(1);
System.out.println(atomicInteger.compareAndSet(1, 2)+ " "+ atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(1, 3)+ " "+ atomicInteger.get());
}
}
compareAndSet 源碼
/**
* this:表示要操作的對象
valueOffset:表示要操作對象中屬性地址的偏移量
expect:表示需要修改數據的期望的值
update:表示需要修改為的新值
* @param args
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
unsafe 是什么
Unsafe 是CAS的核心類,由於Java方法無法直接訪問底層系統,需要通過本地(native)方法來訪問,Unsafe相當於一個后門,基於該類可以直接操作特定內存的數據。Unsafe類存在於sun.misc包中,其內部方法操作可以像C的指針一樣直接操作內存,因為Java中CAS操作的執行依賴於Unsafe類的方法。注意Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操作系統底層資源執行相應任務;變量valueOffset,表示該變量值在內存中的偏移地址,因為Unsafe就是根據內存偏移地址獲取數據的。
實現類
AtomicInteger,AtomicBoolean,AtomicReference等
自旋鎖
可以利用cas的原理自定義一個簡版的自旋鎖
public class SpinlockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference();
public void lock () {
System.out.println(Thread.currentThread().getName()+"進來了");
while (!atomicReference.compareAndSet(null, Thread.currentThread())) {
}
System.out.println(Thread.currentThread().getName()+"搶到鎖了");
}
public void unLock () {
atomicReference.compareAndSet(Thread.currentThread(), null);
System.out.println(Thread.currentThread().getName()+"開始釋放鎖");
}
public static void main(String[] args) throws InterruptedException {
SpinlockDemo spinlockDemo = new SpinlockDemo();
new Thread(()->{
spinlockDemo.lock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
spinlockDemo.unLock();
},"t1").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
spinlockDemo.lock();
spinlockDemo.unLock();
},"t2").start();
}
}
缺點
-
如果CAS長時間一直不成功,可能會給CPU帶來很大的開銷。
-
會引發ABA問題
ABA問題的解決辦法:帶版本號的原子引用 AtomicStampedReference
package com.example.juc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author yeric
* @description:
* @date 2021/9/28 23:11
*/
public class ABADemo {
/**
* 普通的原子引用包裝類
*/
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
// 傳遞兩個值,一個是初始值,一個是初始版本號
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
System.out.println("============以下是ABA問題的產生==========");
new Thread(() -> {
// 把100 改成 101 然后在改成100,也就是ABA
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start();
new Thread(() -> {
try {
// 睡眠一秒,保證t1線程,完成了ABA操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 把100 改成 101 然后在改成100,也就是ABA
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("============以下是ABA問題的解決==========");
new Thread(() -> {
// 獲取版本號
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t 第一次版本號" + stamp);
// 暫停t3一秒鍾
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 傳入4個值,期望值,更新值,期望版本號,更新版本號
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 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);
// 暫停t4 3秒鍾,保證t3線程也進行一次ABA問題
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2019, 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();
}
}