Java并發(fā)編程-無鎖CAS與Unsafe類及其并發(fā)包Atomic

無鎖的概念

在談?wù)摕o鎖概念時(shí)击碗,總會(huì)關(guān)聯(lián)起樂觀派與悲觀派筑悴,對(duì)于樂觀派而言,他們認(rèn)為事情總會(huì)往好的方向發(fā)展稍途,總是認(rèn)為壞的情況發(fā)生的概率特別小阁吝,可以無所顧忌地做事,但對(duì)于悲觀派而已械拍,他們總會(huì)認(rèn)為發(fā)展事態(tài)如果不及時(shí)控制突勇,以后就無法挽回了,即使無法挽回的局面幾乎不可能發(fā)生坷虑。這兩種派系映射到并發(fā)編程中就如同加鎖與無鎖的策略甲馋,即加鎖是一種悲觀策略,無鎖是一種樂觀策略迄损,因?yàn)閷?duì)于加鎖的并發(fā)程序來說定躏,它們總是認(rèn)為每次訪問共享資源時(shí)總會(huì)發(fā)生沖突,因此必須對(duì)每一次數(shù)據(jù)操作實(shí)施加鎖策略芹敌。而無鎖則總是假設(shè)對(duì)共享資源的訪問沒有沖突痊远,線程可以不停執(zhí)行,無需加鎖氏捞,無需等待碧聪,一旦發(fā)現(xiàn)沖突,無鎖策略則采用一種稱為CAS的技術(shù)來保證線程執(zhí)行的安全性液茎,這項(xiàng)CAS技術(shù)就是無鎖策略實(shí)現(xiàn)的關(guān)鍵逞姿,下面我們進(jìn)一步了解CAS技術(shù)的奇妙之處。

無鎖的執(zhí)行者-CAS

CAS

CAS的全稱是Compare And Swap 即比較交換捆等,其算法核心思想如下

執(zhí)行函數(shù):CAS(V,E,N)

其包含3個(gè)參數(shù)V表示要更新的變量E表示預(yù)期值N表示新值如果V值等于E值哼凯,則將V的值設(shè)為N。若V值和E值不同楚里,則說明已經(jīng)有其他線程做了更新断部,則當(dāng)前線程什么都不做。通俗的理解就是CAS操作需要我們提供一個(gè)期望值班缎,當(dāng)期望值與當(dāng)前線程的變量值相同時(shí)蝴光,說明還沒線程修改該值她渴,當(dāng)前線程可以進(jìn)行修改,也就是執(zhí)行CAS操作蔑祟,但如果期望值與當(dāng)前線程不符趁耗,則說明該值已被其他線程修改,此時(shí)不執(zhí)行更新操作疆虚,但可以選擇重新讀取該變量再嘗試再次修改該變量苛败,也可以放棄操作,原理圖如下


由于CAS操作屬于樂觀派径簿,它總認(rèn)為自己可以成功完成操作罢屈,當(dāng)多個(gè)線程同時(shí)使用CAS操作一個(gè)變量時(shí),只有一個(gè)會(huì)勝出篇亭,并成功更新缠捌,其余均會(huì)失敗,但失敗的線程并不會(huì)被掛起译蒂,僅是被告知失敗曼月,并且允許再次嘗試,當(dāng)然也允許失敗的線程放棄操作柔昼,這點(diǎn)從圖中也可以看出來哑芹。基于這樣的原理捕透,CAS操作即使沒有鎖绩衷,同樣知道其他線程對(duì)共享資源操作影響,并執(zhí)行相應(yīng)的處理措施激率。同時(shí)從這點(diǎn)也可以看出,由于無鎖操作中沒有鎖的存在勿决,因此不可能出現(xiàn)死鎖的情況乒躺,也就是說無鎖操作天生免疫死鎖。

CPU指令對(duì)CAS的支持

或許我們可能會(huì)有這樣的疑問低缩,假設(shè)存在多個(gè)線程執(zhí)行CAS操作并且CAS的步驟很多嘉冒,有沒有可能在判斷V和E相同后,正要賦值時(shí)咆繁,切換了線程讳推,更改了值。造成了數(shù)據(jù)不一致呢玩般?答案是否定的银觅,因?yàn)镃AS是一種系統(tǒng)原語,原語屬于操作系統(tǒng)用語范疇坏为,是由若干條指令組成的究驴,用于完成某個(gè)功能的一個(gè)過程镊绪,并且原語的執(zhí)行必須是連續(xù)的,在執(zhí)行過程中不允許被中斷洒忧,也就是說CAS是一條CPU的原子指令蝴韭,不會(huì)造成所謂的數(shù)據(jù)不一致問題。

鮮為人知的指針: Unsafe類

Unsafe類存在于sun.misc包中熙侍,其內(nèi)部方法操作可以像C的指針一樣直接操作內(nèi)存榄鉴,單從名稱看來就可以知道該類是非安全的,畢竟Unsafe擁有著類似于C的指針操作蛉抓,因此總是不應(yīng)該首先使用Unsafe類庆尘,Java官方也不建議直接使用的Unsafe類,據(jù)說Oracle正在計(jì)劃從Java 9中去掉Unsafe類芝雪,但我們還是很有必要了解該類减余,因?yàn)镴ava中CAS操作的執(zhí)行依賴于Unsafe類的方法,注意Unsafe類中的所有方法都是native修飾的惩系,也就是說Unsafe類中的方法都直接調(diào)用操作系統(tǒng)底層資源執(zhí)行相應(yīng)任務(wù)位岔,關(guān)于Unsafe類的主要功能點(diǎn)如下:

內(nèi)存管理,Unsafe類中存在直接操作內(nèi)存的方法

//分配內(nèi)存指定大小的內(nèi)存 public native long allocateMemory(long bytes); //根據(jù)給定的內(nèi)存地址address設(shè)置重新分配指定大小的內(nèi)存 public native long reallocateMemory(long address, long bytes); //用于釋放allocateMemory和reallocateMemory申請(qǐng)的內(nèi)存 public native void freeMemory(long address); //將指定對(duì)象的給定offset偏移量內(nèi)存塊中的所有字節(jié)設(shè)置為固定值 public native void setMemory(Object o, long offset, long bytes, byte value); //設(shè)置給定內(nèi)存地址的值 public native void putAddress(long address, long x); //獲取指定內(nèi)存地址的值 public native long getAddress(long address); //設(shè)置給定內(nèi)存地址的long值 public native void putLong(long address, long x); //獲取指定內(nèi)存地址的long值 public native long getLong(long address); //設(shè)置或獲取指定內(nèi)存的byte值 public native byte getByte(long address); public native void putByte(long address, byte x); //其他基本數(shù)據(jù)類型(long,char,float,double,short等)的操作與putByte及getByte相同 //操作系統(tǒng)的內(nèi)存頁大小 public native int pageSize();

提供實(shí)例對(duì)象新途徑堡牡。

? //傳入一個(gè)對(duì)象的class并創(chuàng)建該實(shí)例對(duì)象抒抬,但不會(huì)調(diào)用構(gòu)造方法public native Object allocateInstance(Class cls) throws InstantiationException;

類和實(shí)例對(duì)象以及變量的操作,主要方法如下

