1. 程式人生 > >Java中Atomic包的原理和分析

Java中Atomic包的原理和分析

原文地址:http://blog.csdn.net/zhangerqing/article/details/43057799

Atomic簡介

Atomic包是Java.util.concurrent下的另一個專門為執行緒安全設計的Java包,包含多個原子操作類。這個包裡面提供了一組原子變數類。其基本的特性就是在多執行緒環境下,當有多個執行緒同時執行這些類的例項包含的方法時,具有排他性,即當某個執行緒進入方法,執行其中的指令時,不會被其他執行緒打斷,而別的執行緒就像自旋鎖一樣,一直等到該方法執行完成,才由JVM從等待佇列中選擇一個另一個執行緒進入,這只是一種邏輯上的理解。實際上是藉助硬體的相關指令來實現的,不會阻塞執行緒(或者說只是在硬體級別上阻塞了)。可以對基本資料、陣列中的基本資料、對類中的基本資料進行操作。原子變數類相當於一種泛化的volatile變數,能夠支援原子的和有條件的讀-改-寫操作。——  引自@

傳統鎖的問題

我們先來看一個例子:計數器(Counter),採用Java裡比較方便的鎖機制synchronized關鍵字,初步的程式碼如下:

  1. class Counter {  
  2.     privateint value;  
  3.     publicsynchronizedint getValue() {  
  4.         return value;  
  5.     }  
  6.     publicsynchronizedint increment() {  
  7.         return ++value;  
  8.     }  
  9.     publicsynchronizedint decrement() {  
  10.         return --value;  
  11.     }  
  12. }   

其實像這樣的鎖機制,滿足基本的需求是沒有問題的了,但是有的時候我們的需求並非這麼簡單,我們需要更有效,更加靈活的機制,synchronized關鍵字是基於阻塞的鎖機制,也就是說當一個執行緒擁有鎖的時候,訪問同一資源的其它執行緒需要等待,直到該執行緒釋放鎖,這裡會有些問題:首先,如果被阻塞的執行緒優先順序很高很重要怎麼辦?其次,如果獲得鎖的執行緒一直不釋放鎖怎麼辦?(這種情況是非常糟糕的)。還有一種情況,如果有大量的執行緒來競爭資源,那CPU將會花費大量的時間和資源來處理這些競爭(事實上CPU的主要工作並非這些),同時,還有可能出現一些例如死鎖之類的情況,最後,其實鎖機制是一種比較粗糙,粒度比較大的機制,相對於像計數器這樣的需求有點兒過於笨重,因此,對於這種需求我們期待一種更合適、更高效的執行緒安全機制。

硬體同步策略

現在的處理器都支援多重處理,當然也包含多個處理器共享外圍裝置和記憶體,同時,加強了指令集以支援一些多處理的特殊需求。特別是幾乎所有的處理器都可以將其他處理器阻塞以便更新共享變數。

Compare and swap(CAS)

當前的處理器基本都支援CAS,只不過每個廠家所實現的演算法並不一樣罷了,每一個CAS操作過程都包含三個運算子:一個記憶體地址V,一個期望的值A和一個新值B,操作的時候如果這個地址上存放的值等於這個期望的值A,則將地址上的值賦為新值B,否則不做任何操作。CAS的基本思路就是,如果這個地址上的值和期望的值相等,則給其賦予新值,否則不做任何事兒,但是要返回原值是多少。我們來看一個例子,解釋CAS的實現過程(並非真實的CAS實現):

  1. class SimulatedCAS {  
  2.     privateint value;  
  3.     publicsynchronizedint getValue() {  
  4.         return value;  
  5.     }  
  6.     publicsynchronizedint compareAndSwap(int expectedValue, int newValue) {  
  7.         int oldValue = value;  
  8.         if (value == expectedValue)  
  9.             value = newValue;  
  10.         return oldValue;  
  11.     }  
  12. }  
下面是一個用CAS實現的Counter
  1. publicclass CasCounter {  
  2.     private SimulatedCAS value;  
  3.     publicint getValue() {  
  4.         return value.getValue();  
  5.     }  
  6.     publicint increment() {  
  7.         int oldValue = value.getValue();  
  8.         while (value.compareAndSwap(oldValue, oldValue + 1) != oldValue)  
  9.             oldValue = value.getValue();  
  10.         return oldValue + 1;  
  11.     }  
  12. }  

