深入掌握底層源碼常見的 CAS 原子編程

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言","attrs":{}}]},{"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":"如果要聊原子類相關的話題, 可以先從基本的概念開始","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"1、原子類爲了解決什麼樣的問題?","attrs":{}}]},{"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":"答: 爲了解決併發場景下無鎖的方式保證單一變量的數據一致性","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"2、什麼情況下存在併發問題?","attrs":{}}]},{"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":"答: 多個線程同時讀寫同一個共享數據時存在多線程併發問題","attrs":{}}]},{"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":"解決併發安全問題的方式有很多種方式, ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"著名的就是 JDK 併發包 concurrent","attrs":{}},{"type":"text","text":", 爲了併發而存在的","attrs":{}}]},{"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":"閱讀文章收穫如下知識:","attrs":{}}]},{"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":" - 無鎖編程產出背景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - CAS 如何實現的無鎖編程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - CAS 使用中的 “ABA” 痛點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 如何解決 “ABA” 問題","attrs":{}}]},{"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":"文章首發自公衆號 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"【源碼興趣圈】","attrs":{}},{"type":"text","text":",關注公衆號第一時間獲取後端硬核知識,已發佈 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"【44】篇","attrs":{}},{"type":"text","text":" 原創技術博文","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"非原子計算","attrs":{}}]},{"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++ 操作, 雖然是一行, 但是執行時候是分爲三步的","attrs":{}}]},{"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":"從主存獲取變量 i","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"變量i值+1","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新增後變量i值寫回主存","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"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":"寫個小程序來更好的理解, 不論我們怎麼運行下方程序, 99% 以上概率不會到達 100000000","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"static int NUM = 0;\npublic static void main(String[] args) throws InterruptedException {\n for (int i = 0; i < 10000; i++) {\n new Thread(() -> {\n for (int j = 0; j < 10000; j++) {\n NUM++;\n }\n }).start();\n }\n Thread.sleep(2000);\n System.out.println(NUM);\n /**\n * 99149419\n */\n}","attrs":{}}]},{"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":"可以使用 JDK 自帶的 synchronized, 通過 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"互斥鎖的方式","attrs":{}},{"type":"text","text":" 同步執行 NUM++ 這個代碼塊","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"static int NUM = 0;\npublic static void main(String[] args) throws InterruptedException {\n for (int i = 0; i < 10000; i++) {\n new Thread(() -> {\n for (int j = 0; j < 10000; j++) {\n synchronized (Object.class) {\n NUM++;\n }\n }\n }).start();\n }\n Thread.sleep(2000);\n System.out.println(NUM);\n /**\n * 100000000\n */\n}","attrs":{}}]},{"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 在這說鎖的問題, 咱不能掛着羊皮賣狗肉是吧","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/87/87f97e887abc587d82d342812d314941.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":"如果看過 JDK 源碼 JUC 包下的類庫源代碼, 關於 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Atomic 開頭的類庫","attrs":{}},{"type":"text","text":" 應該不會陌生","attrs":{}}]},{"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":"Atomic 英 [əˈtɒmɪk] 美 [əˈtɑːmɪk] 翻譯 : 原子的","attrs":{}}]}],"attrs":{}},{"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":"如果不使用鎖來解決上面的非原子自增問題, 可以這麼來寫","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"static AtomicInteger NUM = new AtomicInteger();\npublic static void main(String[] args) throws InterruptedException {\n for (int i = 0; i < 10000; i++) {\n new Thread(() -> {\n for (int j = 0; j < 10000; j++) {\n \t// 🚩 重點哦, 自增並獲取新值\n NUM.incrementAndGet();\n }\n }).start();\n }\n Thread.sleep(2000);\n System.out.println(NUM);\n /**\n * 100000000\n */\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"AtomicInteger 簡介","attrs":{}}]},{"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":"看到一個技術點或者框架之類比較新穎的知識, 一般會思考兩個問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"它是什麼","attrs":{}}]},{"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 是 JDK 併發包下提供的 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"操作 Integer 類型原子類","attrs":{}},{"type":"text","text":", 通過調用底層 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Unsafe 的 CAS 相關方法實現原子操作","attrs":{}}]},{"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":"基於樂觀鎖的思想實現的一種無鎖化原子操作, ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"保障了多線程情況下單一變量的線程安全問題","attrs":{}}]},{"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":"關於 CAS 的概念下面會詳細說明","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"有什麼優點","attrs":{}}]},{"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":"AtmoicInteger 使用硬件級別的指令 CAS 來更新計數器的值, 機器直接支持的指令, ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這樣可以避免加鎖","attrs":{}}]},{"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 在併發比較嚴重情況下, 會將鎖 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"升級到重量級鎖","attrs":{}}]},{"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":"喚醒與阻塞線程時會有一個 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"用戶態到內核態的一個轉變","attrs":{}},{"type":"text","text":", 而轉換狀態是需要消耗很多時間的","attrs":{}}]},{"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":"寫過程序進行測試, 儘管 synchronized 經過升級後, 性能有了大幅度提升, 但在 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一般併發場景下, CAS 無鎖算法性能更高一些","attrs":{}}]}],"attrs":{}},{"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":"當然不可能會有盡善盡美的存在, 關於 CAS 無鎖算法會在下面說明劣勢所在","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結構分析","attrs":{}}]},{"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 有兩個構造方法, 分別是一個無參構造及有參構造","attrs":{}}]},{"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":"無參構造的 value 就是 int 的默認值 0","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有參構造會將 value 賦值","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public AtomicInteger() { }\n\npublic AtomicInteger(int initialValue) {\n value = initialValue;\n}","attrs":{}}]},{"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 有三個重要的變量, 分別是:","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"Unsafe:","attrs":{}},{"type":"text","text":" 可以理解它對於 Java 而言, 是一個 \"BUG\" 的存在, 在 AtomicInteger 裏的最大作用就是直接操作內存進行值替換","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"value:","attrs":{}},{"type":"text","text":" 使用 int 類型存儲 AtomicInteger 計算的值, 通過 volatile 進行修飾, ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"提供了內存可見性及防止指令重排序","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"valueOffset:","attrs":{}},{"type":"text","text":" value 的內存偏移量","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 獲取Unsafe實例\nprivate static final Unsafe unsafe = Unsafe.getUnsafe();\nprivate static final long valueOffset;\n\n// 靜態代碼塊,在類加載時運行\nstatic {\n try {\n \t// 獲取 value 的內存偏移量\n valueOffset = unsafe.objectFieldOffset\n (AtomicInteger.class.getDeclaredField(\"value\"));\n } catch (Exception ex) {\n throw new Error(ex);\n }\n}\n\nprivate volatile int value;","attrs":{}}]},{"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":"這裏羅列出一些常用API, 核心實現思路都是一致的, 會着重講其中一個","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 獲取當前 value 值\npublic final int get();\n\n// 取當前的值, 並設置新的值\npublic final int getAndSet(int newValue);\n\n// 獲取當前的值, 並加上預期的值\npublic final int getAndAdd(int delta);\n\n// 獲取當前值, 並進行自增1\npublic final int getAndIncrement();\n \n// 獲取當前值, 並進行自減1\npublic final int getAndDecrement();","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"獲取當前值並自增 #getAndIncrement()","attrs":{}}]},{"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":"看一下具體在源碼中是如何操作的","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public final int getAndIncrement() {\n \treturn unsafe.getAndAddInt(this, valueOffset, 1);\n}\n\n/**\n * unsafe.getAndAddInt\n *\n * @param var1 AtomicInteger 對象\n * @param var2 value 內存偏移量\n * @param var4 增加的值, 比如在原有值上 + 1\n * @return\n */\npublic final int getAndAddInt(Object var1, long var2, int var4) {\n int var5;\n do {\n // 內存中 value 最新值\n var5 = this.getIntVolatile(var1, var2);\n } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));\n\n return var5;\n}","attrs":{}}]},{"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 體現無鎖算法的地方, 先來說下這段代碼的執行步驟","attrs":{}}]},{"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":"1、根據 AtomicInteger 對象 以及 value 內存偏移量獲取對應 value 最新值","attrs":{}}]},{"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":"2、通過 compareAndSwapInt(...) 將內存中的值(var5)更改爲期望的值 (var5+var4), 不存在多線程競爭成功修改返回 True 結束循環, Flase 繼續執行循環","attrs":{}}]},{"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":"精髓的地方就在於 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"compareAndSwapInt(...)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * 比較 var1 的 var2 內存偏移量處的值是否和 var4 相等, 相等則更新爲 var5\n *\n * @param var1 AtomicInteger 對象\n * @param var2 value 內存偏移量\n * @param var4 value 原本的值\n * @param var5 期望將 value 設置的值\n * @return\n */\npublic final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);","attrs":{}}]},{"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":"由於是 native 關鍵字修飾, 我們無法查看其源碼, 說明一下方法思路","attrs":{}}]},{"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":"1、通過 var1(AtomicInteger) 獲取到 var2 (內存偏移量) 的 value 值","attrs":{}}]},{"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":"2、將 value(內存中值) 與 var4(線程內獲取的value值) 進行比較","attrs":{}}]},{"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":"3、如果相等將 var5(期望值) 設置爲內存中新的 value 並返回 True","attrs":{}}]},{"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":"4、不相等返回 False 繼續嘗試執行循環","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"圖文分析 CAS","attrs":{}}]},{"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","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"Unsafe#getAndAddInt()","attrs":{}},{"type":"text","text":" 以此方法爲例","attrs":{}}]},{"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":"1、這個圖片相當於 AtomicInteger 對象對應的 valueOffset 位置","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/27/27e1023ac7021b621f8f2e2df68e4dfa.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":"2、線程一執行 var5 = this.getIntVolatile(var1, var2)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b0/b056a64fdcaee8a583887c223ed58124.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":"3、線程二執行 var5 = this.getIntVolatile(var1, var2)","attrs":{}}]},{"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":"此時在線程一、二的工作內存中 var5 都是 0","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c6/c63bbd511b5ff20ddfed1182f69fd0f5.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":"4、線程一欲修改內存中的 value 爲1, 通過比對 var4 與內存中的值相等, 內存值成功被設置成 1","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/67/676a2d6ed5c96a1d45e2a1a662ed81d9.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":"5、線程二也是要修改內存中對應的 value 值, 發現已經不相等了, 返回 False 繼續嘗試修改","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d5/d5a3b09b2372a7a8300735ff018e4152.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":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"不足之處","attrs":{}}]},{"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 雖然能夠實現無鎖編程, 在一般情況下對性能做出了提升, 但是並不是沒有侷限性或缺點","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"1、CPU 自旋開銷較大","attrs":{}}]},{"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 如果長時間不成功, 會給 CPU 帶來非常大的執行開銷","attrs":{}}]},{"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":"如果是實現高併發下的計數, 可以使用 LongAdder, 設計的高併發思想真的強!","attrs":{}}]}],"attrs":{}},{"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","marks":[{"type":"strong","attrs":{}}],"text":"2、著名的 \"ABA\" 問題","attrs":{}}]},{"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 需要在操作值的時候檢查下值有沒有發生變化, 如果沒有發生變化則更新","attrs":{}}]},{"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":"但是如果一個值原來是A, 變成了B, 又變成了A, 那麼使用 CAS 進行檢查時會發現它的值沒有發生變化, 但是實際上卻變化了","attrs":{}}]},{"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":"如果感興趣的小夥伴可以去看下 JUCA 原子包下的 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"AtomicStampedReference","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ABA 問題背景","attrs":{}}]},{"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 存在的一個問題, 也是大部分 Atomic 相關類存在的, 就是 ABA 問題","attrs":{}}]},{"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 的 value 爲 0, 在準備做修改之前","attrs":{}}]},{"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 的 value 做了兩次操作, 一次是將值修改爲 1, 然後又將值修改爲原來的 0","attrs":{}}]},{"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 操作, 發現內存中的值依舊是 0, OK, 更新成功, 結合下圖瞭解","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ea/ea5f1986163f80f2acbfc9760c14765e.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","marks":[{"type":"strong","attrs":{}}],"text":"1、爲什麼線程二能夠在線程一獲取最新值後進行操作?","attrs":{}}]},{"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":"我們以 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"AtomicInteger#getAndIncrement","attrs":{}},{"type":"text","text":" 說明","attrs":{}}]},{"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// 標記1\n var5 = this.getIntVolatile(var1, var2);\n } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));\n\n return var5;\n}","attrs":{}}]},{"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 執行是 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"搶佔式的, 時間片分配並不固定","attrs":{}}]},{"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":"可能在線程一讀取完標記1處之後, 時間片就分配執行了線程二, 此時線程一等待時間片的分配","attrs":{}}]},{"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":"等線程二做完兩步操作之後, 時間片分配到線程一, 這時纔會繼續執行","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"2、ABA 會引發什麼樣的問題?","attrs":{}}]},{"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 的行爲是如何發生的, 列舉網上的一個小例子, 大致意思如下:","attrs":{}}]},{"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":"1、小明銀行卡賬戶餘額1萬元, 去取款機取錢5千元, 正常應該發起一次請求, 因爲網絡或機器故障發起了兩次請求","attrs":{}}]},{"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":"各位大哥大姐不要擡槓哈, 不要說什麼後端接口冪等, 取款機防重複提交之類的, ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"例子是爲了說明問題","attrs":{}},{"type":"text","text":", 感謝 😄","attrs":{}}]}],"attrs":{}},{"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":"2、線程一「由取款機發起」: 獲取當前銀行卡餘額值1萬元, 期望值5千元","attrs":{}}]},{"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":"3、線程二「由取款機發起」: 獲取當前銀行卡餘額值1萬元, 期望值5千元","attrs":{}}]},{"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":"4、線程一成功了, 銀行卡內餘額剩餘五千, 線程二未分配時間片, 獲取到餘額後被阻塞","attrs":{}}]},{"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":"5、此時線程三「支付寶轉入銀行卡」: 獲取當前銀行卡餘額5千元, 期望值1萬元, 修改成功","attrs":{}}]},{"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":"6、線程二獲得時間片, 發現卡內餘額是1萬, 成功將卡內餘額減少至5千","attrs":{}}]},{"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":"7、卡內餘額原本應是1萬-5千+5千=1萬, 最終因爲 ABA 問題導致 1萬-5千+5千-5千=5千","attrs":{}}]},{"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":"當然正式業務中, 可能不會存在此類問題, 不過平常自己業務中使用到了原子類, 還是會埋下潛在隱患","attrs":{}}]},{"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 問題是如何復現的","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static void main(String[] args) throws InterruptedException {\n AtomicInteger atomicInteger = new AtomicInteger(100);\n new Thread(() -> {\n atomicInteger.compareAndSet(100, 101);\n atomicInteger.compareAndSet(101, 100);\n }).start();\n\n Thread.sleep(1000);\n\n new Thread(() -> {\n boolean result = atomicInteger.compareAndSet(100, 101);\n System.out.println(String.format(\" >>> 修改 atomicInteger :: %s \", result));\n }).start();\n /**\n * >>> 修改 atomicInteger :: true \n */\n}","attrs":{}}]},{"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":"1、創建一個初始值爲100的 AtomicInteger, 線程一將100-> 101, 再從101->100","attrs":{}}]},{"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":"2、休眠1000ms, 防止時間片分配線程二提前執行","attrs":{}}]},{"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":"3、線程二從100->101, 修改成功","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"AtomicStampedReference","attrs":{}}]},{"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 的思路吧, 也就是 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"AtomicStampedReference","attrs":{}},{"type":"text","text":" 的原理","attrs":{}}]},{"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":"內部維護了一個 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Pair 對象","attrs":{}},{"type":"text","text":", 存儲了 value 值和一個版本號, 每次更新除了 value 值還會更新版本號","attrs":{}}]},{"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 \t// 存儲值, 相當於上文的值100\n final T reference;\n \t// 類似於版本號的概念\n final int stamp;\n\n private Pair(T reference, int stamp) {\n this.reference = reference;\n this.stamp = stamp;\n }\n\t\t// 創建一個新的Pair對象, 每次值變化時都會創建一個新的對象\n static AtomicStampedReference.Pair of(T reference, int stamp) {\n return new AtomicStampedReference.Pair(reference, stamp);\n }\n}","attrs":{}}]},{"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":"我們還是先通過一個小程序來了解下 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"AtomicStampedReference","attrs":{}},{"type":"text","text":" 運行機制","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@SneakyThrows\npublic static void main(String[] args) {\n AtomicStampedReference stampedReference = new AtomicStampedReference(100, 0);\n\n new Thread(() -> {\n Thread.sleep(50);\n stampedReference.compareAndSet(100,\n 101,\n stampedReference.getStamp(),\n stampedReference.getStamp() + 1);\n\n stampedReference.compareAndSet(101,\n 100,\n stampedReference.getStamp(),\n stampedReference.getStamp() + 1);\n }).start();\n\n\n new Thread(() -> {\n int stamp = stampedReference.getStamp();\n Thread.sleep(500);\n boolean result = stampedReference.compareAndSet(100, 101, stamp, stamp + 1);\n System.out.println(String.format(\" >>> 修改 stampedReference :: %s \", result));\n }).start();\n \t/**\n \t\t\t* >>> 修改 atomicInteger :: false \n \t\t\t*/\n}","attrs":{}}]},{"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":"1、創建 AtomicStampedReference, 並設置初始值100, 以及版本號0","attrs":{}}]},{"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":"2、線程一睡眠50ms, 然後對value做出操作100->101, 101->100, 版本+1","attrs":{}}]},{"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":"爲什麼要睡眠50ms? 爲了模擬多線程併發搶佔, 讓線程二先獲取到版本號","attrs":{}}]}],"attrs":{}},{"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":"3、線程二睡眠500ms, 等待線程一執行完畢, 開始將100->101, 版本號+1","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"不出意外,\u0010 線程二一定會修改失敗","attrs":{}},{"type":"text","text":", 雖然值相對應, 但是預期的版本號與 Pair 中的已不一致","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"compareAndSet","attrs":{}}]},{"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":"來看下它的 compareAndSet(...) 是如何做的","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * 比較並設置\n *\n * @param expectedReference 預期值\n * @param newReference 期望值\n * @param expectedStamp 預期版本號\n * @param newStamp 期望版本號\n * @return\t\t\t\t\t\t\t\t\t是否成功\n */\npublic boolean compareAndSet(V expectedReference,\n V newReference,\n int expectedStamp,\n int newStamp) {\n // 獲取當前 pair 引用\n AtomicStampedReference.Pair current = pair;\n // 預期值對比 pair value\n return expectedReference == current.reference &&\n \t\t\t// 預期版本號對比 pair stamp\n expectedStamp == current.stamp &&\n \t\t\t// 期望值對比 pair value\n ((newReference == current.reference &&\n \t\t\t// 期望版本號對比 pair stamp\n newStamp == current.stamp) ||\n \t\t\t\t// casPair\n casPair(current, Pair.of(newReference, newStamp)));\n}","attrs":{}}]},{"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":"如果 compareAndSet(...) 爲 True, 必須滿足上述條件表達式中3個條件","attrs":{}}]},{"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":"1、預期值等於 pair value","attrs":{}}]},{"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":"2、預期版本號等於 pair stamp","attrs":{}}]},{"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":"3、期望值等於 pair value 並且期望版本號等於 pair stamp","attrs":{}}]},{"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":"這是 value 和 版本號未發生變化時的場景","attrs":{}}]}],"attrs":{}},{"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":"4、當第一個條件和第二個條件滿足, 但是不滿足第三個條件, 證明值和版本號發生了變化, 創建 Pair 進行 CAS 比較替換","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"上述條件必須滿足1、2、3或者滿足1、2、4均可返回 True","attrs":{}}]},{"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":"將當前 Pair 原子引用切換爲新 Pair, 與 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"AtomicReference 思路一致","attrs":{}},{"type":"text","text":", 將對象引用進行原子轉換","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private boolean casPair(AtomicStampedReference.Pair cmp, AtomicStampedReference.Pair val) {\n return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"使用 Integer value 的坑","attrs":{}}]},{"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":"小夥伴應該都知道, 如果你寫出下面的代碼, 是不會在堆內創建對象的","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Integer i1 = 100;\nInteger i2 = 101;","attrs":{}}]},{"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":"因爲 JVM 會在常量池內存儲兩個對象, 而超過 -128~127 的數值則會在堆中創建對象","attrs":{}}]},{"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":"我們 Pair 對象的 reference 是範型, 傳遞的 int 類型的數值會被轉型, 變爲 Integer","attrs":{}}]},{"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":"使用 Integer 類型並且值不在 -128~127 範圍內, 會出現數據比對出錯的情況, 大家看一下 compareAndSet(...) 就明白了","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"AtomicMarkableReference","attrs":{}}]},{"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":"大家看到上面的版本號兩次操作必須保持不一致, 得自增或、自減或者別的操作, 相對而言也比較繁瑣","attrs":{}}]},{"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":"Doug Lea 大師同時也爲我們提供了一個省事的類庫 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"AtomicMarkableReference","attrs":{}}]},{"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":"API 接口以及實現思路與上述的基本一致, 只不過把版本號 int 類型替換爲了 boolean 類型, 其它無區別","attrs":{}}]},{"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":"不過, ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"AtomicMarkableReference 並不能完全解決 ABA 問題, 只是能夠小概率預防","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結言","attrs":{}}]},{"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":"由於作者水平有限, 歡迎大家能夠反饋指正文章中錯誤不正確的地方, 感謝 🙏","attrs":{}}]},{"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":"小夥伴的喜歡就是對我最大的支持, 如果讀了文章有所收穫, 希望能夠 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"點贊、評論、關注三連!","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"推薦閱讀:","attrs":{}}]},{"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":"link","attrs":{"href":"https://juejin.cn/post/6891120931716988942","title":""},"content":[{"type":"text","text":"【強烈推薦】謹慎使用 JDK 8 新特性並行流 ParallelStream","attrs":{}}]}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://juejin.cn/post/6898869137724932110","title":""},"content":[{"type":"text","text":"【強烈推薦】一文快速掌握 Redisson 如何實現分佈式鎖原理","attrs":{}}]}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://juejin.cn/post/6896278031317663751","title":""},"content":[{"type":"text","text":"【強烈推薦】聊一聊 ReentrantLock 和 AQS 那點事","attrs":{}}]}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/sk5-MAtm-fmmOCMzTuznbg","title":""},"content":[{"type":"text","text":"【大廠面試真題】JDK 線程池中如何不超最大線程數快速消費任務","attrs":{}}]}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://juejin.cn/post/6897017564271280142","title":""},"content":[{"type":"text","text":"【大廠面試真題】JDK 線程池如何保證核心線程不被銷燬","attrs":{}}]}]}],"attrs":{}}],"attrs":{}},{"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","marks":[{"type":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"作者麻花,座標帝都 Java 後端研發,勵志成爲架構師的一枚處女座程序員,專注高併發、框架底層源碼、分佈式等知識分享","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章