//獲取字段f在實(shí)例對(duì)象中的偏移量 public native long objectFieldOffset(Field f); //靜態(tài)屬性的偏移量晤柄,用于在對(duì)應(yīng)的Class對(duì)象中讀寫靜態(tài)屬性 public native long staticFieldOffset(Field f); //返回值就是f.getDeclaringClass() public native Object staticFieldBase(Field f); //獲得給定對(duì)象偏移量上的int值擦剑,所謂的偏移量可以簡單理解為指針指向該變量的內(nèi)存地址, //通過偏移量便可得到該對(duì)象的變量芥颈,進(jìn)行各種操作 public native int getInt(Object o, long offset); //設(shè)置給定對(duì)象上偏移量的int值 public native void putInt(Object o, long offset, int x); //獲得給定對(duì)象偏移量上的引用類型的值 public native Object getObject(Object o, long offset); //設(shè)置給定對(duì)象偏移量上的引用類型的值 public native void putObject(Object o, long offset, Object x); //其他基本數(shù)據(jù)類型(long,char,byte,float,double)的操作與getInthe及putInt相同 //設(shè)置給定對(duì)象的int值惠勒,使用volatile語義,即設(shè)置后立馬更新到內(nèi)存對(duì)其他線程可見 public native void putIntVolatile(Object o, long offset, int x); //獲得給定對(duì)象的指定偏移量offset的int值爬坑,使用volatile語義纠屋,總能獲取到最新的int值。 public native int getIntVolatile(Object o, long offset); //其他基本數(shù)據(jù)類型(long,char,byte,float,double)的操作與putIntVolatile及getIntVolatile相同盾计,引用類型putObjectVolatile也一樣售担。 //與putIntVolatile一樣,但要求被操作字段必須有volatile修飾 public native void putOrderedInt(Object o,long offset,int x);

下面通過一個(gè)簡單的Demo來演示上述的一些方法以便加深對(duì)Unsafe類的理解

public class UnSafeDemo {

? ? public? static? void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {

? ? ? ? // 通過反射得到theUnsafe對(duì)應(yīng)的Field對(duì)象? ? ? ? Field field = Unsafe.class.getDeclaredField("theUnsafe");

? ? ? ? // 設(shè)置該Field為可訪問? ? ? ? field.setAccessible(true);

? ? ? ? // 通過Field得到該Field對(duì)應(yīng)的具體對(duì)象署辉,傳入null是因?yàn)樵揊ield為static的? ? ? ? Unsafe unsafe = (Unsafe) field.get(null);

? ? ? ? System.out.println(unsafe);

? ? ? ? //通過allocateInstance直接創(chuàng)建對(duì)象? ? ? ? User user = (User) unsafe.allocateInstance(User.class);

? ? ? ? Class userClass = user.getClass();

? ? ? ? Field name = userClass.getDeclaredField("name");

? ? ? ? Field age = userClass.getDeclaredField("age");

? ? ? ? Field id = userClass.getDeclaredField("id");

? ? ? ? //獲取實(shí)例變量name和age在對(duì)象內(nèi)存中的偏移量并設(shè)置值? ? ? ? unsafe.putInt(user,unsafe.objectFieldOffset(age),18);

? ? ? ? unsafe.putObject(user,unsafe.objectFieldOffset(name),"android TV");

? ? ? ? // 這里返回 User.class族铆,? ? ? ? Object staticBase = unsafe.staticFieldBase(id);

? ? ? ? System.out.println("staticBase:"+staticBase);

? ? ? ? //獲取靜態(tài)變量id的偏移量staticOffset? ? ? ? long staticOffset = unsafe.staticFieldOffset(userClass.getDeclaredField("id"));

? ? ? ? //獲取靜態(tài)變量的值? ? ? ? System.out.println("設(shè)置前的ID:"+unsafe.getObject(staticBase,staticOffset));

? ? ? ? //設(shè)置值? ? ? ? unsafe.putObject(staticBase,staticOffset,"SSSSSSSS");

? ? ? ? //獲取靜態(tài)變量的值? ? ? ? System.out.println("設(shè)置前的ID:"+unsafe.getObject(staticBase,staticOffset));

? ? ? ? //輸出USER? ? ? ? System.out.println("輸出USER:"+user.toString());

? ? ? ? long data = 1000;

? ? ? ? byte size = 1;//單位字節(jié)? ? ? ? //調(diào)用allocateMemory分配內(nèi)存,并獲取內(nèi)存地址memoryAddress? ? ? ? long memoryAddress = unsafe.allocateMemory(size);

? ? ? ? //直接往內(nèi)存寫入數(shù)據(jù)? ? ? ? unsafe.putAddress(memoryAddress, data);

? ? ? ? //獲取指定內(nèi)存地址的數(shù)據(jù)? ? ? ? long addrData=unsafe.getAddress(memoryAddress);

? ? ? ? System.out.println("addrData:"+addrData);

? ? ? ? /**

? ? ? ? * 輸出結(jié)果:

? ? ? ? sun.misc.Unsafe@6f94fa3e

? ? ? ? staticBase:class geym.conc.ch4.atomic.User

? ? ? ? 設(shè)置前的ID:USER_ID

? ? ? ? 設(shè)置前的ID:SSSSSSSS

? ? ? ? 輸出USER:User{name='android TV', age=18', id=SSSSSSSS'}

? ? ? ? addrData:1000

? ? ? ? */? ? }

}

class User{

? ? public User(){

? ? ? ? System.out.println("user 構(gòu)造方法被調(diào)用");

? ? }

? ? private String name;

? ? private int age;

? ? private static String id="USER_ID";

? ? @Override

? ? public String toString() {

? ? ? ? return "User{" +

? ? ? ? ? ? ? ? "name='" + name + '\'' +

? ? ? ? ? ? ? ? ", age=" + age +'\'' +

? ? ? ? ? ? ? ? ", id=" + id +'\'' +

? ? ? ? ? ? ? ? '}';

? ? }

}

雖然在Unsafe類中存在getUnsafe()方法,但該方法只提供給高級(jí)的Bootstrap類加載器使用哭尝,普通用戶調(diào)用將拋出異常哥攘,所以我們?cè)贒emo中使用了反射技術(shù)獲取了Unsafe實(shí)例對(duì)象并進(jìn)行相關(guān)操作。

public static Unsafe getUnsafe() {

? ? ? Class cc = sun.reflect.Reflection.getCallerClass(2);

? ? ? if (cc.getClassLoader() != null)

? ? ? ? ? throw new SecurityException("Unsafe");

? ? ? return theUnsafe;

? }

數(shù)組操作

//獲取數(shù)組第一個(gè)元素的偏移地址public native int arrayBaseOffset(Class arrayClass);//數(shù)組中一個(gè)元素占據(jù)的內(nèi)存空間,arrayBaseOffset與arrayIndexScale配合使用,可定位數(shù)組中每個(gè)元素在內(nèi)存中的位置public native int arrayIndexScale(Class arrayClass);

CAS 操作相關(guān)?

CAS是一些CPU直接支持的指令献丑,也就是我們前面分析的無鎖操作末捣,在Java中無鎖操作CAS基于以下3個(gè)方法實(shí)現(xiàn),在稍后講解Atomic系列內(nèi)部方法是基于下述方法的實(shí)現(xiàn)的创橄。

//第一個(gè)參數(shù)o為給定對(duì)象箩做,offset為對(duì)象內(nèi)存的偏移量,通過這個(gè)偏移量迅速定位字段并設(shè)置或獲取該字段的值妥畏,//expected表示期望值邦邦,x表示要設(shè)置的值,下面3個(gè)方法都通過CAS原子指令執(zhí)行操作醉蚁。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);

這里還需介紹Unsafe類中JDK 1.8新增的幾個(gè)方法燃辖,它們的實(shí)現(xiàn)是基于上述的CAS方法,如下

//1.8新增网棍,給定對(duì)象o黔龟,根據(jù)獲取內(nèi)存偏移量指向的字段,將其增加delta滥玷, //這是一個(gè)CAS操作過程氏身,直到設(shè)置成功方能退出循環(huán),返回舊值 public final int getAndAddInt(Object o, long offset, int delta) {

? ? int v;

? ? do {

? ? ? ? //獲取內(nèi)存中最新值? ? ? ? v = getIntVolatile(o, offset);

? ? ? //通過CAS操作? ? } while (!compareAndSwapInt(o, offset, v, v + delta));

? ? return v;

}//1.8新增惑畴,方法作用同上蛋欣,只不過這里操作的long類型數(shù)據(jù) public final long getAndAddLong(Object o, long offset, long delta) {

? ? long v;

? ? do {

? ? ? ? v = getLongVolatile(o, offset);

? ? } while (!compareAndSwapLong(o, offset, v, v + delta));

? ? return v;

}