Atomic類

在JDK5.0之前,想要實現無鎖無等待的演算法是不可能的,除非用本地庫,自從有了Atomic變數類後,這成為可能。下面這張圖是java.util.concurrent.atomic包下的類結構。

  • 標量類:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 陣列類:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器類:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 複合變數類:AtomicMarkableReference,AtomicStampedReference

第一組AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference這四種基本型別用來處理布林,整數,長整數,物件四種資料,其內部實現不是簡單的使用synchronized,而是一個更為高效的方式CAS (compare and swap) + volatile和native方法,從而避免了synchronized的高開銷,執行效率大為提升。我們來看個例子,與我們平時i++所對應的原子操作為:getAndIncrement()

  1. publicstaticvoid main(String[] args) {  
  2.     AtomicInteger ai = new AtomicInteger();  
  3.     System.out.println(ai);  
  4.     ai.getAndIncrement();  
  5.     System.out.println(ai);  
  6. }  
我們可以看一下AtomicInteger的實現:
  1. /** 
  2.  * Atomically increments by one the current value. 
  3.  * 
  4.  * @return the previous value 
  5.  */
  6. publicfinalint getAndIncrement() {  
  7.     return unsafe.getAndAddInt(this, valueOffset, 1);  
  8. }  

這裡直接呼叫一個叫Unsafe的類去處理,看來我們還需要繼續看一下unsafe類的原始碼了。JDK8中sun.misc下UnSafe類,點選檢視原始碼

從原始碼註釋得知,這個類是用於執行低級別、不安全操作的方法集合。儘管這個類和所有的方法都是公開的(public),但是這個類的使用仍然受限,你無法在自己的java程式中直接使用該類,因為只有授信的程式碼才能獲得該類的例項。所以我們平時的程式碼是無法使用這個類的,因為其設計的操作過於偏底層,如若操作不慎可能會帶來很大的災難,所以直接禁止普通程式碼的訪問,當然JDK使用是沒有問題的。

Atomic中的CAS

從前面的解釋得知,CAS的原理是拿期望的值和原本的一個值作比較,如果相同則更新成新的值,此處這個“原本的一個值”怎麼來,我們看看AtomicInteger裡的實現:

  1.     // setup to use Unsafe.compareAndSwapInt for updates
  2.     privatestaticfinal Unsafe unsafe = Unsafe.getUnsafe();  
  3.     privatestaticfinallong valueOffset;  
  4.     static {  
  5.         try {  
  6.             valueOffset = unsafe.objectFieldOffset  
  7.                 (AtomicInteger.class.getDeclaredField("value"));  
  8.         } catch (Exception ex) { thrownew Error(ex); }  
  9.     }  

這裡用到UnSafe的一個方法objectFieldOffset(),檢視原始碼:
  1. /** 
  2.      * Report the location of a given static field, in conjunction with {@link 
  3.      * #staticFieldBase}. 
  4.      * <p>Do not expect to perform any sort of arithmetic on this offset; 
  5.      * it is just a cookie which is passed to the unsafe heap memory accessors. 
  6.      * 
  7.      * <p>Any given field will always have the same offset, and no two distinct 
  8.      * fields of the same class will ever have the same offset. 
  9.      * 
  10.      * <p>As of 1.4.1, offsets for fields are represented as long values, 
  11.      * although the Sun JVM does not use the most significant 32 bits. 
  12.      * It is hard to imagine a JVM technology which needs more than 
  13.      * a few bits to encode an offset within a non-array object, 
  14.      * However, for consistency with other methods in this class, 
  15.      * this method reports its result as a long value. 
  16.      * @see #getInt(Object, long) 
  17.      */
  18.     publicnativelong objectFieldOffset(Field f);  

這個方法是用來拿到我們上文提到的這個“原來的值”的記憶體地址。是一個本地方法,返回值是valueOffset。它的引數field就是AtomicInteger裡定義的value屬性:
  1. privatevolatileint value;  
  2. /** 
  3.  * Creates a new AtomicInteger with the given initial value. 
  4.  * 
  5.  * @param initialValue the initial value 
  6.  */
  7. public AtomicInteger(int initialValue) {  
  8.     value = initialValue;  
  9. }  
  10. /** 
  11.  * Creates a new AtomicInteger with initial value {@code 0}. 
  12.  */
  13. public AtomicInteger() {  
  14. }  

