文章同步更新在個人公眾號“梓莘”敬察,歡迎大家關注,相互交流尔当。
一莲祸、什么是CAS
CompareAndSwap,比較當前工作內(nèi)存中的值和主內(nèi)存中的值,如果相同則執(zhí)行規(guī)定操作椭迎,否則繼續(xù)比較直到主內(nèi)存中的值一致為止锐帜。
CAS有3個操作數(shù),內(nèi)存值為V畜号,舊的預期值為A缴阎,要修改的更新值為B。當且僅當預期值A和內(nèi)存值V相同時简软,將內(nèi)存值V修改為B蛮拔,否則什么都不做述暂。
Demo
package com.zishen;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @ClassName CASDemo
* @Description TODO
* @Author zishen
* @Date 2019/12/25 8:06
* @Version 1.0
* 1、什么是CAS
* 比較并交換
**/
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
//如果內(nèi)存值和期望值相同則修改
System.out.println(atomicInteger.compareAndSet(5,2019)+"\t current data is :"+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5,1024)+"\t current data is :"+atomicInteger.get());
}
}
分析
由上面我們可以看到他實際上調(diào)用的Unsafe類的方法建炫。接下來看下Unsafe.java
二畦韭、Unsafe
- Unsafe是CAS的核心類,由于Java方法無法直接訪問底層系統(tǒng)肛跌,需要通過本地(native)方法來訪問艺配,Unsafe相當于開了一個后門,基于該類可以直接操作特定內(nèi)存的數(shù)據(jù)衍慎。UnSafe類存在于sun.misc包中转唉,其內(nèi)部方法操作可以像C的指針一樣直接操作內(nèi)存,因為Java中CAS操作的執(zhí)行依賴于UnSafe類的方法稳捆。
注意:Unsafe類的所有方法都是native修飾的赠法,也就是說UnSafe類中的方法都直接調(diào)用操作系統(tǒng)底層資源執(zhí)行相應任務。 - 變量valueOffset,表示該變量值在內(nèi)存中的偏移地址眷柔,因為UnSafe就是根據(jù)內(nèi)存偏移地址來獲取數(shù)據(jù)的期虾。
- 變量value用volatile修飾,保證了多線程之間的內(nèi)存可見性
三驯嘱、換個姿勢重新看
CAS的全稱為Compare-And-Swap,從操作系統(tǒng)層面來看镶苞,它是一條CPU并發(fā)原語。
它的功能是判斷內(nèi)存某個位置的值是否為預期值鞠评,如果是則更改為新的值茂蚓,這個過程是原子的。
CAS并發(fā)原語體現(xiàn)在JAVA語言中就是sun.misc.Unsafe類中的各個方法剃幌。調(diào)用UnSafe類中的CAS方法聋涨,JVM會幫我們實現(xiàn)出CAS匯編指令,這是一種完全依賴于硬件的功能负乡,通過它實現(xiàn)了原子操作牍白。再次強調(diào),由于CAS是一種系統(tǒng)源于抖棘,原語屬于操作系統(tǒng)的范疇茂腥,是由若干條指令組成的,用于完成某個功能的一個過程切省,并且原語的執(zhí)行必須是連續(xù)的最岗,在執(zhí)行過程中不允許被中斷,也就是是CAS是一條CPU的原子指令朝捆,不會操作所謂的數(shù)據(jù)不一致問題般渡。
放碼過來
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
i = getIntVolatile(paramObject, paramLong);
while (!(compareAndSwapInt(paramObject, paramLong, i, i + paramInt)));
return i;
}
- paramObject:AtomicInteger對象本身
- paramLong:該對象值的引用地址
- paramInt:需要變動的數(shù)量
- i:是通過paramObject和paramLong找出的主內(nèi)存中真實的值
用該對象當前的值與i進行比較
- 如果相同,更新paramInt+i并且返回true
- 如果不同,繼續(xù)取值然后再比較驯用,直到更新完成脸秽。
舉個栗子
假設線程A和線程B兩個線程同時執(zhí)行getAndAddInt操作(分別跑在不同的cpu上)
- AtomicInteger里面的value原始值為3,即主內(nèi)存中AtomicInteger的value為3晨汹,根據(jù)JMM模型豹储,線程A和線程B各自持有一份值為3的value的副本分為到各自的工作內(nèi)存。
- 線程A通過getIntVolatile(var1,var2)拿到value的值為3淘这,這時線程A被掛起
- 線程B也通過geyIntVolatile(var1 ,var2)方法獲取到value值為3剥扣,此時剛好線程B沒有被掛起并執(zhí)行compareAndSwapInt方法,此時比較內(nèi)存值也為3铝穷,成功修改內(nèi)存值為4钠怯,線程B執(zhí)行完畢。
- 這時線程A恢復曙聂,執(zhí)行compareAndSwapInt方法比較晦炊,發(fā)現(xiàn)自己手里的值數(shù)字3和主內(nèi)存的值4數(shù)字不一致,說明該值已經(jīng)被其他現(xiàn)場搶先一步修改過了宁脊,那A線程本次修改失敗断国,只能重新獲取重新來一次。
- 線程A重新獲取value值榆苞,因為變量value被volatile修飾稳衬,所以其他線程對它的修改,線程A總是能夠看到坐漏,線程A繼續(xù)執(zhí)行compareAndSwapInt進行比較替換薄疚,直到成功。
四赊琳、更上一層樓
UnSafe類中的compareAndSwapInt是一個本地方法街夭,該方法的實現(xiàn)位于unsafe.cpp中(可以看:http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/tip/src/share/vm/prims/unsafe.cpp),
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
先想辦法拿到value在內(nèi)存中的地址躏筏,通過Atomic::cmpxchg實現(xiàn)比較替換板丽,其中參數(shù)x是即將更新的值,參數(shù)e是原內(nèi)存的值趁尼。
五檐什、CAS的缺點
- 循環(huán)時間長開銷大
在getAndAddInt方法執(zhí)行時,有一個do-while弱卡,如果CAS失敗,會一直進行嘗試住册,如果CAS長時間一直不成功婶博,可能會給CPU帶來很大的開銷。 - 只能保證一個共享變量的原子操作
- 引出來ABA問題
書寫不易荧飞,轉載請注明出處凡人。