//1.8新增,給定對(duì)象o如贷,根據(jù)獲取內(nèi)存偏移量對(duì)于字段陷虎,將其 設(shè)置為新值newValue, //這是一個(gè)CAS操作過程杠袱,直到設(shè)置成功方能退出循環(huán)尚猿,返回舊值 public final int getAndSetInt(Object o, long offset, int newValue) {

? ? int v;

? ? do {

? ? ? ? v = getIntVolatile(o, offset);

? ? } while (!compareAndSwapInt(o, offset, v, newValue));

? ? return v;

}// 1.8新增,同上楣富,操作的是long類型 public final long getAndSetLong(Object o, long offset, long newValue) {

? ? long v;

? ? do {

? ? ? ? v = getLongVolatile(o, offset);

? ? } while (!compareAndSwapLong(o, offset, v, newValue));

? ? return v;

}

//1.8新增凿掂,同上,操作的是引用類型數(shù)據(jù) public final Object getAndSetObject(Object o, long offset, Object newValue) {

? ? Object v;

? ? do {

? ? ? ? v = getObjectVolatile(o, offset);

? ? } while (!compareAndSwapObject(o, offset, v, newValue));

? ? return v;

}

上述的方法我們?cè)谏院蟮腁tomic系列分析中還會(huì)見到它們的身影菩彬。

掛起與恢復(fù)?

將一個(gè)線程進(jìn)行掛起是通過park方法實(shí)現(xiàn)的,調(diào)用 park后潮梯,線程將一直阻塞直到超時(shí)或者中斷等條件出現(xiàn)骗灶。unpark可以終止一個(gè)掛起的線程,使其恢復(fù)正常秉馏。Java對(duì)線程的掛起操作被封裝在 LockSupport類中耙旦,LockSupport類中有各種版本pack方法,其底層實(shí)現(xiàn)最終還是使用Unsafe.park()方法和Unsafe.unpark()方法

//線程調(diào)用該方法萝究,線程將一直阻塞直到超時(shí)免都,或者是中斷條件出現(xiàn)锉罐。 public native void park(boolean isAbsolute, long time); //終止掛起的線程,恢復(fù)正常.java.util.concurrent包中掛起操作都是在LockSupport類實(shí)現(xiàn)的绕娘,其底層正是使用這兩個(gè)方法脓规, public native void unpark(Object thread);

內(nèi)存屏障

這里主要包括了loadFence、storeFence险领、fullFence等方法侨舆,這些方法是在Java 8新引入的,用于定義內(nèi)存屏障绢陌,避免代碼重排序挨下,與Java內(nèi)存模型相關(guān),感興趣的可以看博主的另一篇博文全面理解Java內(nèi)存模型(JMM)及volatile關(guān)鍵字脐湾,這里就不展開了

//在該方法之前的所有讀操作臭笆,一定在load屏障之前執(zhí)行完成public native void loadFence();//在該方法之前的所有寫操作,一定在store屏障之前執(zhí)行完成public native void storeFence();//在該方法之前的所有讀寫操作秤掌,一定在full屏障之前執(zhí)行完成愁铺,這個(gè)內(nèi)存屏障相當(dāng)于上面兩個(gè)的合體功能public native void fullFence();

其他操作

//獲取持有鎖,已不建議使用@Deprecatedpublic native void monitorEnter(Object var1);//釋放鎖机杜,已不建議使用@Deprecatedpublic native void monitorExit(Object var1);//嘗試獲取鎖帜讲,已不建議使用@Deprecatedpublic native boolean tryMonitorEnter(Object var1);//獲取本機(jī)內(nèi)存的頁數(shù),這個(gè)值永遠(yuǎn)都是2的冪次方 public native int pageSize(); //告訴虛擬機(jī)定義了一個(gè)沒有安全檢查的類椒拗,默認(rèn)情況下這個(gè)類加載器和保護(hù)域來著調(diào)用者類 public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain); //加載一個(gè)匿名類public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);//判斷是否需要加載一個(gè)類public native boolean shouldBeInitialized(Class c);//確保類一定被加載 public native void ensureClassInitialized(Class c)

并發(fā)包中的原子操作類(Atomic系列)

通過前面的分析我們已基本理解了無鎖CAS的原理并對(duì)Java中的指針類Unsafe類有了比較全面的認(rèn)識(shí)似将,下面進(jìn)一步分析CAS在Java中的應(yīng)用,即并發(fā)包中的原子操作類(Atomic系列)蚀苛,從JDK 1.5開始提供了java.util.concurrent.atomic包在验,在該包中提供了許多基于CAS實(shí)現(xiàn)的原子操作類,用法方便堵未,性能高效腋舌,主要分以下4種類型。

原子更新基本類型

原子更新基本類型主要包括3個(gè)類:

AtomicBoolean:原子更新布爾類型

AtomicInteger:原子更新整型

AtomicLong:原子更新長整型

這3個(gè)類的實(shí)現(xiàn)原理和使用方式幾乎是一樣的渗蟹,這里我們以AtomicInteger為例進(jìn)行分析块饺,AtomicInteger主要是針對(duì)int類型的數(shù)據(jù)執(zhí)行原子操作,它提供了原子自增方法雌芽、原子自減方法以及原子賦值方法等授艰,鑒于AtomicInteger的源碼不多,我們直接看源碼

public classAtomicIntegerextendsNumberimplementsjava.io.Serializable{ private static final long serialVersionUID = 6214790243416807050L;

? ? // 獲取指針類Unsafe? ? private static final Unsafe unsafe = Unsafe.getUnsafe();

? ? //下述變量value在AtomicInteger實(shí)例對(duì)象內(nèi)的內(nèi)存偏移量? ? private static final long valueOffset;

? ? static {

? ? ? ? try {

? ? ? ? ? //通過unsafe類的objectFieldOffset()方法世落,獲取value變量在對(duì)象內(nèi)存中的偏移? ? ? ? ? //通過該偏移量valueOffset淮腾,unsafe類的內(nèi)部方法可以獲取到變量value對(duì)其進(jìn)行取值或賦值操作? ? ? ? ? ? valueOffset = unsafe.objectFieldOffset

? ? ? ? ? ? ? ? (AtomicInteger.class.getDeclaredField("value"));

? ? ? ? } catch (Exception ex) { throw new Error(ex); }

? ? }

? //當(dāng)前AtomicInteger封裝的int變量value? ? private volatile int value;

? ? public AtomicInteger(int initialValue) {

? ? ? ? value = initialValue;

? ? }

? ? public AtomicInteger() {

? ? }

? //獲取當(dāng)前最新值,? ? public final int get() {

? ? ? ? return value;

? ? }

? ? //設(shè)置當(dāng)前值,具備volatile效果谷朝,方法用final修飾是為了更進(jìn)一步的保證線程安全洲押。? ? public final void set(int newValue) {

? ? ? ? value = newValue;

? ? }

? ? //最終會(huì)設(shè)置成newValue,使用該方法后可能導(dǎo)致其他線程在之后的一小段時(shí)間內(nèi)可以獲取到舊值圆凰,有點(diǎn)類似于延遲加載? ? public final void lazySet(int newValue) {

? ? ? ? unsafe.putOrderedInt(this, valueOffset, newValue);

? ? }

? //設(shè)置新值并獲取舊值杈帐,底層調(diào)用的是CAS操作即unsafe.compareAndSwapInt()方法? ? public final int getAndSet(int newValue) {

? ? ? ? return unsafe.getAndSetInt(this, valueOffset, newValue);

? ? }

? //如果當(dāng)前值為expect,則設(shè)置為update(當(dāng)前值指的是value變量)? ? public final boolean compareAndSet(int expect, int update) {

? ? ? ? return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

? ? }

? ? //當(dāng)前值加1返回舊值送朱,底層CAS操作? ? public final int getAndIncrement() {

? ? ? ? return unsafe.getAndAddInt(this, valueOffset, 1);

? ? }

? ? //當(dāng)前值減1娘荡,返回舊值,底層CAS操作? ? public final int getAndDecrement() {

? ? ? ? return unsafe.getAndAddInt(this, valueOffset, -1);

? ? }

? //當(dāng)前值增加delta驶沼,返回舊值炮沐,底層CAS操作? ? public final int getAndAdd(int delta) {