value是一個volatile變數,在記憶體中可見,任何執行緒都不允許對其進行拷貝,因此JVM可以保證任何時刻任何執行緒總能拿到該變數的最新值。此處value的值,可以在AtomicInteger類初始化的時候傳入,也可以留空,留空則自動賦值為0。

我們再回到CAS,看看getAndIncrement()方法是怎麼利用CAS實現的。

  1.    /** 
  2.     * Atomically increments by one the current value. 
  3.     * 
  4.     * @return the previous value 
  5.     */
  6.    publicfinalint getAndIncrement() {  
  7.        return unsafe.getAndAddInt(this, valueOffset, 1);  
  8.    }  

繼續:

  1. publicfinalint getAndAddInt(Object o, long offset, int delta) {  
  2.         int v;  
  3.         do {  
  4.             v = getIntVolatile(o, offset);//------------0---------------
  5.         } while (!compareAndSwapInt(o, offset, v, v + delta));//-------------1-------------
  6.         return v;  
  7.     }  

  1. /** 
  2.    * Atomically update Java variable to <tt>x</tt> if it is currently 
  3.    * holding <tt>expected</tt>. 
  4.    * @return <tt>true</tt> if successful 
  5.    */
  6.   publicfinalnativeboolean compareAndSwapInt(Object o, long offset,//---------------2--------------
  7.                                                 int expected,  
  8.                                                 int x);  

我稍微解釋一下,其實compareAndSwapInt的註釋解釋的很明確,原子的將變數的值更新為x,如果成功了返回true,我們知道,如果我們建立AtomicInteger例項時不傳入引數,則原始變數的值即為0,所以上面//----------0-----------處得到的v的值即為0,1處的程式碼為:

while(!compareAndSwapInt(o, offset, 0, 1))我們知道offset指向的地址對應的值就是原始變數的初值0,所以與期望的值0相同,所以將初值賦值為1,返回true,取反後為false,迴圈結束,返回v即更新之前的值0. 這就是類似於i++操作的原子操作的實現,當然最終CAS的實現都是native的,用C語言實現的,我們這裡看不到原始碼,有時間我會反編譯一下這段程式碼看看。

CAS執行緒安全

說了半天,我們要回歸到最原始的問題了:這樣怎麼實現執行緒安全呢?請大家自己先考慮一下這個問題,其實我們在語言層面是沒有做任何同步的操作的,大家也可以看到原始碼沒有任何鎖加在上面,可它為什麼是執行緒安全的呢?這就是Atomic包下這些類的奧祕:

相關推薦

JavaAtomic原理分析

原文地址:http://blog.csdn.net/zhangerqing/article/details/43057799 Atomic簡介 Atomic包是Java.util.concurrent下的另一個專門為執行緒安全設計的Java包,包含多個原子操作類。這個

java註解的原理實現機制

                                          &

24.Javaatomic的原子操作類總結

tun 添加 原來 說明 array 內存地址 void rri delta 1. 原子操作類介紹 在並發編程中很容易出現並發安全的問題,有一個很簡單的例子就是多線程更新變量i=1,比如多個線程執行i++操作,就有可能獲取不到正確的值,而這個問題,最常用的方法是通過Sy

JAVAjarwar的區別是

服務器 直接 jar文件 tomcat服務 一起 是把 目的 相關 web 其實jar包和war包都可以看成壓縮文件,用解壓軟件都可以打開,jar包和war包所存在的原因是,為了項目的部署和發布,通常把項目打包,通常在打包部署的時候,會在裏面加上部署的相關信息。 這個打包實

java的類鎖物件鎖對比分析

      說到鎖機制,不得不提到Thread執行緒,而又不得不提到synchronized關鍵字,這個單詞的意思是表示“同步”的意思。用它去修飾方法函式的時候,如果有多個執行緒同時呼叫這個方法函式的時候,那麼當一個執行緒獲得鎖的時候,其他的執行緒只

#Java乾貨分享:一篇文章讓你深入瞭解Java介面

很多新手程式設計師對於Java中兩個具創新性的特徵————包與介面不是非常清楚,所以我特意發了這篇文章來闡述什麼是包,什麼是介面。 包(package)是多個類的容器,它們用於保持類的名稱空間相互隔離。 如果有想學習java的程式設計師,可來我們的java學習扣qun:79979,2590免

