無鎖的概念
在談?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ù)的奇妙之處。
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)死鎖的情況乒躺,也就是說無鎖操作天生免疫死鎖。
或許我們可能會(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類存在于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ù)組里的某個(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ì)于的值躲惰,但也是自旋鎖的一種。