? ? ? ? return unsafe.getAndAddInt(this, valueOffset, delta);

? ? }

? ? //當(dāng)前值加1,返回新值回怜,底層CAS操作? ? public final int incrementAndGet() {

? ? ? ? return unsafe.getAndAddInt(this, valueOffset, 1) + 1;

? ? }

? ? //當(dāng)前值減1大年,返回新值,底層CAS操作? ? public final int decrementAndGet() {

? ? ? ? return unsafe.getAndAddInt(this, valueOffset, -1) - 1;

? ? }

? //當(dāng)前值增加delta玉雾,返回新值翔试,底層CAS操作? ? public final int addAndGet(int delta) {

? ? ? ? return unsafe.getAndAddInt(this, valueOffset, delta) + delta;

? ? }

? //省略一些不常用的方法....}

通過上述的分析,可以發(fā)現(xiàn)AtomicInteger原子類的內(nèi)部幾乎是基于前面分析過Unsafe類中的CAS相關(guān)操作的方法實(shí)現(xiàn)的复旬,這也同時(shí)證明AtomicInteger是基于無鎖實(shí)現(xiàn)的垦缅,這里重點(diǎn)分析自增操作實(shí)現(xiàn)過程,其他方法自增實(shí)現(xiàn)原理一樣驹碍。

//當(dāng)前值加1壁涎,返回新值,底層CAS操作public final int incrementAndGet() {

? ? return unsafe.getAndAddInt(this, valueOffset, 1) + 1;

}

我們發(fā)現(xiàn)AtomicInteger類中所有自增或自減的方法都間接調(diào)用Unsafe類中的getAndAddInt()方法實(shí)現(xiàn)了CAS操作志秃,從而保證了線程安全怔球,關(guān)于getAndAddInt其實(shí)前面已分析過,它是Unsafe類中1.8新增的方法浮还,源碼如下

//Unsafe類中的getAndAddInt方法public final int getAndAddInt(Object o, long offset, int delta) {

? ? ? ? int v;

? ? ? ? do {

? ? ? ? ? ? v = getIntVolatile(o, offset);

? ? ? ? } while (!compareAndSwapInt(o, offset, v, v + delta));

? ? ? ? return v;

? ? }

可看出getAndAddInt通過一個(gè)while循環(huán)不斷的重試更新要設(shè)置的值竟坛,直到成功為止,調(diào)用的是Unsafe類中的compareAndSwapInt方法钧舌,是一個(gè)CAS操作方法担汤。這里需要注意的是,上述源碼分析是基于JDK1.8的洼冻,如果是1.8之前的方法崭歧,AtomicInteger源碼實(shí)現(xiàn)有所不同,是基于for死循環(huán)的碘赖,如下

//JDK 1.7的源碼驾荣,由for的死循環(huán)實(shí)現(xiàn),并且直接在AtomicInteger實(shí)現(xiàn)該方法普泡,//JDK1.8后播掷,該方法實(shí)現(xiàn)已移動(dòng)到Unsafe類中,直接調(diào)用getAndAddInt方法即可public final int incrementAndGet() {

? ? for (;;) {

? ? ? ? int current = get();

? ? ? ? int next = current + 1;

? ? ? ? if (compareAndSet(current, next))

? ? ? ? ? ? return next;

? ? }

}

ok~,下面簡單看個(gè)Demo撼班,感受一下AtomicInteger使用方式

public classAtomicIntegerDemo{ //創(chuàng)建AtomicInteger,用于自增操作 static AtomicInteger i=new AtomicInteger();

? ? public static classAddThreadimplementsRunnable{? ? ? ? public void run(){

? ? ? ? ? for(int k=0;k<10000;k++)

? ? ? ? ? ? ? i.incrementAndGet();

? ? ? ? }

? ? }

? ? public static void main(String[] args) throws InterruptedException {

? ? ? ? Thread[] ts=new Thread[10];

? ? ? ? //開啟10條線程同時(shí)執(zhí)行i的自增操作? ? ? ? for(int k=0;k<10;k++){

? ? ? ? ? ? ts[k]=new Thread(new AddThread());

? ? ? ? }

? ? ? ? //啟動(dòng)線程? ? ? ? for(int k=0;k<10;k++){ts[k].start();}

? ? ? ? for(int k=0;k<10;k++){ts[k].join();}

? ? ? ? System.out.println(i);//輸出結(jié)果:100000? ? }

}

在Demo中歧匈,使用原子類型AtomicInteger替換普通int類型執(zhí)行自增的原子操作,保證了線程安全砰嘁。至于AtomicBoolean和AtomicLong的使用方式以及實(shí)現(xiàn)原理是一樣件炉,大家可以自行查閱源碼。

原子更新引用

原子更新引用類型可以同時(shí)更新引用類型矮湘,這里主要分析一下AtomicReference原子類斟冕,即原子更新引用類型。先看看其使用方式缅阳,如下

public class AtomicReferenceDemo2 {

? ? public static AtomicReference atomicUserRef = new AtomicReference();

? ? public static void main(String[] args) {

? ? ? ? User user = new User("zejian", 18);

? ? ? ? atomicUserRef.set(user);

? ? ? ? User updateUser = new User("Shine", 25);

? ? ? ? atomicUserRef.compareAndSet(user, updateUser);

? ? ? ? //執(zhí)行結(jié)果:User{name='Shine', age=25}? ? ? ? ? ? ? System.out.println(atomicUserRef.get().toString());?

? ? }

? ? static class User {

? ? ? ? public String name;

? ? ? ? private int age;

? ? ? ? public User(String name, int age) {

? ? ? ? ? ? this.name = name;

? ? ? ? ? ? this.age = age;

? ? ? ? }

? ? ? ? public String getName() {

? ? ? ? ? ? return name;

? ? ? ? }

? ? ? ? @Override

? ? ? ? public String toString() {

? ? ? ? ? ? return "User{" +

? ? ? ? ? ? ? ? ? ? "name='" + name + '\'' +

? ? ? ? ? ? ? ? ? ? ", age=" + age +

? ? ? ? ? ? ? ? ? ? '}';

? ? ? ? }

? ? }

}

那么AtomicReference原子類內(nèi)部是如何實(shí)現(xiàn)CAS操作的呢磕蛇?

private static final Unsafe unsafe = Unsafe.getUnsafe();

? ? private static final long valueOffset;

? ? static {

? ? ? ? try {

? ? ? ? ? ? valueOffset = unsafe.objectFieldOffset

? ? ? ? ? ? ? ? (AtomicReference.class.getDeclaredField("value"));

? ? ? ? } catch (Exception ex) { throw new Error(ex); }

? ? }

? ? //內(nèi)部變量value,Unsafe類通過valueOffset內(nèi)存偏移量即可獲取該變量? ? private volatile V value;//CAS方法十办,間接調(diào)用unsafe.compareAndSwapObject(),它是一個(gè)//實(shí)現(xiàn)了CAS操作的native方法public final boolean compareAndSet(V expect, V update) {

? ? ? ? return unsafe.compareAndSwapObject(this, valueOffset, expect, update);

}//設(shè)置并獲取舊值public final V getAndSet(V newValue) {

? ? ? ? return (V)unsafe.getAndSetObject(this, valueOffset, newValue);

? ? }

? ? //省略其他代碼......}//Unsafe類中的getAndSetObject方法秀撇,實(shí)際調(diào)用還是CAS操作public final Object getAndSetObject(Object o, long offset, Object newValue) {

? ? ? Object v;

? ? ? do {

? ? ? ? ? v = getObjectVolatile(o, offset);

? ? ? } while (!compareAndSwapObject(o, offset, v, newValue));

? ? ? return v;

? }

從源碼看來,AtomicReference與AtomicInteger的實(shí)現(xiàn)原理基本是一樣的向族,最終執(zhí)行的還是Unsafe類呵燕,關(guān)于AtomicReference的其他方法也是一樣的,如下


