深入理解CAS:以AtomicInteger爲例

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從本篇文章開始,我們將對JDK併發包"},{"type":"codeinline","content":[{"type":"text","text":"java.util.concurrent"}]},{"type":"text","text":"中相關類的源碼進行分析,通過分析源碼,能讓我們儘快地掌握併發包中提供的併發工具,能讓我們更好地利用這些併發工具寫出更加好的代碼。本篇文章的主角是AtomicInteger,接下來,請跟隨文章的節奏一起分析AtomicInteger吧!"}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"一、問題場景引入"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大家都清楚,在多線程環境下,"},{"type":"codeinline","content":[{"type":"text","text":"i++"}]},{"type":"text","text":"會存在線程不安全問題,原因是因爲"},{"type":"codeinline","content":[{"type":"text","text":"i++"}]},{"type":"text","text":"不是一個原子操作,它可以被解析爲"},{"type":"codeinline","content":[{"type":"text","text":"i = i + 1"}]},{"type":"text","text":",它在運行時是被劃分爲三個步驟,分別是從主存中讀取"},{"type":"codeinline","content":[{"type":"text","text":"i"}]},{"type":"text","text":"的值到線程的工作內存中,然後線程對"},{"type":"codeinline","content":[{"type":"text","text":"i"}]},{"type":"text","text":"值進行"},{"type":"codeinline","content":[{"type":"text","text":"+1"}]},{"type":"text","text":"操作,最後將計算後的"},{"type":"codeinline","content":[{"type":"text","text":"i"}]},{"type":"text","text":"值寫回到主存中。因爲這三個步驟不是一個原子操作,那麼就存在某個線程A在進行"},{"type":"codeinline","content":[{"type":"text","text":"i++"}]},{"type":"text","text":"操作的過程中,線程B對主存中的"},{"type":"codeinline","content":[{"type":"text","text":"i"}]},{"type":"text","text":"值進行了讀取並完成修改,那麼此時線程A的計算結果就不正確了,且會出現數據被覆蓋的問題,這是線程不安全的根本原因。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/dd/ddf15a9883bccedb30f7560640f120c2.png","alt":"在這裏插入圖片描述","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解決這種線程之間不可見且非原子性的操作,通常可以使用"},{"type":"codeinline","content":[{"type":"text","text":"synchronized"}]},{"type":"text","text":"關鍵字來保證數據的正確性,基本的代碼如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class AtomicExample {\n \n private int size = 0;\n \n public synchronized void increment() {\n size++;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用"},{"type":"codeinline","content":[{"type":"text","text":"synchronized"}]},{"type":"text","text":"關鍵字後,某一個時間段只能有一個線程來進行"},{"type":"codeinline","content":[{"type":"text","text":"size++"}]},{"type":"text","text":"的操作,其他線程如果需要進行同樣的操作,那麼必須等待當前線程結束並釋放鎖後才能操作,這樣就保證了數據的安全性,避免了數據被覆蓋的風險。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然"},{"type":"codeinline","content":[{"type":"text","text":"synchronized"}]},{"type":"text","text":"關鍵字能保證線程的安全,但是"},{"type":"codeinline","content":[{"type":"text","text":"synchronized"}]},{"type":"text","text":"關鍵字涉及到線程之間的資源競爭與鎖的獲取和釋放,其性能略低,那麼在JDK中是是否有替代方案呢?答案當然是有,"},{"type":"codeinline","content":[{"type":"text","text":"AtomicInteger"}]},{"type":"text","text":"在這種場景下可以替代"},{"type":"codeinline","content":[{"type":"text","text":"synchronized"}]},{"type":"text","text":"關鍵字,且性能優於"},{"type":"codeinline","content":[{"type":"text","text":"synchronized"}]},{"type":"text","text":"關鍵字,基本的代碼如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class AtomicExample {\n\n private final AtomicInteger size = new AtomicInteger(0);\n \n public void increment() {\n size.getAndIncrement();\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼問題來了,AtomicInteger是如何保證線程的安全的呢?這就需要進入到AtomicInteger源碼中一探究竟了!"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"二、AtomicInteger源碼解析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們以上面的第二段代碼爲例,我們進入到AtomicInteger的源碼(基於"},{"type":"codeinline","content":[{"type":"text","text":"JDK8"}]},{"type":"text","text":"),部分源碼粘貼如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class AtomicInteger extends Number implements java.io.Serializable {\n\t\n \t// unsafe對象的定義\n \tprivate static final Unsafe unsafe = Unsafe.getUnsafe();\n \n\t// 省略部分代碼\n\t// 使用volatile修飾了一個變量用來存儲數值\n private volatile int value;\n\n \t// 構造函數直接初始化value值\n public AtomicInteger(int initialValue) {\n value = initialValue;\n }\n\n // 無參構造,此時value默認爲0\n public AtomicInteger() {\n }\n\n\t// getAndIncrement方法內部調用了unsafe的getAndAddInt方法\n public final int getAndIncrement() {\n \t// unsafe定義如上靜態變量所示,調用了Unsafe的getUnsafe()方法\n return unsafe.getAndAddInt(this, valueOffset, 1);\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"源碼中使用"},{"type":"codeinline","content":[{"type":"text","text":"volatile"}]},{"type":"text","text":"關鍵字修飾了"},{"type":"codeinline","content":[{"type":"text","text":"vlaue"}]},{"type":"text","text":"屬性,這樣可以保證值的可見性。而getAndIncrement方法,內部調用的是Unsafe類對象的getAndAddInt方法,在正式介紹getAndAddInt方法之前,首先簡單介紹一下Unsafe類。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java語言本身是從C++語言衍生而來的,是比C++更加高級的一門語言。Java語言和C++語言有一個重要的區別就是前者無法直接操作某個內存區域,而C++語言是可以直接操作一塊內存區域,可以實現自主內存區域申請和釋放。雖然Java屏蔽了操作內存區域的接口,但是也提供了一個類似C++可以手動管理內存的Unsafe類,有了這個類,那麼基本可以實現手動管理內存區域。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Unsafe類是包"},{"type":"codeinline","content":[{"type":"text","text":"sun.misc"}]},{"type":"text","text":"下的一個類,這個類的大部分方法都是本地方法,比如"},{"type":"codeinline","content":[{"type":"text","text":"getAndAddInt"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"allocateMemory"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"freeMemory"}]},{"type":"text","text":"等,這些方法都是使用了"},{"type":"codeinline","content":[{"type":"text","text":"native"}]},{"type":"text","text":"進行修飾,底層調用的都是C++的代碼,通常我們無法直接閱讀本地方法的具體實現,需要進一步閱讀OpenJDK的源碼。這裏先分析一下Unsafe類的構造方法和獲取其對象的基本方法,代碼如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public final class Unsafe {\n \t// Unsafe的單例對象,由類加載的初始化階段在靜態代碼塊中初始化\n \tprivate static final Unsafe theUnsafe;\n \n \tstatic {\n \t// 忽略部分代碼\n \ttheUnsafe = new Unsafe();\n }\n\n\t\tprivate Unsafe() {\n }\n\n @CallerSensitive\n public static Unsafe getUnsafe() {\n Class var0 = Reflection.getCallerClass();\n if (!VM.isSystemDomainLoader(var0.getClassLoader())) {\n throw new SecurityException(\"Unsafe\");\n } else {\n \t// theUnsafe來源於單例變量\n return theUnsafe;\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面的代碼可知,Unsafe類的構造法方法是私有的,且類本身是要的是final修飾的,所以該類不可以在外部被實例化,且不能被繼承。獲取Unsafe類對象是通過getUnsafe方法來獲取的,本質上是通過反射機制來創建對象。但是這裏有個條件,調用getUnsafe方法的類必須是由啓動類加載器加載纔可以,否則將拋出SecurityException異常。何出此言呢?我們來對這段getUnsafe方法的代碼細細地品:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static Unsafe getUnsafe() {\n\t// 第一行代碼是獲取調用當前方法的類的Class對象\n Class var0 = Reflection.getCallerClass();\n // if條件中判斷Class對象的類加載器是否是啓動類加載器,如果不是,則拋出SecurityException異常\n if (!VM.isSystemDomainLoader(var0.getClassLoader())) {\n throw new SecurityException(\"Unsafe\");\n } else {\n \t// theUnsafe來源於單例變量\n return theUnsafe;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述的"},{"type":"codeinline","content":[{"type":"text","text":"if"}]},{"type":"text","text":"條件中,如果"},{"type":"codeinline","content":[{"type":"text","text":"VM.isSystemDomainLoader(var0.getClassLoader())"}]},{"type":"text","text":"返回false,那麼將拋出異常,而這段代碼就是判斷當前調用getUnsafe方法的類的加載器是否是啓動類加載器,爲何這麼說呢?我們進入到"},{"type":"codeinline","content":[{"type":"text","text":"isSystemDomainLoader"}]},{"type":"text","text":"方法中看看:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static boolean isSystemDomainLoader(ClassLoader var0) {\n return var0 == null;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從這段代碼很簡單,就是判斷傳入的類加載器對象是否爲"},{"type":"codeinline","content":[{"type":"text","text":"null"}]},{"type":"text","text":",如果爲"},{"type":"codeinline","content":[{"type":"text","text":"null"}]},{"type":"text","text":",說明這個類加載器對象是啓動類加載器對象。可能我說了這麼多,讀者不一定理解,爲了幫助大家理解,我來簡單描述一下類加載器機制。常見的類加載器有啓動類加載器(Bootstrap ClassLoader)、拓展類加載器(Extension ClassLoader)、應用類加載器(Application ClassLoader)以及自定義類加載器(Custom ClassLoader)。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0b/0b39f759d9170489ac20df06371e6f0f.png","alt":"在這裏插入圖片描述","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上圖中不僅展示了常見的類加載器,還展示了『雙親委派機制』。雙親委派機制在JVM類加載系統中有着廣泛的應用,它要求除了啓動類加載器以外,其他所有的類加載器都應當有自己的父類加載器,也就是說,在現有的JVM實現中,啓動類加載器沒有自己的父類加載器,擴展類加載器和應用類加載器都有自己的父類加載器,其中啓動類加載器是擴展類加載器的父類加載器,擴展類加載器是應用類加載器的父類加載器,而應用類加載器是自定義類加載器的父類加載器。這裏需要注意一點,那就是這裏的父類加載器並不是表明加載器之間存在繼承關係,而是通過組合模式來實現的父類加載器的代碼複用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏補充說明一下各個類加載器負責加載的內容:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"啓動類加載器:主要的職責是加載JVM在運行是需要的核心類庫,這個類加載器不同於其他類加載器,這個類加載器是由C++語言編寫的,它是虛擬機本身的一個組成部分,負責將"},{"type":"codeinline","content":[{"type":"text","text":"/lib"}]},{"type":"text","text":"路徑下的核心類庫或"},{"type":"codeinline","content":[{"type":"text","text":"-Xbootclasspath"}]},{"type":"text","text":"參數指定的路徑下的jar包加載到內存中,但是並不是所有放置在"},{"type":"codeinline","content":[{"type":"text","text":"/lib"}]},{"type":"text","text":"路徑下的jar都會被加載,從安全角度出發,啓動類加載器只加載包名爲java、javax、sun等開頭的類。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"擴展類加載器:是指Sun公司實現的"},{"type":"codeinline","content":[{"type":"text","text":"sun.misc.Launcher$ExtClassLoader"}]},{"type":"text","text":"類,這個類加載器是由Java語言實現的,是Launcher的靜態內部類,它負責加載"},{"type":"codeinline","content":[{"type":"text","text":"/lib/ext"}]},{"type":"text","text":"目錄下或者由系統變量"},{"type":"codeinline","content":[{"type":"text","text":"-Djava.ext.dir"}]},{"type":"text","text":"指定位路徑中的類庫,開發者可以直接使用標準擴展類加載器。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"應用類加載器:是指 Sun公司實現的"},{"type":"codeinline","content":[{"type":"text","text":"sun.misc.Launcher$AppClassLoader"}]},{"type":"text","text":"。它負責加載系統類路徑"},{"type":"codeinline","content":[{"type":"text","text":"java -classpath"}]},{"type":"text","text":"或"},{"type":"codeinline","content":[{"type":"text","text":"-D java.class.path"}]},{"type":"text","text":"指定路徑下的類庫,也就是我們經常用到的classpath路徑,開發者可以直接使用系統類加載器,一般情況下該類加載是程序中默認的類加載器,通過"},{"type":"codeinline","content":[{"type":"text","text":"ClassLoader#getSystemClassLoader()"}]},{"type":"text","text":"方法可以獲取到該類加載器對象。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般情況下,開發者自己寫的類都是由應用類加載器加載的,比如如下獲取Unsafe對象的代碼:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class UnsafeTest {\n\n public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {\n Unsafe unsafe = Unsafe.getUnsafe();\n System.out.println(unsafe);\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏寫的測試類UnsafeTest是由AppClassLoader加載的,所以在這個類下調用Unsafe的getUnsafe方法,必然會拋出異常,我們打斷點測試一下:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5e/5e94414c1690a7a01219bf2c0db3de98.png","alt":"在這裏插入圖片描述","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只有啓動類加載器加載的類,其Class對象中的類加載器才爲null,因爲啓動類加載器是由C++運行時提供的加載能力,在JVM運行環境中無法獲取它的對象信息。所以說想通過Unsafe的getUnsafe方法來獲取Unsafe對象,是行不通的了,但是可以通過反射的方式來獲取。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在AtomicInteger類中,是可以通過Unsafe的getUnsafe方法來獲取Unsafe對象的,這是因爲AtomicInteger是由啓動類加載器加載的。在AtomicInteger的getAndIncrement方法中,調用了Unsafe的getAndAddInt方法,這個方法在Unsafe類中有具體實現,在看代碼之前,我們首先需要了解一下getAndAddInt方法的三個參數值的含義:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一個參數:指向當前AtomicInteger對象。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二個參數:指向當前AtomicInteger對象的value屬性在內存中的偏移量。這裏特別解釋一下這個value屬性在內存中偏移量的含義,其實就是當前AtomicInteger對象的value屬性存儲在內存中某個位置的long類型數值表示,後期通過unsafe來操作這個value屬性的時候都是直接去指定的offset處去讀取值。我們在一開始就說過,Unsafe具有像C++一樣操作內存的能力,所以這裏可以理解爲unsafe獲取value的值是直接從內存地址中讀取的。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三個參數:就是value屬性值需要加的值,這裏就是常量1。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"getAndAddInt的源碼如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public final int getAndAddInt(Object var1, long var2, int var4) {\n int var5;\n do {\n \t// 從內存中直接獲取指定對象var1的偏移量爲var2的屬性的值\n var5 = this.getIntVolatile(var1, var2);\n // 利用CAS原理來寫入新值var5 + var4\n } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));\n return var5;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"while條件中調用了unsafe的compareAndSwapInt方法,也就是常說的CAS(Compare And Swap,比較和替換)。這個方法是一個本地方法,無法看到其具體實現,但是可以通過查看OpenJDK的源碼來看到compareAndSwapInt方法的C++實現。在查看C++代碼之前,我們一起來分析一下compareAndSwapInt方法的四個參數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一個參數:指向調用getAndAddInt方法的對象,本文中是指AtomicInteger對象。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二個參數:指向調用getAndAddInt方法的對象的屬性在內存中的偏移量,這裏指的是AtomicInteger對象的value屬性的偏移量。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三個參數:這個值是從unsafe的第二個參數指定內存地址中讀取出來的值。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第四個參數:即將設置到主存中的值。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了以上對參數的理解,接下來我們重點來理解CAS原理,這裏我們一起來讀compareAndSwapInt的源碼,查看OpenJDK的源碼,compareAndSwapInt的C++實現代碼如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"jboolean\nsun::misc::Unsafe::compareAndSwapInt (jobject obj, jlong offset,\n jint expect, jint update)\n{\n // 計算出value屬性在對象所存儲的值\n jint *addr = (jint *)((char *)obj + offset);\n return compareAndSwap (addr, expect, update);\n}\n\nstatic inline bool\ncompareAndSwap (volatile jint *addr, jint old, jint new_val)\n{\n jboolean result = false;\n spinlock lock;\n if ((result = (*addr == old)))\n *addr = new_val;\n return result;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代碼中在寫回新值到主存中之前進行了一個判斷,判斷從內存中讀取到的值是否和計算新值前的老值是一致的,如果不是一致,將不會把計算後的值寫回主存,並寫返回false表示寫入失敗。這樣就使得while條件爲true,那麼將進行下一次的"},{"type":"codeinline","content":[{"type":"text","text":"do...while"}]},{"type":"text","text":"循環,直到寫入主存成功爲止。整個流程表現爲自旋的形式,先將內存中的值讀取後進行進行計算,計算完畢準備寫回主存之前進行判斷主存中的值是否發生改變,如果發生改變,則重新重複該流程,直到寫入成功爲止。基本的原理圖如下所示:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7a/7ac18e2048169aa95201cbd9e4bd9e9f.png","alt":"在這裏插入圖片描述","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"CAS"}]},{"type":"text","text":"自旋方式是很好理解的,但是存在一個"},{"type":"codeinline","content":[{"type":"text","text":"ABA"}]},{"type":"text","text":"的問題,所謂"},{"type":"codeinline","content":[{"type":"text","text":"ABA"}]},{"type":"text","text":"問題,就是存在於當前線程在計算結果值"},{"type":"codeinline","content":[{"type":"text","text":"V"}]},{"type":"text","text":"的過程中,其他線程已經完成了多次主存中值的讀取、計算、寫回,比如將值從最初"},{"type":"codeinline","content":[{"type":"text","text":"A"}]},{"type":"text","text":"修改爲"},{"type":"codeinline","content":[{"type":"text","text":"B"}]},{"type":"text","text":",後又計算改爲了"},{"type":"codeinline","content":[{"type":"text","text":"A"}]},{"type":"text","text":",雖然最後的當前線程讀取的還是"},{"type":"codeinline","content":[{"type":"text","text":"A"}]},{"type":"text","text":",但是中間狀態是沒有獲知的,這就是"},{"type":"codeinline","content":[{"type":"text","text":"ABA"}]},{"type":"text","text":"問題。"},{"type":"codeinline","content":[{"type":"text","text":"ABA"}]},{"type":"text","text":"問題廣泛存在於AtomicInteger、AtomicBoolean、AtomicLong等以Atomic開頭的原子類中(當然也有例外,比如"},{"type":"codeinline","content":[{"type":"text","text":"AtomicStampedReference"}]},{"type":"text","text":"用來解決"},{"type":"codeinline","content":[{"type":"text","text":"ABA"}]},{"type":"text","text":"問題),這是因爲它們的底層都是基於Unsafe的,Unsafe本身就存在"},{"type":"codeinline","content":[{"type":"text","text":"ABA"}]},{"type":"text","text":"問題。解決"},{"type":"codeinline","content":[{"type":"text","text":"ABA"}]},{"type":"text","text":"問題其實並不難,那就是額外加一個版本號,每次修改了主存中的值,都需要對版本號進行標記修改,其他線程寫主內存的值的時候,都需要比較比較值和版本號,兩者都一致,那麼才說明主存中的值沒有被其他線程修改,這樣可以放心去更新主存中的值。"},{"type":"codeinline","content":[{"type":"text","text":"AtomicStampedReference"}]},{"type":"text","text":"類就是使用版本號的方式來解決"},{"type":"codeinline","content":[{"type":"text","text":"ABA"}]},{"type":"text","text":"問題的,有興趣的同學可以跟我一起來閱讀其源碼。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"三、ABA問題的解決辦法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解決"},{"type":"codeinline","content":[{"type":"text","text":"ABA"}]},{"type":"text","text":"問題已經有了成熟的方案,那就是通過添加版本號來進行解決的,"},{"type":"codeinline","content":[{"type":"text","text":"JDK"}]},{"type":"text","text":"中的"},{"type":"codeinline","content":[{"type":"text","text":"AtomicStampedReference"}]},{"type":"text","text":"就幫助我們來做了這個事情,其對應的"},{"type":"codeinline","content":[{"type":"text","text":"CAS"}]},{"type":"text","text":"方法如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public boolean compareAndSet(V expectedReference,\n V newReference,\n int expectedStamp,\n int newStamp) {\n Pair current = pair;\n return\n expectedReference == current.reference &&\n expectedStamp == current.stamp &&\n ((newReference == current.reference &&\n newStamp == current.stamp) ||\n casPair(current, Pair.of(newReference, newStamp)));\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個方法有四個參數,前面兩個參數分別是需要比較和修改的值,後面兩個參數版本號的對比值和新值。比較的原理也很簡單,就是判斷當"},{"type":"codeinline","content":[{"type":"text","text":"expectedReference"}]},{"type":"text","text":"是否與當前的"},{"type":"codeinline","content":[{"type":"text","text":"reference"}]},{"type":"text","text":"是否一致,如果不一致就說明該數據必然被其他線程修改過,如果是一致的,那麼就在比較版本號是否一致,如果不一致,說明當前"},{"type":"codeinline","content":[{"type":"text","text":"reference"}]},{"type":"text","text":"中途被修改過,但是最後還是修改回來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據是被存儲在"},{"type":"codeinline","content":[{"type":"text","text":"Pair"}]},{"type":"text","text":"內,"},{"type":"codeinline","content":[{"type":"text","text":"Pair"}]},{"type":"text","text":"是"},{"type":"codeinline","content":[{"type":"text","text":"AtomicStampedReference"}]},{"type":"text","text":"的一個內部類,如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private static class Pair {\n final T reference;\n final int stamp;\n private Pair(T reference, int stamp) {\n this.reference = reference;\n this.stamp = stamp;\n }\n static Pair of(T reference, int stamp) {\n return new Pair(reference, stamp);\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"ABA"}]},{"type":"text","text":"問題解決辦法比較簡單好理解,感興趣的同學可以再更加深入的理解一下"},{"type":"codeinline","content":[{"type":"text","text":"AtomicStampedReference"}]},{"type":"text","text":"的源碼。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"四、CAS思想引發的思考"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"4.1 樂觀鎖和悲觀鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CAS的實現其實就是典型的樂觀鎖的基本思想的實現。樂觀鎖認爲,共享數據被其他線程修改的概率比較小,所以在讀取數據之前不會對數據進行加鎖,讀取完數據之後再進行計算,但是在寫入之前,會再去讀取一次共享數據,判斷該數據在此期間是否被其他線程修改,如果被其他線程修改了,那麼將重新讀取並重復之前的操作,如果沒有被修改,那麼就直接將數據寫回到主存中。CAS就是"},{"type":"codeinline","content":[{"type":"text","text":"Compare And Swap"}]},{"type":"text","text":",這是兩個操作,但是這兩個操作被合成了一個原子操作,從而保證了數據的線程安全。AtomicInteger就是典型的樂觀鎖的實現,這種思想廣泛存在於各種中間件中,比如MySQL、Redis等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相對於樂觀鎖,必然存在悲觀鎖,悲觀鎖認爲,共享數據總可能被其他線程併發修改,所以在讀取修改數據之前都會對數據進行加鎖處理,保證在此時間段內只能被一個線程訪問,等當前訪問線程訪問結束並釋放鎖後,那麼其他的線程纔有機會訪問共享數據。悲觀鎖思想是通過上鎖的方式保證了數據的安全性,但是損失了數據訪問的性能,悲觀鎖思想應用也很廣泛,比如synchronized、ReentrantLock、MySQL等都有悲觀鎖的實現。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"4.2 阻塞和自旋"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阻塞和自旋其實是線程兩種等待操作共享資源的方式,這兩種方式也是比較常用的方式,它們主要區別在於:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阻塞:線程進入阻塞狀態,其表現爲放棄CPU時間片,等待後期被操作系統線程調度器喚醒,然後在繼續執行線程中的邏輯。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"自旋:線程進入自旋狀態,其表現爲不放棄CPU時間片,利用CPU來進行“旋轉”,也就是不斷地進行重試。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這兩種方式都有各自的應用場景,在單核CPU中,自旋不太適合,因爲如果一旦自旋持續進行很久,那麼其他線程都將無法被執行,在這種場景下,更加適合阻塞。在多核CPU下,自旋就很適合,以爲其他CPU核心可以持續工作,當前CPU核心的線程中的任務在自旋,可以減少線程切換的次數,提高性能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Atomic類是基於Unsafe來實現的,底層的基本原理都是採用的自旋方式,在現代多核CPU中應用效果還是十分可觀的。當然阻塞和自旋並不是一對互斥的關係,它們可以很好地結合起來應用,比如自適應自旋鎖的應用,其基本原理是優先自旋,達到一定次數之後仍然沒有得到資源,那麼就進入到阻塞狀態。在JDK1.6之後,自旋鎖是默認開啓的,適用於鎖被佔用時間很多的情況,反之自旋的線程只會白白消耗處理器資源,反而帶來了性能上的浪費。所以自旋等待的時間必須有一定的限度,超過了限定的次數仍然沒有成功獲取鎖,就應當使用傳統的方式掛起線程了。自旋次數的默認值是10,用戶可以通過-XX:PreBlockSpin來更改。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文以AtomicInteger爲例分析了CAS的基本實現原理,其他的比如AtomicBoolean、AtomicLong的基本原理都是一樣的,感興趣的讀者可以對比閱讀器源碼進行分析。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"瞭解更多幹貨,歡迎關注我的微信公衆號:爪哇論劍(微信號:itlemon)"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f1/f1599143b5c054dd2744a7c92b9ff8a3.jpeg","alt":"微信公衆號-爪哇論劍-itlemon","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章