參考:https://mp.weixin.qq.com/s/JFLqL1GGW0fFXoGbHxmowAhtml
簡單講一下這個類。Java 沒法直接訪問底層操做系統,而是經過本地(native)方法來訪問。不過儘管如此,JVM 仍是開了一個後門,JDK 中有一個類 Unsafe,它提供了硬件級別的原子操做。java
這個類儘管裏面的方法都是 public 的,可是並無辦法使用它們,JDK API 文檔也沒有提供任何關於這個類的方法的解釋。總而言之,對於 Unsafe 類的使用都是受限制的,只有授信的代碼才能得到該類的實例,固然 JDK 庫裏面的類是能夠隨意使用的。算法
從第一行的描述能夠了解到 Unsafe 提供了硬件級別的操做,好比說獲取某個屬性在內存中的位置,好比說修改對象的字段值,即便它是私有的。不過 Java 自己就是爲了屏蔽底層的差別,對於通常的開發而言也不多會有這樣的需求。數組
舉兩個例子,比方說:安全
這個方法能夠用來獲取給定的 paramField 的內存地址偏移量,這個值對於給定的 field 是惟一的且是固定不變的。再好比說:併發
前一個方法是用來獲取數組第一個元素的偏移地址,後一個方法是用來獲取數組的轉換因子即數組中元素的增量地址的。最後看三個方法:atom
分別用來分配內存,擴充內存和釋放內存的。操作系統
固然這須要有必定的 C/C++ 基礎,對內存分配有必定的瞭解,這也是爲何我一直認爲 C/C++ 開發者轉行作 Java 會有優點的緣由。線程
CAS,Compare and Swap 即比較並交換,設計併發算法時經常使用到的一種技術,java.util.concurrent 包全完創建在 CAS 之上,沒有 CAS 也就沒有此包,可見 CAS 的重要性。設計
當前的處理器基本都支持 CAS,只不過不一樣的廠家的實現不同罷了。CAS 有三個操做數:內存值 V、舊的預期值 A、要修改的值 B,當且僅當預期值 A 和內存值 V 相同時,將內存值修改成 B 並返回 true,不然什麼都不作並返回 false。
CAS 也是經過 Unsafe 實現的,看下 Unsafe 下的三個方法:
就拿中間這個比較並交換 Int 值爲例好了,若是咱們不用 CAS,那麼代碼大體是這樣的:
固然這段代碼在併發下是確定有問題的,有可能線程 1 運行到了第 5 行正準備運行第 7 行,線程 2 運行了,把 i 修改成 10,線程切換回去,線程1因爲先前已經知足第 5 行的 if 了,因此致使兩個線程同時修改了變量 i。
解決辦法也很簡單,給 compareAndSwapInt 方法加鎖同步就好了,這樣,compareAndSwapInt 方法就變成了一個原子操做。CAS 也是同樣的道理,比較、交換也是一組原子操做,不會被外部打斷,先根據 paramLong/paramLong1 獲取到內存當中當前的內存值 V,在將內存值 V 和原值 A 做比較,要是相等就修改成要修改的值 B,因爲 CAS 都是硬件級別的操做,所以效率會高一些。
java.util.concurrent.atomic 包下的原子操做類都是基於 CAS 實現的,下面拿 AtomicInteger 分析一下,首先是 AtomicInteger 類變量的定義:
關於這段代碼中出現的幾個成員屬性:
一、Unsafe是 CAS 的核心類,前面已經講過了。
二、valueOffset 表示的是變量值在內存中的偏移地址,由於 Unsafe 就是根據內存偏移地址獲取數據的原值的。
三、value 是用 volatile 修飾的,這是很是關鍵的。
下面找一個方法 getAndIncrement 來研究一下 AtomicInteger 是如何實現的,好比咱們經常使用的 addAndGet 方法:
這段代碼如何在不加鎖的狀況下經過 CAS 實現線程安全,咱們不妨考慮一下方法的執行:
一、AtomicInteger 裏面的 value 原始值爲 3,即主內存中 AtomicInteger 的 value 爲 3,根據 Java 內存模型,線程 1 和線程 2 各自持有一份 value 的副本,值爲 3。
二、線程 1 運行到第三行獲取到當前的 value 爲 3,線程切換。
三、線程 2 開始運行,獲取到 value 爲 3,利用 CAS 對比內存中的值也爲 3,比較成功,修改內存,此時內存中的 value 改變比方說是 4,線程切換。
四、線程 1 恢復運行,利用 CAS 比較發現本身的 value 爲 3,內存中的 value 爲 4,獲得一個重要的結論 –> 此時 value 正在被另一個線程修改,因此我不能去修改它。
五、線程 1 的 compareAndSet 失敗,循環判斷,由於 value 是 volatile 修飾的,因此它具有可見性的特性,線程 2 對於 value 的改變能被線程 1 看到,只要線程 1 發現當前獲取的 value 是 4,內存中的 value 也是 4,說明線程 2 對於 value 的修改已經完畢而且線程 1 能夠嘗試去修改它。
六、最後說一點,好比說此時線程 3 也準備修改 value 了,不要緊,由於比較-交換是一個原子操做不可被打斷,線程 3 修改了 value,線程 1 進行 compareAndSet 的時候必然返回的 false,這樣線程 1 會繼續循環去獲取最新的 value 並進行 compareAndSet,直至獲取的 value 和內存中的 value 一致爲止。
整個過程當中,利用 CAS 機制保證了對於 value 的修改的線程安全性。
CAS 看起來很美,但這種操做顯然沒法涵蓋併發下的全部場景,而且 CAS 從語義上來講也不是完美的,存在這樣一個邏輯漏洞:若是一個變量 V 初次讀取的時候是 A 值,而且在準備賦值的時候檢查到它仍然是 A 值,那咱們就能說明它的值沒有被其餘線程修改過了嗎?若是在這段期間它的值曾經被改爲了 B,而後又改回 A,那 CAS 操做就會誤認爲它歷來沒有被修改過。這個漏洞稱爲 CAS 操做的 」ABA」 問題。java.util.concurrent 包爲了解決這個問題,提供了一個帶有標記的原子引用類 」AtomicStampedReference」,它能夠經過控制變量值的版原本保證 CAS 的正確性。不過目前來講這個類比較」雞肋」,大部分狀況下 ABA 問題並不會影響程序併發的正確性,若是須要解決 ABA 問題,使用傳統的互斥同步可能迴避原子類更加高效。
原文連接:
www.cnblogs.com/xrq730/p/4976007.html