紅框內(nèi)的方法是Java8新增的件相,可以基于Lambda表達(dá)式對(duì)傳遞進(jìn)來的期望值或要更新的值進(jìn)行其他操作后再進(jìn)行CAS操作再扭,說白了就是對(duì)期望值或要更新的值進(jìn)行額外修改后再執(zhí)行CAS更新,在所有的Atomic原子類中幾乎都存在這幾個(gè)方法适肠。

原子更新數(shù)組

原子更新數(shù)組指的是通過原子的方式更新數(shù)組里的某個(gè)元素霍衫,主要有以下3個(gè)類

AtomicIntegerArray:原子更新整數(shù)數(shù)組里的元素

AtomicLongArray:原子更新長整數(shù)數(shù)組里的元素

AtomicReferenceArray:原子更新引用類型數(shù)組里的元素

這里以AtomicIntegerArray為例進(jìn)行分析,其余兩個(gè)使用方式和實(shí)現(xiàn)原理基本一樣侯养,簡單案例如下敦跌,

public classAtomicIntegerArrayDemo{ static AtomicIntegerArray arr = new AtomicIntegerArray(10);

? ? public static classAddThreadimplementsRunnable{? ? ? ? public void run(){

? ? ? ? ? for(int k=0;k<10000;k++)

? ? ? ? ? ? ? //執(zhí)行數(shù)組中元素自增操作,參數(shù)為index,即數(shù)組下標(biāo)? ? ? ? ? ? ? arr.getAndIncrement(k%arr.length());

? ? ? ? }

? ? }

? ? public static void main(String[] args) throws InterruptedException {

? ? ? ? Thread[] ts=new Thread[10];

? ? ? ? //創(chuàng)建10條線程? ? ? ? for(int k=0;k<10;k++){

? ? ? ? ? ? ts[k]=new Thread(new AddThread());

? ? ? ? }

? ? ? ? //啟動(dòng)10條線程? ? ? ? for(int k=0;k<10;k++){ts[k].start();}

? ? ? ? for(int k=0;k<10;k++){ts[k].join();}

? ? ? ? //執(zhí)行結(jié)果? ? ? ? //[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]? ? ? ? System.out.println(arr);

? ? }

}

啟動(dòng)10條線程對(duì)數(shù)組中的元素進(jìn)行自增操作,執(zhí)行結(jié)果符合預(yù)期逛揩。使用方式比較簡單柠傍,接著看看AtomicIntegerArray內(nèi)部是如何實(shí)現(xiàn),先看看部分源碼

public classAtomicIntegerArrayimplementsjava.io.Serializable{ //獲取unsafe類的實(shí)例對(duì)象 private static final Unsafe unsafe = Unsafe.getUnsafe();

? ? //獲取數(shù)組的第一個(gè)元素內(nèi)存起始地址? ? private static final int base = unsafe.arrayBaseOffset(int[].class);

? ? private static final int shift;

? ? //內(nèi)部數(shù)組? ? private final int[] array;

? ? static {

? ? ? ? //獲取數(shù)組中一個(gè)元素占據(jù)的內(nèi)存空間? ? ? ? int scale = unsafe.arrayIndexScale(int[].class);

? ? ? ? //判斷是否為2的次冪辩稽,一般為2的次冪否則拋異常? ? ? ? if ((scale & (scale - 1)) != 0)

? ? ? ? ? ? throw new Error("data type scale not a power of two");

? ? ? ? //? ? ? ? shift = 31 - Integer.numberOfLeadingZeros(scale);

? ? }

? ? private long checkedByteOffset(int i) {

? ? ? ? if (i < 0 || i >= array.length)

? ? ? ? ? ? throw new IndexOutOfBoundsException("index " + i);

? ? ? ? return byteOffset(i);

? ? }

? ? //計(jì)算數(shù)組中每個(gè)元素的的內(nèi)存地址? ? private static long byteOffset(int i) {

? ? ? ? return ((long) i << shift) + base;

? ? }

? ? //省略其他代碼......}

通過前面對(duì)Unsafe類的分析惧笛,我們知道arrayBaseOffset方法可以獲取數(shù)組的第一個(gè)元素起始地址,而arrayIndexScale方法可以獲取每個(gè)數(shù)組元素占用的內(nèi)存空間逞泄,由于這里是Int類型患整,而Java中一個(gè)int類型占用4個(gè)字節(jié)拜效,也就是scale的值為4,那么如何根據(jù)數(shù)組下標(biāo)值計(jì)算每個(gè)元素的內(nèi)存地址呢各谚?顯然應(yīng)該是

每個(gè)數(shù)組元素的內(nèi)存地址=起始地址+元素下標(biāo) * 每個(gè)元素所占用的內(nèi)存空間

與該方法原理相同

//計(jì)算數(shù)組中每個(gè)元素的的內(nèi)存地址

private static long byteOffset(int i) { return ((long) i << shift) + base;

}

這是為什么紧憾,首先來計(jì)算出shift的值

shift = 31 - Integer.numberOfLeadingZeros(scale);

其中Integer.numberOfLeadingZeros(scale)是計(jì)算出scale的前導(dǎo)零個(gè)數(shù)(必須是連續(xù)的),scale=4昌渤,轉(zhuǎn)成二進(jìn)制為?

00000000 00000000 00000000 00000100?

即前導(dǎo)零數(shù)為29赴穗,也就是shift=2,然后利用shift來定位數(shù)組中的內(nèi)存位置膀息,在數(shù)組不越界時(shí)般眉,計(jì)算出前3個(gè)數(shù)組元素內(nèi)存地址

//第一個(gè)數(shù)組元素,index=0 潜支, 其中base為起始地址甸赃,4代表int類型占用的字節(jié)數(shù) address = base + 0 * 4 即address= base + 0 << 2//第二個(gè)數(shù)組元素,index=1address = base + 1 * 4 即address= base + 1 << 2//第三個(gè)數(shù)組元素冗酿,index=2address = base + 2 * 4 即address= base + 2 << 2//........

顯然shift=2辑奈,替換去就是

address= base + i << shift

這就是 byteOffset(int i) 方法的計(jì)算原理。因此byteOffset(int)方法可以根據(jù)數(shù)組下標(biāo)計(jì)算出每個(gè)元素的內(nèi)存地址已烤。至于其他方法就比較簡單了鸠窗,都是間接調(diào)用Unsafe類的CAS原子操作方法,如下簡單看其中幾個(gè)常用方法

//執(zhí)行自增操作胯究,返回舊值稍计,i是指數(shù)組元素下標(biāo)public final int getAndIncrement(int i) {

? ? ? return getAndAdd(i, 1);

}//指定下標(biāo)元素執(zhí)行自增操作,并返回新值public final int incrementAndGet(int i) {

? ? return getAndAdd(i, 1) + 1;

}//指定下標(biāo)元素執(zhí)行自減操作裕循,并返回新值public final int decrementAndGet(int i) {

? ? return getAndAdd(i, -1) - 1;

}//間接調(diào)用unsafe.getAndAddInt()方法public final int getAndAdd(int i, int delta) {

? ? return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);

}//Unsafe類中的getAndAddInt方法臣嚣,執(zhí)行CAS操作public final int getAndAddInt(Object o, long offset, int delta) {

? ? ? ? int v;

? ? ? ? do {

? ? ? ? ? ? v = getIntVolatile(o, offset);

? ? ? ? } while (!compareAndSwapInt(o, offset, v, v + delta));

? ? ? ? return v;

? ? }

于AtomicLongArray和AtomicReferenceArray原子類,使用方式和實(shí)現(xiàn)原理基本一樣剥哑。

原子更新屬性

如果我們只需要某個(gè)類里的某個(gè)字段硅则,也就是說讓普通的變量也享受原子操作,可以使用原子更新字段類株婴,如在某些時(shí)候由于項(xiàng)目前期考慮不周全怎虫,項(xiàng)目需求又發(fā)生變化俩莽,使得某個(gè)類中的變量需要執(zhí)行多線程操作柿隙,由于該變量多處使用,改動(dòng)起來比較麻煩祭陷,而且原來使用的地方無需使用線程安全座哩,只要求新場(chǎng)景需要使用時(shí)徒扶,可以借助原子更新器處理這種場(chǎng)景,Atomic并發(fā)包提供了以下三個(gè)類:

AtomicIntegerFieldUpdater:原子更新整型的字段的更新器根穷。

AtomicLongFieldUpdater:原子更新長整型字段的更新器姜骡。

AtomicReferenceFieldUpdater:原子更新引用類型里的字段导坟。

請(qǐng)注意原子更新器的使用存在比較苛刻的條件如下

操作的字段不能是static類型。

操作的字段不能是final類型的圈澈,因?yàn)閒inal根本沒法修改乍迄。

字段必須是volatile修飾的,也就是數(shù)據(jù)本身是讀一致的士败。

屬性必須對(duì)當(dāng)前的Updater所在的區(qū)域是可見的,如果不是當(dāng)前類內(nèi)部進(jìn)行原子更新器操作不能使用private褥伴,protected子類操作父類時(shí)修飾符必須是protect權(quán)限及以上谅将,如果在同一個(gè)package下則必須是default權(quán)限及以上,也就是說無論何時(shí)都應(yīng)該保證操作類與被操作類間的可見性重慢。

下面看看AtomicIntegerFieldUpdater和AtomicReferenceFieldUpdater的簡單使用方式

public classAtomicIntegerFieldUpdaterDemo{ public static classCandidate{ int id;

? ? ? ? volatile int score;

? ? }

? ? public static classGame{? ? ? ? int id;

? ? ? ? volatile String name;

? ? ? ? public Game(int id, String name) {

? ? ? ? ? ? this.id = id;

? ? ? ? ? ? this.name = name;

? ? ? ? }

? ? ? ? @Override? ? ? ? public String toString() {

? ? ? ? ? ? return "Game{" +

? ? ? ? ? ? ? ? ? ? "id=" + id +

? ? ? ? ? ? ? ? ? ? ", name='" + name + '\'' +

? ? ? ? ? ? ? ? ? ? '}';

? ? ? ? }

? ? }

? ? static AtomicIntegerFieldUpdater atIntegerUpdater

? ? ? ? = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");

? ? static AtomicReferenceFieldUpdater atRefUpdate =

? ? ? ? ? ? AtomicReferenceFieldUpdater.newUpdater(Game.class,String.class,"name");

? ? //用于驗(yàn)證分?jǐn)?shù)是否正確? ? public static AtomicInteger allScore=new AtomicInteger(0);

? ? public static void main(String[] args) throws InterruptedException {

? ? ? ? final Candidate stu=new Candidate();

? ? ? ? Thread[] t=new Thread[10000];

? ? ? ? //開啟10000個(gè)線程? ? ? ? for(int i = 0 ; i < 10000 ; i++) {

? ? ? ? ? ? t[i]=new Thread() {

? ? ? ? ? ? ? ? public void run() {

? ? ? ? ? ? ? ? ? ? if(Math.random()>0.4){

? ? ? ? ? ? ? ? ? ? ? ? atIntegerUpdater.incrementAndGet(stu);

? ? ? ? ? ? ? ? ? ? ? ? allScore.incrementAndGet();

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? };

? ? ? ? ? ? t[i].start();

? ? ? ? }

? ? ? ? for(int i = 0 ; i < 10000 ; i++) {? t[i].join();}

? ? ? ? System.out.println("最終分?jǐn)?shù)score="+stu.score);

? ? ? ? System.out.println("校驗(yàn)分?jǐn)?shù)allScore="+allScore);

? ? ? ? //AtomicReferenceFieldUpdater 簡單的使用? ? ? ? Game game = new Game(2,"zh");

? ? ? ? atRefUpdate.compareAndSet(game,game.name,"JAVA-HHH");

? ? ? ? System.out.println(game.toString());

? ? ? ? /**

? ? ? ? * 輸出結(jié)果:

? ? ? ? * 最終分?jǐn)?shù)score=5976

? ? ? ? ? 校驗(yàn)分?jǐn)?shù)allScore=5976

? ? ? ? ? Game{id=2, name='JAVA-HHH'}

? ? ? ? */? ? }

}

我們使用AtomicIntegerFieldUpdater更新候選人(Candidate)的分?jǐn)?shù)score饥臂,開啟了10000條線程投票,當(dāng)隨機(jī)值大于0.4時(shí)算一票似踱,分?jǐn)?shù)自增一次隅熙,其中allScore用于驗(yàn)證分?jǐn)?shù)是否正確(其實(shí)用于驗(yàn)證AtomicIntegerFieldUpdater更新的字段是否線程安全),當(dāng)allScore與score相同時(shí)核芽,則說明投票結(jié)果無誤囚戚,也代表AtomicIntegerFieldUpdater能正確更新字段score的值,是線程安全的轧简。對(duì)于AtomicReferenceFieldUpdater驰坊,我們?cè)诖a中簡單演示了其使用方式,注意在AtomicReferenceFieldUpdater注明泛型時(shí)需要兩個(gè)泛型參數(shù)哮独,一個(gè)是修改的類類型拳芙,一個(gè)修改字段的類型。至于AtomicLongFieldUpdater則與AtomicIntegerFieldUpdater類似皮璧,不再介紹舟扎。接著簡單了解一下AtomicIntegerFieldUpdater的實(shí)現(xiàn)原理,實(shí)際就是反射和Unsafe類結(jié)合悴务,AtomicIntegerFieldUpdater是個(gè)抽象類睹限,實(shí)際實(shí)現(xiàn)類為AtomicIntegerFieldUpdaterImpl

public abstract class AtomicIntegerFieldUpdater {

? ? public static AtomicIntegerFieldUpdater newUpdater(Class tclass,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? String fieldName) {

? ? ? ? //實(shí)際實(shí)現(xiàn)類AtomicIntegerFieldUpdaterImpl? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return new AtomicIntegerFieldUpdaterImpl

? ? ? ? ? ? (tclass, fieldName, Reflection.getCallerClass());

? ? }

}

看看AtomicIntegerFieldUpdaterImpl