Java的類載入Class.forName();java反射機制與原理

對於大部分人來說,第一次見到class.forName(String className)這句程式碼應該是在使用jdbc方式連線資料庫的時候。但這句程式碼本質上是什麼含義,做了什麼工作呢?本文將回答此問題。 理解Class.forName方法需要一些知識鋪墊,也就是

javatry/catch效能原理

部分內容轉載自http://blog.csdn.net/tao_zi7890/article/details/17584813 記得在做企業雲專案的時候,我接了兩個有意思的任務,一個是為幾個執行緒加很多的try/catch程式碼。catch的異常有好幾層,範圍最小的,或者說

JUCAtomic分析

併發場景中,為了保證執行緒安全,也就是臨界區程式碼按照我們所想的時序執行,需要進行加鎖,也就是同步控制,但是有很多情況,不需要我們自己進行同步控制,而是可以使用java自帶的併發元件,本文主要講Atomic包中提供原子操作的類,接下來我們依次分析。 Atomi

JavascriptJava的理解

一。Javascript中閉包: 1.變數的作用域   要理解閉包,首先必須理解Javascript特殊的變數作用域。   變數的作用域無非就是兩種:全域性變數和區域性變數。      Javascript語言的特殊之處,就在於函式內部可以直接讀取全域性變數  var n=

二進位制原碼、反碼、補碼以及Java的<< >> >>> 詳細分析

## 1、計算機二進位制系統中最小單位bit > 在計算機二進位制系統中: > bit (位) :資料儲存的最小單元。 簡記為`b`,也稱為位元(`bit`),每個二進位制數字0或1就是一個位(`bit`),其中,每 `8bit = 1 byte`(位元組); > 再回顧Java 中的

Java實現String.padLeftString.padRight

toc 還要 color for 失去 1-1 arraycopy ace pre 因為習慣了C#中的padLeft和padRight,接觸Java後突然失去這兩個功能,覺得別扭,就試著實現了這兩個方法。 Java中String.format()中帶有字符串對齊功能如下

理解Java的引用傳遞值傳遞

包裝類 pri ble buffer 聲明 change cnblogs padding ber 關於Java傳參時是引用傳遞還是值傳遞,一直是一個討論比較多的話題,有論壇說Java中只有值傳遞,也有些地方說引用傳遞和值傳遞都存在,比較容易讓人迷惑。關於值傳遞和引用傳遞其

Java靜態變量動態變量

.com pan 1-1 一道 args ict print stat 類的加載 這是我面試遇到的一道題,題目如下: 1 public class StaticTest { 2 private static int b = 1; 3 priv

JAVA 成員變量實例變量區別

protected ket 成員變量 訪問修飾符 lena idt variable 類型 多少 java語言支持的變量類型 類變量:獨立於方法之外的變量,用 static 修飾。 局部變量:類的方法中的變量。 實例變量(全局變量):獨立於方法之外的變量,不過沒有 sta

JAVA的值傳遞引用傳遞問題

log 輸出結果 基礎知識 blue static 繼續 oid .net red   這是個老生常談的問題了,引起過無數爭論,但可以說一直沒有一個令人滿意的回答。   有人總結過: 對象是按引用傳遞的 Java 應用程序有且僅有的一種參數傳遞機制,即按值傳遞

java的序列化反序列化學習筆記

文件 track 反序列化 out val nts 鼠標 main version 須要序列化的Person類: package cn.itcast_07; import java.io.Serializable; /* * NotSerializableE

房上的貓:java

編碼規範 不能訪問 package 選項 imp src 前綴 alt cli 包 1.作用: (1)包允許將類組合成較小的單元(類似文件夾),易於找到和使用相應的類文件 (2)防止命名沖突: java中只有在不同包中的類才能重名 (3)包允許在更廣的範圍內保護

淺談Java的深拷貝淺拷貝

detail tle pac err @override 復制對象 deep har 間接   淺談Java中的深拷貝和淺拷貝(轉載) 原文鏈接: http://blog.csdn.net/tounaobun/article/details/8491392 假如說你想復制一

Java字符編碼字符串所占字節數 .

cor baidu print 世界 encoding p s 技術分享 family lan 首 先,java中的一個char是2個字節。java采用unicode,2個字節來表示一個字符,這點與C語言中不同,C語言中采用ASCII,在大多數 系統中,一個char通常占1