高併發系列——CAS操作及CPU底層操作解析

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CAS(Compare-and-Swap),即比較並替換,是一種實現併發算法時常用到的技術,Java併發包中的很多類都使用了CAS技術。CAS也是現在面試經常問的問題,本文將深入的介紹CAS的原理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"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":"一個方法實現i++時,如下面的getAndIncrement1()方法,多線程調用時,會出現併發安全問題。這時,可以通過synchronized關鍵字顯式加鎖保證併發安全。"}]},{"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併發包中,提供了很多的Atomic原子類,也可以保證線程安全,比如getAndIncrement2()中的AtomicInteger.getAndIncrement()方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"package com.wuxiaolong.concurrent;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Description:\n *\n * @author 諸葛小猿\n * @date 2020-09-14\n */\npublic class Test1 {\n\n public static int i = 0;\n\n /**\n * 加鎖保證多線程調用的併發安全\n * @return\n */\n public synchronized static int getAndIncrement1(){\n i++;\n return i;\n }\n\n /**\n * AtomicInteger是線程安全的 \n * @return\n */\n public static int getAndIncrement2(){\n AtomicInteger ai = new AtomicInteger();\n int in = ai.getAndIncrement();\n return in;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二、什麼是CAS"}]},{"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:compare and swap或compare and exchange,比較和交互。作用是保證在沒有鎖的情況下,多個線程對一個值的更新是線程安全的。"}]},{"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(V,E,N),V表示要更新的變量,E表示預期值,N表示新值,僅當V值等於E值時,纔會將V值設置爲N值,如果V值和E值不同,說明已經有其他線程做了更新,則當前線程什麼都不做。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/00db8643b23c06a8f8e277a2628ab503.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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實現i++這個操作,可以有如下幾步:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.讀取當前i的值(比如i=0),記爲E"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.計算i++的值爲1,記爲V"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.再次讀取i的值,記爲N。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.如果E==N,則將i的值更新爲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的過程很像樂觀鎖,樂觀鎖認爲發生線程安全問題的概率比較小,所以不用直接加鎖,只是更新數據時比較一下原來的數據有沒有發生變化。"}]},{"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":"上面第四步中,如果E==N,並不能說明i的值沒有改變過,可能一個線程執行第四步的時候,另一個線程將i改變後又變回來了,對於第一個線程來說,並不知道這個中間過程的存在。這個現象就是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":"ABA問題如何解決?其實也很簡單,給i加一個版本號字段,每次i有變化都對版本號加1,每次更新i的時候除了比較E值,還比較版本號是否一致。這樣就解決了ABA問題。在實際開發過程中,如果ABA問題對業務沒有影響,就不用考慮這個問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三、CAS在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":"CAS的底層實現、synchronized的底層實現、volatile的底層實現都是一樣的。我們以上面提到的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是線程安全的,通常說AtomicInteger是無鎖或自旋鎖。這就是CAS在JDK中的應用。"}]},{"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.getAndIncrement()方法的源碼:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" /**\n * Atomically increments by one the current value.\n *\n * @return the previous value\n */\n public final int getAndIncrement() {\n return unsafe.getAndAddInt(this, valueOffset, 1);\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.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 var5 = this.getIntVolatile(var1, var2);\n } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));\n\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":"Unsafe.compareAndSwapInt()源碼:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);"}]},{"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":"native"}]},{"type":"text","text":"修飾的,在JDK中就看不到源碼實現了。由於java代碼是在JVM中執行的,Oracle的JVM是Hotspot, 如果要看native方法的實現,可以找Hotspot的源碼,這個源碼是C和C++寫的。Unsafe.java這個類的源碼對應Hotspot源碼中unsafe.cpp,這個是C++寫的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"四、CAS的底層實現"}]},{"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的底層實現,就必須瞭解Hotspot的源碼。可以在查看OpenJdk的代碼,"},{"type":"link","attrs":{"href":"http://hg.openjdk.java.net/","title":""},"content":[{"type":"text","text":"這裏"}]},{"type":"text","text":"就可以找到各種版本的源碼。我們以jdk8u爲中的unsafe.cpp爲例,繼續分析"},{"type":"codeinline","content":[{"type":"text","text":"compareAndSwapInt"}]},{"type":"text","text":"方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 地址:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/a0eb08e2db5a/src/share/vm/prims/unsafe.cpp\n\nUNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))\n UnsafeWrapper(\"Unsafe_CompareAndSwapInt\");\n oop p = JNIHandles::resolve(obj);\n jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);\n return (jint)(Atomic::cmpxchg(x, addr, e)) == e;\nUNSAFE_END"}]},{"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::cmpxchg方法,繼續分析找到這個方法:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 地址:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/a0eb08e2db5a/src/share/vm/runtime/atomic.cpp\n\njbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {\n assert(sizeof(jbyte) == 1, \"assumption.\");\n uintptr_t dest_addr = (uintptr_t)dest;\n uintptr_t offset = dest_addr % sizeof(jint);\n volatile jint* dest_int = (volatile jint*)(dest_addr - offset);\n jint cur = *dest_int;\n jbyte* cur_as_bytes = (jbyte*)(&cur);\n jint new_val = cur;\n jbyte* new_val_as_bytes = (jbyte*)(&new_val);\n new_val_as_bytes[offset] = exchange_value;\n while (cur_as_bytes[offset] == compare_value) {\n //關鍵方法\n jint res = cmpxchg(new_val, dest_int, cur); \n if (res == cur) break;\n cur = res;\n new_val = cur;\n new_val_as_bytes[offset] = exchange_value;\n }\n return cur_as_bytes[offset];\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":"各種系統下各種cpu架構,都有相關的實現方法,具體的文件名稱如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 地址:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/a0eb08e2db5a/src/share/vm/runtime/atomic.inline.hpp\n\n#ifndef SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP\n#define SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP\n\n#include \"runtime/atomic.hpp\"\n\n// Linux\n#ifdef TARGET_OS_ARCH_linux_x86\n# include \"atomic_linux_x86.inline.hpp\"\n#endif\n#ifdef TARGET_OS_ARCH_linux_sparc\n# include \"atomic_linux_sparc.inline.hpp\"\n#endif\n#ifdef TARGET_OS_ARCH_linux_zero\n# include \"atomic_linux_zero.inline.hpp\"\n#endif\n#ifdef TARGET_OS_ARCH_linux_arm\n# include \"atomic_linux_arm.inline.hpp\"\n#endif\n#ifdef TARGET_OS_ARCH_linux_ppc\n# include \"atomic_linux_ppc.inline.hpp\"\n#endif\n\n// Solaris\n#ifdef TARGET_OS_ARCH_solaris_x86\n# include \"atomic_solaris_x86.inline.hpp\"\n#endif\n#ifdef TARGET_OS_ARCH_solaris_sparc\n# include \"atomic_solaris_sparc.inline.hpp\"\n#endif\n\n// Windows\n#ifdef TARGET_OS_ARCH_windows_x86\n# include \"atomic_windows_x86.inline.hpp\"\n#endif\n\n// AIX\n#ifdef TARGET_OS_ARCH_aix_ppc\n# include \"atomic_aix_ppc.inline.hpp\"\n#endif\n\n// BSD\n#ifdef TARGET_OS_ARCH_bsd_x86\n# include \"atomic_bsd_x86.inline.hpp\"\n#endif\n#ifdef TARGET_OS_ARCH_bsd_zero\n# include \"atomic_bsd_zero.inline.hpp\"\n#endif\n\n#endif // SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP\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":"在src/os"},{"type":"text","marks":[{"type":"italic"}],"text":"cpu/目錄下面有各種系統下各種cpu架構的代碼實現,其中src/os"},{"type":"text","text":"cpu/linux_x86/vm下是基於linux下x86架構的代碼,cmpxchg方法的最終實現:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 地址:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/a0eb08e2db5a/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp\n\ninline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {\n int mp = os::is_MP();\n __asm__ volatile (LOCK_IF_MP(%4) \"cmpxchgl %1,(%3)\"\n : \"=a\" (exchange_value)\n : \"r\" (exchange_value), \"a\" (compare_value), \"r\" (dest), \"r\" (mp)\n : \"cc\", \"memory\");\n return exchange_value;\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":"__asm__ volatile (LOCK_IF_MP(%4) \"cmpxchgl %1,(%3)\""}]},{"type":"text","text":"一段代碼是核心,asm指彙編語言,是機器語言,直接和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":"LOCK"},{"type":"text","marks":[{"type":"italic"}],"text":"IF"},{"type":"text","text":"MP指“如果是多個CPU就加鎖”,MP指Multi-Processors。程序會根據當前處理器的數量來決定是否爲cmpxchg指令添加lock前綴。如果程序是在多處理器上運行,就爲cmpxchg指令加上lock前綴(lock cmpxchg)。反之,如果程序是在單處理器上運行,就省略lock前綴(單個處理器自身會維護單處理器內的順序一致性,不需要lock前綴提供的內存屏障效果)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 地址:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/a0eb08e2db5a/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp\n\n// Adding a lock prefix to an instruction on MP machine\n#define LOCK_IF_MP(mp) \"cmp $0, \" #mp \"; je 1f; lock; 1: \""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"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的本質就是:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"lock cmpxchg"}]},{"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":"cmpxchg"}]},{"type":"text","text":"這條cpu指令本身並不是原子性的,還是依賴了前面的"},{"type":"codeinline","content":[{"type":"text","text":"lock"}]},{"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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關注公衆號,輸入“"},{"type":"text","marks":[{"type":"strong"}],"text":"java-summary"},{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/77/77e2a53cabfaaac7d98ee0a860144e29.gif","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"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":"text","marks":[{"type":"strong"}],"text":"傳播知識,共享價值"},{"type":"text","text":"】,感謝小夥伴們的關注和支持,我是【"},{"type":"text","marks":[{"type":"strong"}],"text":"諸葛小猿"},{"type":"text","text":"】,一個彷徨中奮鬥的互聯網民工。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6a/6acafea3f4c9b96373b3f566ec7078e2.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章