private static classAtomicIntegerFieldUpdaterImplextendsAtomicIntegerFieldUpdater { private static final Unsafe unsafe = Unsafe.getUnsafe();

? ? ? ? private final long offset;//內(nèi)存偏移量? ? ? ? private final Class tclass;

? ? ? ? private final Class cclass;

? ? ? ? AtomicIntegerFieldUpdaterImpl(final Class tclass,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? final String fieldName,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? final Class caller) {

? ? ? ? ? ? final Field field;//要修改的字段? ? ? ? ? ? final int modifiers;//字段修飾符? ? ? ? ? ? try {

? ? ? ? ? ? ? ? field = AccessController.doPrivileged(

? ? ? ? ? ? ? ? ? ? new PrivilegedExceptionAction() {

? ? ? ? ? ? ? ? ? ? ? ? public Field run() throws NoSuchFieldException {

? ? ? ? ? ? ? ? ? ? ? ? ? ? return tclass.getDeclaredField(fieldName);//反射獲取字段對(duì)象? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? });

? ? ? ? ? ? ? ? ? ? //獲取字段修飾符? ? ? ? ? ? ? ? modifiers = field.getModifiers();

? ? ? ? ? ? //對(duì)字段的訪問權(quán)限進(jìn)行檢查,不在訪問范圍內(nèi)拋異常? ? ? ? ? ? ? ? sun.reflect.misc.ReflectUtil.ensureMemberAccess(

? ? ? ? ? ? ? ? ? ? caller, tclass, null, modifiers);

? ? ? ? ? ? ? ? ClassLoader cl = tclass.getClassLoader();

? ? ? ? ? ? ? ? ClassLoader ccl = caller.getClassLoader();

? ? ? ? ? ? ? ? if ((ccl != null) && (ccl != cl) &&

? ? ? ? ? ? ? ? ? ? ((cl == null) || !isAncestor(cl, ccl))) {

? ? ? ? ? ? ? sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? } catch (PrivilegedActionException pae) {

? ? ? ? ? ? ? ? throw new RuntimeException(pae.getException());

? ? ? ? ? ? } catch (Exception ex) {

? ? ? ? ? ? ? ? throw new RuntimeException(ex);

? ? ? ? ? ? }

? ? ? ? ? ? Class fieldt = field.getType();

? ? ? ? ? ? //判斷是否為int類型? ? ? ? ? ? if (fieldt != int.class)

? ? ? ? ? ? ? ? throw new IllegalArgumentException("Must be integer type");

? ? ? ? ? ? //判斷是否被volatile修飾? ? ? ? ? ? if (!Modifier.isVolatile(modifiers))

? ? ? ? ? ? ? ? throw new IllegalArgumentException("Must be volatile type");

? ? ? ? ? ? this.cclass = (Modifier.isProtected(modifiers) &&

? ? ? ? ? ? ? ? ? ? ? ? ? caller != tclass) ? caller : null;

? ? ? ? ? ? this.tclass = tclass;

? ? ? ? ? ? //獲取該字段的在對(duì)象內(nèi)存的偏移量,通過內(nèi)存偏移量可以獲取或者修改該字段的值? ? ? ? ? ? offset = unsafe.objectFieldOffset(field);

? ? ? ? }

? ? ? ? }

從AtomicIntegerFieldUpdaterImpl的構(gòu)造器也可以看出更新器為什么會(huì)有這么多限制條件了讯檐,當(dāng)然最終其CAS操作肯定是通過unsafe完成的邦泄,簡單看一個(gè)方法

public int incrementAndGet(T obj) {

? ? ? ? int prev, next;

? ? ? ? do {

? ? ? ? ? ? prev = get(obj);

? ? ? ? ? ? next = prev + 1;

? ? ? ? ? ? //CAS操作? ? ? ? } while (!compareAndSet(obj, prev, next));

? ? ? ? return next;

}//最終調(diào)用的還是unsafe.compareAndSwapInt()方法public boolean compareAndSet(T obj, int expect, int update) {

? ? ? ? ? ? if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);

? ? ? ? ? ? return unsafe.compareAndSwapInt(obj, offset, expect, update);

? ? ? ? }

CAS的ABA問題及其解決方案

假設(shè)這樣一種場(chǎng)景,當(dāng)?shù)谝粋€(gè)線程執(zhí)行CAS(V,E,U)操作裂垦,在獲取到當(dāng)前變量V顺囊,準(zhǔn)備修改為新值U前,另外兩個(gè)線程已連續(xù)修改了兩次變量V的值蕉拢,使得該值又恢復(fù)為舊值特碳,這樣的話诚亚,我們就無法正確判斷這個(gè)變量是否已被修改過,如下圖


這就是典型的CAS的ABA問題午乓,一般情況這種情況發(fā)現(xiàn)的概率比較小站宗,可能發(fā)生了也不會(huì)造成什么問題,比如說我們對(duì)某個(gè)做加減法益愈,不關(guān)心數(shù)字的過程梢灭,那么發(fā)生ABA問題也沒啥關(guān)系。但是在某些情況下還是需要防止的蒸其,那么該如何解決呢敏释?在Java中解決ABA問題,我們可以使用以下兩個(gè)原子類

AtomicStampedReference

AtomicStampedReference原子類是一個(gè)帶有時(shí)間戳的對(duì)象引用摸袁,在每次修改后钥顽,AtomicStampedReference不僅會(huì)設(shè)置新值而且還會(huì)記錄更改的時(shí)間。當(dāng)AtomicStampedReference設(shè)置對(duì)象值時(shí)靠汁,對(duì)象值以及時(shí)間戳都必須滿足期望值才能寫入成功蜂大,這也就解決了反復(fù)讀寫時(shí),無法預(yù)知值是否已被修改的窘境蝶怔,測(cè)試demo如下

/**

* Created by zejian on 2017/7/2.

* Blog : http://blog.csdn.net/javazejian [原文地址,請(qǐng)尊重原創(chuàng)]

*/public classABADemo{? ? static AtomicInteger atIn = new AtomicInteger(100);

? ? //初始化時(shí)需要傳入一個(gè)初始值和初始時(shí)間? ? static AtomicStampedReference atomicStampedR =

? ? ? ? ? ? new AtomicStampedReference(200,0);

? ? static Thread t1 = new Thread(new Runnable() {

? ? ? ? @Override? ? ? ? public void run() {

? ? ? ? ? ? //更新為200? ? ? ? ? ? atIn.compareAndSet(100, 200);

? ? ? ? ? ? //更新為100? ? ? ? ? ? atIn.compareAndSet(200, 100);

? ? ? ? }

? ? });

? ? static Thread t2 = new Thread(new Runnable() {

? ? ? ? @Override? ? ? ? public void run() {

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(1);

? ? ? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

? ? ? ? ? ? boolean flag=atIn.compareAndSet(100,500);

? ? ? ? ? ? System.out.println("flag:"+flag+",newValue:"+atIn);

? ? ? ? }

? ? });

? ? static Thread t3 = new Thread(new Runnable() {

? ? ? ? @Override? ? ? ? public void run() {

? ? ? ? ? ? int time=atomicStampedR.getStamp();

? ? ? ? ? ? //更新為200? ? ? ? ? ? atomicStampedR.compareAndSet(100, 200,time,time+1);

? ? ? ? ? ? //更新為100? ? ? ? ? ? int time2=atomicStampedR.getStamp();

? ? ? ? ? ? atomicStampedR.compareAndSet(200, 100,time2,time2+1);

? ? ? ? }

? ? });

? ? static Thread t4 = new Thread(new Runnable() {

? ? ? ? @Override? ? ? ? public void run() {

? ? ? ? ? ? int time = atomicStampedR.getStamp();

? ? ? ? ? ? System.out.println("sleep 前 t4 time:"+time);

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(1);

? ? ? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

? ? ? ? ? ? boolean flag=atomicStampedR.compareAndSet(100,500,time,time+1);

? ? ? ? ? ? System.out.println("flag:"+flag+",newValue:"+atomicStampedR.getReference());

? ? ? ? }

? ? });

? ? public static? void? main(String[] args) throws InterruptedException {

? ? ? ? t1.start();

? ? ? ? t2.start();

? ? ? ? t1.join();

? ? ? ? t2.join();

? ? ? ? t3.start();

? ? ? ? t4.start();

? ? ? ? /**

? ? ? ? * 輸出結(jié)果:

? ? ? ? flag:true,newValue:500

? ? ? ? sleep 前 t4 time:0

? ? ? ? flag:false,newValue:200

? ? ? ? */? ? }

}

對(duì)比輸出結(jié)果可知奶浦,AtomicStampedReference類確實(shí)解決了ABA的問題,下面我們簡單看看其內(nèi)部實(shí)現(xiàn)原理

public class AtomicStampedReference {

? ? //通過Pair內(nèi)部類存儲(chǔ)數(shù)據(jù)和時(shí)間戳? ? private static class Pair {

? ? ? ? final T reference;

? ? ? ? final int stamp;

? ? ? ? private Pair(T reference, int stamp) {

? ? ? ? ? ? this.reference = reference;

? ? ? ? ? ? this.stamp = stamp;

? ? ? ? }

? ? ? ? static Pair of(T reference, int stamp) {

? ? ? ? ? ? return new Pair(reference, stamp);

? ? ? ? }

? ? }

? ? //存儲(chǔ)數(shù)值和時(shí)間的內(nèi)部類? ? private volatile Pair pair;

? ? //構(gòu)造器踢星,創(chuàng)建時(shí)需傳入初始值和時(shí)間初始值? ? public AtomicStampedReference(V initialRef, int initialStamp) {

? ? ? ? pair = Pair.of(initialRef, initialStamp);

? ? }

}

接著看看其compareAndSet方法的實(shí)現(xiàn):

public boolean compareAndSet(V expectedReference,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? V? newReference,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int expectedStamp,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int newStamp) {

? ? ? ? Pair current = pair;

? ? ? ? return? ? ? ? ? ? expectedReference == current.reference &&

? ? ? ? ? ? expectedStamp == current.stamp &&

? ? ? ? ? ? ((newReference == current.reference &&

? ? ? ? ? ? ? newStamp == current.stamp) ||

? ? ? ? ? ? casPair(current, Pair.of(newReference, newStamp)));

? ? }

