1. 程式人生 > >Atomic的介紹和使用(原子變數)

Atomic的介紹和使用(原子變數)

開始之前,我們來看一下上一篇文章中《CAS (全 ) && concurrent包的實現》中提到了concurrent包的實現圖。
下圖中的原子變數類就是Atomic類中的一部分。
也就是說,atomic類首先是一個樂觀鎖,然後底層實現也是根據CAS操作和Volatile關鍵字實現的。
在這裡插入圖片描述

什麼是執行緒安全?
**執行緒安全是指多執行緒訪問是時,無論執行緒的排程策略是什麼,程式能夠正確的執行。**導致執行緒不安全的一個原因是狀態不一致,如果執行緒A修改了某個共享變數(比如給id++),而執行緒B沒有及時知道,就會導致B在錯誤的狀態上執行,結果的正確性也就無法保證。原子變數為我們提供了一種保證單個狀態一致的簡單方式,一個執行緒修改了原子變數,另外的執行緒立即就能看到,這比通過鎖實現的方式效率要高;如果要同時保證多個變數狀態一致,就只能使用鎖了。

Atomic
在JDK1.5之後,JDK的(concurrent包)併發包裡提供了一些類來支援原子操作,如AtomicBoolean,AtomicInteger,AtomicLong都是用原子的方式來更新指定型別的值。

從多執行緒平行計算樂觀鎖 和 悲觀鎖 來講,JAVA中的synchronized 屬於悲觀鎖,即是在操作某資料的時候總是會認為多執行緒之間會相互干擾,屬於阻塞式的加鎖;Atomic系列則屬於樂觀鎖系列,即當操作某一段資料的時候,執行緒之間是不會相互影響,採用非阻塞的模式,直到更新資料的時候才會進行版本的判斷是否值已經進行了修改。

sun.misc.Unsafe
這個類包含了大量的對C程式碼的操作,包括了很多直接記憶體分配和原子操作的呼叫,都存在安全隱患,所以標記為unsafe。
AtomicInteger是一個標準的樂觀鎖實現,sun.misc.Unsafe是JDK提供的樂觀鎖的支援。

Atomic在JAVA中的家族如下:
a、基本類:AtomicInteger、AtomicLong、AtomicBoolean;
b、引用型別:AtomicReference、AtomicReference的ABA例項、AtomicStampedRerence、AtomicMarkableReference;
c、陣列型別:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
d、屬性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

Atomic的使用測試和講解 見:https://blog.csdn.net/xh16319/article/details/17056767
這裡我們不對每一個Atomic類進行詳細的測試和講解,我們只需要知道Atomic底層是通過CAS操作實現的資料的原子性,其中AtomicStampedReference類解決了ABA問題(ABA問題在《CAS (全 ) && concurrent包的實現》中也有講解)

我們在這裡只對 AtomicStampedReference 類和AtomicMarkableReference類的使用進行簡單描述,讀者理解其用法即可。

import java.util.concurrent.atomic.AtomicStampedReference;  
  
public class AtomicStampedReferenceTest {     
    public final static AtomicStampedReference <String>ATOMIC_REFERENCE = new AtomicStampedReference<String>("abc" , 0);  
      
    public static void main(String []args) {  
        for(int i = 0 ; i < 100 ; i++) {  
            final int num = i;  
            final int stamp = ATOMIC_REFERENCE.getStamp();  
            new Thread() {  
                public void run() {  
                    try {  
                        Thread.sleep(Math.abs((int)(Math.random() * 100)));  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2" , stamp , stamp + 1)) {  
                        System.out.println("我是執行緒:" + num + ",我獲得了鎖進行了物件修改!");  
                    }  
                }  
            }.start();  
        }  
        //下面這個執行緒是為了將資料改回原始值,以便之後的操作
        new Thread() {  
            public void run() {  
                int stamp = ATOMIC_REFERENCE.getStamp();  
                while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc" , stamp , stamp + 1));  
                System.out.println("已經改回為原始值!");  
            }  
        }.start();  
    }  
}  

其中的compareAndSet函式的原始碼中就用到了之前文章中講解的CAS操作:

public final boolean compareAndSet(V expect, V update) {  
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);  
}  

類:AtomicMarkableReference和AtomicStampedReference功能差不多,有點區別的是:它描述更加簡單的是與否的關係,通常ABA問題只有兩種狀態,而AtomicStampedReference是多種狀態,那麼為什麼還要有AtomicMarkableReference呢,因為它在處理是與否上面更加具有可讀性,而AtomicStampedReference過於隨意定義狀態,並不便於閱讀大量的是和否的關係,它可以被認為是一個計數器或狀態列表等資訊,java提倡通過類名知道其意義,所以這個類的存在也是必要的,它的定義就是將資料變換為true|false如下:

public final static AtomicMarkableReference <String>ATOMIC_MARKABLE_REFERENCE = new AtomicMarkableReference<String>("abc" , false);  

操作時使用:

ATOMIC_MARKABLE_REFERENCE.compareAndSet("abc", "abc2", false, true);  

另外我們來簡單介紹一些Atomic中的擴充套件類:
java提供一個外部的Updater可以對物件的屬性本身的修改提供類似Atomic的操作,也就是它對這些普通的屬性的操作是併發下安全的,分別由:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceUpdater

這樣操作後,系統會更加靈活,也就是可能那些類的屬性只是在某些情況下需要控制併發,很多時候不需要,但是他們的使用通常有以下幾個限制:
限制1:操作的目標不能是static型別,前面說到unsafe的已經可以猜測到它提取的是非static型別的屬性偏移量,如果是static型別在獲取時如果沒有使用對應的方法是會報錯的,而這個Updater並沒有使用對應的方法。

限制2:操作的目標不能是final型別的,因為final根本沒法修改。

限制3:必須是volatile型別的資料,也就是資料本身是讀一致的。

限制4:屬性必須對當前的Updater所在的區域是可見的,也就是private如果不是當前類肯定是不可見的,protected如果不存在父子關係也是不可見的,default如果不是在同一個package下也是不可見的。

**實現方式:**通過反射找到屬性,對屬性進行操作,但是並不是設定accessable,所以必須是可見的屬性才能操作。

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;  
  
public class AtomicIntegerFieldUpdaterTest {  
  
    static class A {  
        volatile int intValue = 100;  
    }  
      
    /** 
     * 可以直接訪問對應的變數,進行修改和處理 
     * 條件:要在可訪問的區域內,如果是private或挎包訪問default型別以及非父親類的protected均無法訪問到 
     * 其次訪問物件不能是static型別的變數(因為在計算屬性的偏移量的時候無法計算),也不能是final型別的變數(因為根本無法修改),必須是普通的成員變數 
     *  
     * 方法(說明上和AtomicInteger幾乎一致,唯一的區別是第一個引數需要傳入物件的引用) 
     * @see AtomicIntegerFieldUpdater#addAndGet(Object, int) 
     * @see AtomicIntegerFieldUpdater#compareAndSet(Object, int, int) 
     * @see AtomicIntegerFieldUpdater#decrementAndGet(Object) 
     * @see AtomicIntegerFieldUpdater#incrementAndGet(Object) 
     *  
     * @see AtomicIntegerFieldUpdater#getAndAdd(Object, int) 
     * @see AtomicIntegerFieldUpdater#getAndDecrement(Object) 
     * @see AtomicIntegerFieldUpdater#getAndIncrement(Object) 
     * @see AtomicIntegerFieldUpdater#getAndSet(Object, int) 
     */  
    public final static AtomicIntegerFieldUpdater <A>ATOMIC_INTEGER_UPDATER = AtomicIntegerFieldUpdater.newUpdater(A.class, "intValue");  
      
    public static void main(String []args) {  
        final A a = new A();  
        for(int i = 0 ; i < 100 ; i++) {  
            final int num = i;  
            new Thread() {  
                public void run() {  
                    if(ATOMIC_INTEGER_UPDATER.compareAndSet(a, 100, 120)) {  
                        System.out.println("我是執行緒:" + num + " 我對對應的值做了修改!");  
                    }  
                }  
            }.start();  
        }  
    }  
}