同時(shí)對(duì)當(dāng)前數(shù)據(jù)和當(dāng)前時(shí)間進(jìn)行比較财喳,只有兩者都相等是才會(huì)執(zhí)行casPair()方法,單從該方法的名稱就可知是一個(gè)CAS方法斩狱,最終調(diào)用的還是Unsafe類中的compareAndSwapObject方法

private boolean casPair(Pair cmp, Pair val) {

? ? ? ? return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);

? ? }

到這我們就很清晰AtomicStampedReference的內(nèi)部實(shí)現(xiàn)思想了耳高,通過一個(gè)鍵值對(duì)Pair存儲(chǔ)數(shù)據(jù)和時(shí)間戳,在更新時(shí)對(duì)數(shù)據(jù)和時(shí)間戳進(jìn)行比較所踊,只有兩者都符合預(yù)期才會(huì)調(diào)用Unsafe的compareAndSwapObject方法執(zhí)行數(shù)值和時(shí)間戳替換泌枪,也就避免了ABA的問題。

AtomicMarkableReference類

AtomicMarkableReference與AtomicStampedReference不同的是秕岛,AtomicMarkableReference維護(hù)的是一個(gè)boolean值的標(biāo)識(shí)碌燕,也就是說至于true和false兩種切換狀態(tài),經(jīng)過博主測(cè)試继薛,這種方式并不能完全防止ABA問題的發(fā)生修壕,只能減少ABA問題發(fā)生的概率。

public classABADemo{ static AtomicMarkableReference atMarkRef =

? ? ? ? ? ? ? new AtomicMarkableReference(100,false);

static Thread t5 = new Thread(new Runnable() {

? ? ? ? @Override? ? ? ? public void run() {

? ? ? ? ? ? boolean mark=atMarkRef.isMarked();

? ? ? ? ? ? System.out.println("mark:"+mark);

? ? ? ? ? ? //更新為200? ? ? ? ? ? System.out.println("t5 result:"+atMarkRef.compareAndSet(atMarkRef.getReference(), 200,mark,!mark));

? ? ? ? }

? ? });

? ? static Thread t6 = new Thread(new Runnable() {

? ? ? ? @Override? ? ? ? public void run() {

? ? ? ? ? ? boolean mark2=atMarkRef.isMarked();

? ? ? ? ? ? System.out.println("mark2:"+mark2);

? ? ? ? ? ? System.out.println("t6 result:"+atMarkRef.compareAndSet(atMarkRef.getReference(), 100,mark2,!mark2));

? ? ? ? }

? ? });

? ? static Thread t7 = new Thread(new Runnable() {

? ? ? ? @Override? ? ? ? public void run() {

? ? ? ? ? ? boolean mark=atMarkRef.isMarked();

? ? ? ? ? ? System.out.println("sleep 前 t7 mark:"+mark);

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(1);

? ? ? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

? ? ? ? ? ? boolean flag=atMarkRef.compareAndSet(100,500,mark,!mark);

? ? ? ? ? ? System.out.println("flag:"+flag+",newValue:"+atMarkRef.getReference());

? ? ? ? }

? ? });

? ? public static? void? main(String[] args) throws InterruptedException {? ? ? ?

? ? ? ? t5.start();t5.join();

? ? ? ? t6.start();t6.join();

? ? ? ? t7.start();

? ? ? ? /**

? ? ? ? * 輸出結(jié)果:

? ? ? ? mark:false

? ? ? ? t5 result:true

? ? ? ? mark2:true

? ? ? ? t6 result:true

? ? ? ? sleep 前 t5 mark:false

? ? ? ? flag:true,newValue:500 ---->成功了.....說明還是發(fā)生ABA問題

? ? ? ? */? ? }

}

AtomicMarkableReference的實(shí)現(xiàn)原理與AtomicStampedReference類似遏考,這里不再介紹慈鸠。到此,我們也明白了如果要完全杜絕ABA問題的發(fā)生灌具,我們應(yīng)該使用AtomicStampedReference原子類更新對(duì)象青团,而對(duì)于AtomicMarkableReference來說只能減少ABA問題的發(fā)生概率譬巫,并不能杜絕。

再談自旋鎖

自旋鎖是一種假設(shè)在不久將來督笆,當(dāng)前的線程可以獲得鎖芦昔,因此虛擬機(jī)會(huì)讓當(dāng)前想要獲取鎖的線程做幾個(gè)空循環(huán)(這也是稱為自旋的原因),在經(jīng)過若干次循環(huán)后娃肿,如果得到鎖咕缎,就順利進(jìn)入臨界區(qū)。如果還不能獲得鎖料扰,那就會(huì)將線程在操作系統(tǒng)層面掛起凭豪,這種方式確實(shí)也是可以提升效率的。但問題是當(dāng)線程越來越多競(jìng)爭很激烈時(shí)记罚,占用CPU的時(shí)間變長會(huì)導(dǎo)致性能急劇下降,因此Java虛擬機(jī)內(nèi)部一般對(duì)于自旋鎖有一定的次數(shù)限制壳嚎,可能是50或者100次循環(huán)后就放棄桐智,直接掛起線程,讓出CPU資源烟馅。如下通過AtomicReference可實(shí)現(xiàn)簡單的自旋鎖说庭。

public classSpinLock{ private AtomicReference sign =new AtomicReference<>();

? public void lock(){

? ? Thread current = Thread.currentThread();

? ? while(!sign .compareAndSet(null, current)){

? ? }

? }

? public void unlock (){

? ? Thread current = Thread.currentThread();

? ? sign .compareAndSet(current, null);

? }

}

使用CAS原子操作作為底層實(shí)現(xiàn),lock()方法將要更新的值設(shè)置為當(dāng)前線程郑趁,并將預(yù)期值設(shè)置為null刊驴。unlock()函數(shù)將要更新的值設(shè)置為null,并預(yù)期值設(shè)置為當(dāng)前線程寡润。然后我們通過lock()和unlock來控制自旋鎖的開啟與關(guān)閉捆憎,注意這是一種非公平鎖。事實(shí)上AtomicInteger(或者AtomicLong)原子類內(nèi)部的CAS操作也是通過不斷的自循環(huán)(while循環(huán))實(shí)現(xiàn)梭纹,不過這種循環(huán)的結(jié)束條件是線程成功更新對(duì)于的值躲惰,但也是自旋鎖的一種。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末变抽,一起剝皮案震驚了整個(gè)濱河市础拨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绍载,老刑警劉巖诡宗,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異击儡,居然都是意外死亡塔沃,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門阳谍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芳悲,“玉大人立肘,你說我怎么就攤上這事∶福” “怎么了谅年?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肮韧。 經(jīng)常有香客問我融蹂,道長,這世上最難降的妖魔是什么弄企? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任超燃,我火速辦了婚禮,結(jié)果婚禮上拘领,老公的妹妹穿的比我還像新娘意乓。我一直安慰自己,他們只是感情好约素,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布届良。 她就那樣靜靜地躺著,像睡著了一般圣猎。 火紅的嫁衣襯著肌膚如雪士葫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天送悔,我揣著相機(jī)與錄音慢显,去河邊找鬼。 笑死欠啤,一個(gè)胖子當(dāng)著我的面吹牛荚藻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洁段,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼鞋喇,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了眉撵?” 一聲冷哼從身側(cè)響起侦香,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纽疟,沒想到半個(gè)月后罐韩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡污朽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年散吵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡矾睦,死狀恐怖晦款,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情枚冗,我是刑警寧澤缓溅,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站赁温,受9級(jí)特大地震影響坛怪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜股囊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一袜匿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧稚疹,春花似錦居灯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至其屏,卻和暖如春喇勋,著一層夾襖步出監(jiān)牢的瞬間缨该,已是汗流浹背偎行。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贰拿,地道東北人蛤袒。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像膨更,于是被迫代替她去往敵國和親妙真。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容