1. 程式人生 > >Java-原子操作和原子變數

Java-原子操作和原子變數

API連結:https://docs.oracle.com/javase/8/docs/api/

原子操作:不可被中斷的一個或一系列操作。在多核處理器上實現原子操作會變得複雜許多。

Java8種資料型別,並且每個資料型別都有一個包裝類,如intInteger,它們之間的轉化也就是我們常稱作的自動拆箱和裝箱的過程。但是它們只是一個簡單的資料,當在併發程式設計下,沒有任何特殊的語義。

volatile能保證可見性,以及阻止編譯器和處理器對其重排序,並且對單一資料讀寫具有原子性,然而對於複合操作卻沒有原子性,比如i++那麼如果需要一種原子性int那就是atomic包下面的AtomicInteger了。

AtomicXXX具體的實現:volatile的基本資料型別+CAS操作。
volatile
保證可見性,當一個執行緒修改volatile變數時,其他執行緒拿到的都是修改後的最新值。

裡面的方法都是對Unsafe方法的封裝,而Unsafe裡面的方法都是JNI方法,通過呼叫底層c++方法從而實現指令級的原子操作。

一、原子更新基本型別

使用原子方式更新基本型別,共包括3個類:

AtomicBoolean:原子更新布林變數

AtomicInteger:原子更新整型變數

AtomicLong:原子更新長整型變數

構造方法:

public AtomicInteger(int initialValue)

public AtomicInteger()初始值為0

 get set方法:

public final int get()

public final void set(int newValue)

public final void lazySet(int newValue){
       unsafe.putOrderedInt(this,valueOffset, newValue);
} //
沒有storeload屏障的set1.6之後才有

public final int getAndSet(int newValue){
        return unsafe.getAndSetInt(this,valueOffset,newValue);
} //

獲取當前值並set新值

public final boolean compareAndSet(int expect,int update) {
        return unsafe.compareAndSwapInt(this,valueOffset, expect, update);
}//
比較新值與期望相符則set新值

public final boolean weakCompareAndSet(int expect,int update)  {
        return unsafe.compareAndSwapInt(this,valueOffset, expect, update);
} //weak
CAS,也就是沒有volatile語義的CAS,沒有加入記憶體屏障

加減操作:

public final int getAndIncrement() {
        return unsafe.getAndAddInt(this,valueOffset,1);
} //
獲取當前值並加一

public final int getAndAddInt(Object var1, long var2, int var4) {

int var5;

       do {

           var5 = this.getIntVolatile(var1,var2);

       } while(!this.compareAndSwapInt(var1,var2, var5, var5 + var4));

       return var5;

    }

public final int getAndDecrement() {
        return unsafe.getAndAddInt(this,valueOffset, -1);
} //
獲取當前值並減一

public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this,valueOffset, delta);
} //
獲取當前值並加上差值

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this,valueOffset, 1) + 1;
}//
加一之後獲取當前值

public final int decrementAndGet(){ 

       return unsafe.getAndAddInt(this, valueOffset, -1) - 1; 

    }  //減一之後獲取當前值

public final int addAndGet(int delta) { 

       return unsafe.getAndAddInt(this, valueOffset, delta) + delta; 

    }  //加差值之後獲取當前值

阻塞式更新,並且對prev進行一個IntUnaryOperator操作運算:

public final int getAndUpdate(IntUnaryOperator updateFunction){ 

       int prev, next; 

       do { 

           prev =get(); 

           next =updateFunction.applyAsInt(prev); 

       } while(!compareAndSet(prev, next)); 

       return prev; 

    } 

public final int updateAndGet(IntUnaryOperator updateFunction) {
        int prev, next;
        do{            prev = get();
            next=updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev,next));
        return next;
}

阻塞式更新,並對prevx,進行二元運算操作:

public final int getAndAccumulate(int x,IntBinaryOperator accumulatorFunction) {
        int prev, next;
        do{            prev =get();
            next=accumulatorFunction.applyAsInt(prev, x);
        } while (!compareAndSet(prev,next));
        return prev;
    }

public final int accumulateAndGet(int x,IntBinaryOperator accumulatorFunction)(intx, 

                                     IntBinaryOperatoraccumulatorFunction) { 

       int prev, next; 

       do { 

           prev =get(); 

           next =accumulatorFunction.applyAsInt(prev, x); 

       } while(!compareAndSet(prev, next)); 

       return next; 

    } 

 都是通過一個死迴圈進行反覆的進行CAS操作,直到更新成功才返回

轉換成基礎值:

public int intValue(){  

    return (long)get();  

}  

public long longValue()

public float floatValue()

public double doubleValue()

lazySet

從上面程式碼可以看到,由於valuevolatile型別,所以普通方法set,就是寫入volatile型別變數。此時JVM會插入特定的記憶體屏障,記憶體語義具有可見性。

lazySet懶惰setset是記憶體語義是立即對其他執行緒可見,則lazySet則是不一定立即可見。

1.首先set()是對volatile變數的一個寫操作,我們知道volatilewrite為了保證對其他執行緒的可見性會追加以下兩個Fence(記憶體屏障)

1)StoreStore // intel cpu,不存在[寫寫]重排序,這個可以直接省略了
2)StoreLoad //
這個是所有記憶體屏障裡最耗效能的 2.   lazySet()省去了StoreLoad屏障,只留下StoreStore。詳見Doug Lea的描述: 所以這樣一來,在效率上毫無疑問lazySet要比set高很多,可以這樣理解,lazySetintel cpu中,其實就可以看做普通變數的寫操作了。 lazySetset()具有效能優勢,但是使用場景很有限。

相關參考:http://ifeve.com/juc-atomic-class-lazyset-que/

weakCompareAndSet

基於Java8分析,在上述程式碼中,可以很明顯的看到weakCompareAndSet方法和compareAndSet方法,具有相同的實現,但是為啥名字不同呢?其實按照DougLea本來的設計意圖,是想吧weakCompareAndSet設定為一種在效能上更加高效的方法。由於compareAndSet和其他讀取並更新的操作,擁有相同的記憶體語義,即具有原子性。所以設計了weakCompareAndSet方法,在讀上具有通用的原子性,但是寫方面不具有volatile語義了,換而言之,weakCompareAndSet的寫操作,不能即時被其他執行緒可見。上述詳情參見:

IntUnaryOperatorIntBinaryOperator

在上面程式碼最下面,可以看到兩個具有阻塞性的方法,updateAndGetgetAndAccumulate,這兩個方法是Java8新加入的,增加了函數語言程式設計的運用。
IntUnaryOperator

@Functional Interface

Public interface IntUnaryOperator{

/**

*一個運算元的函式

*/

    int applyAsInt(int operand);

    //compose

    defalut IntUnaryOperator compose(IntUnaryOperator before){

        Objects requireNonNull(before);

        return (int v)->applyAsInt(before.applyAsInt(v));

    }

    //andThen

    default IntUnaryOperator andThen(IntUnaryOperator after){

        Objects requireNonNull(after);

        return (int i)->after.applyAsInt(t);

    }

//返回當前運算值

    static IntUnaryOperator identity(){

        return t->t;

    }

}

如上,IntUnaryOperator就是一個隊單個int型數的操作運算,而composeandThen,可以參看:Function介面學習(composeandThen)

IntBinaryOperator
IntBinaryOperator
則更加簡單,也是函式式的方法介面,就只有一個待實現方法:

@Functional Interface

Public interface IntBinaryOperator{

   /**

    * 兩個int的原酸

    *

    *@param left the firstoperand

    *@param right thesecond operand

    *@return the operatorresult

    */

    int applyAsInt(intleft, int right);

}

AtomicLong注意點:

有些機器是32位的,所以會把64long型別volatile拆分成232位進行計算,但有些不是的。所以在實現AtomicLong的時候,如果是32位,那就需要加上鎖來實現CAS操作。JVM特別的還加了一個判斷,來區分是32位:

/**
     * Records whether the underlying JVM supports lockless
     * compareAndSwap for longs. While the Unsafe.compareAndSwapLong
     * method works in either case, some constructions shouldbe
     * handled at Java level to avoid locking user-visible locks.
     */
   
static final boolean VM_SUPPORTS_LONG_CAS =VMSupportsCS8();

    /**
     * Returns whether underlying JVM supports lockless CompareAndSet
     * for longs. Called only once and cachedinVM_SUPPORTS_LONG_CAS.
     */
    private static native Boolean VMSupportsCS8();

AtomicBoolean注意點:

AtomicBoolean中,作為volatile變數的,並不是boolean型別,而是一個int型別的01來分別表示falsetrue。

Private volatile int value;

/**

*Creates a new {@code AtomicBoolean} with the given initial value.

*@param initialValuethe initial value

*/

publicAtomicBoolean(boolean initialValue) {

    value= initialValue ?1:0;

}

二、原子更新陣列

注意,Java中Atomic*Array,並不是對整個陣列物件實現原子化(也沒有必要這樣做),而是對陣列中的某個元素實現原子化。

通過原子更新數組裡的某個元素,共有3個類:

AtomicIntegerArray:原子更新整型陣列的某個元素

AtomicLongArray:原子更新長整型陣列的某個元素

AtomicReferenceArray:原子更新引用型別陣列的某個元素

AtomicIntegerArray常用的方法有:

int addAndSet(int i, int delta):以原子方式將輸入值與陣列中索引為i的元素相加

boolean compareAndSet(int i,int expect,int update):如果當前值等於預期值,則以原子方式更新陣列中索引為i的值為update

AtomicIntegerArray會將當前陣列拷貝一份,所以在陣列拷貝的操作不影響原陣列的值。

package com.rhwayfun.concurrency.r0405;
   
import java.util.concurrent.atomic.AtomicIntegerArray;
   
/**
     * Created by rhwayfun on 16-4-5.
     */
publicclassAtomicIntegerArrayDemo {

staticint[] value =newint[]{1,2};
       
static AtomicIntegerArray ai =new AtomicIntegerArray(value);
       
publicstaticvoidmain(String[] args){
            ai.getAndSet(
0,3);
            System.out.println(ai.get(
0));
            System.out.println(value[
0]);
        }
    }

執行結果:

3

1

三、原子更新引用型別

引用的操作本身不就是原子的嗎?

一個物件的引用,從A切換到B,本身也不會出現 非原子操作啊?這種想法本身沒有什麼問題,

但是考慮下嘛的場景:物件a,當前執行引用a1,

執行緒X期望將a的引用設定為a2,也就是a=a2,

執行緒Y期望將a的引用設定為a3,也就是a=a3。

X要求,a必須從a1變為a2,也就是說compareAndSet(expect=a1,setValue=a2);

Y要求,a必須從a1變為a3,也就是說compareAndSet(expect=a1,setValue=a3)。

如果嚴格遵循要求,應該出現X把a的引用設定為a2後,Y執行緒操作失敗的情況,也就是說:

X:a==a1--> a=a2;

Y:a!=a1 --> Exception;

如果沒有原子化,那麼Y會直接將a賦值為a3,從而導致出現髒資料。

這就是原子引用AtomicReference存在的原因。

需要更新引用型別往往涉及多個變數,早atomic包有三個類:

AtomicReference:原子更新引用型別

AtomicReferenceFieldUpdater:原子更新引用型別裡的欄位

AtomicMarkableReference:原子更新帶有標記位的引用型別。

public final V getAndSet(V newValue) {
2           while (true) {
3                V x = get();
4               if (compareAndSet(x, newValue))
5                   return x;
6           }
7        }

注意,AtomicReference要求引用也是volatile的。

下面以AtomicReference為例進行說明:

package com.rhwayfun.concurrency.r0405;

import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by rhwayfun on 16-4-5.
 */
public class AtomicReferenceDemo {

    static class User{
        private String name;
        private int id;

        public User(String name, int id) {
            this.name = name;
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }
    }

    public static AtomicReference<User> ar = new AtomicReference<User>();

    public static void main(String[] args){
        User user = new User("aa",11);
        ar.set(user);
        User newUser = new User("bb",22);
        ar.compareAndSet(user,newUser);
        System.out.println(ar.get().getName());
        System.out.println(ar.get().getId());
    }
}執行結果為:bb 22可以看到user被成功更新。

四、原子更新欄位類

其它幾個Atomic類,都是對被volatile修飾的基本資料型別的自身資料進行原子化操作,

但是如果一個被volatile修飾的變數本身已經存在在類中,那要如何提供原子化操作呢?

如果需要原子更新某個類的某個欄位,就需要用到原子更新欄位類,可以使用以下幾個類:

AtomicIntegerFieldUpdater:原子更新整型欄位

AtomicLongFieldUpdater:原子更新長整型欄位

AtomicStampedReference:原子更新帶有版本號的引用型別。

要想原子更新欄位,需要兩個步驟:

1.     每次必須使用newUpdater建立一個更新器,並且需要設定想要更新的類的欄位

2.     更新類的欄位(屬性)必須為public volatile

下面的程式碼演示如何使用原子更新欄位類更新欄位:

package com.rhwayfun.concurrency.r0405;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * Created by rhwayfun on 16-4-5.
 */
public class AtomicIntegerFieldUpdaterDemo {

    //建立一個原子更新器
    private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater =
            AtomicIntegerFieldUpdater.newUpdater(User.class,"old");

    public static void main(String[] args){
        User user = new User("Tom",15);
        //原來的年齡
        System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));
        //現在的年齡
        System.out.println(atomicIntegerFieldUpdater.get(user));
    }

    static class User{
        private String name;
        public volatile int old;

        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getOld() {
            return old;
        }

        public void setOld(int old) {
            this.old = old;
        }
    }
}

輸出的結果如下:

15
16

參考: 

http://blog.csdn.net/anla_/article/details/78652509

https://www.cnblogs.com/my376908915/p/6758415.html

https://www.jianshu.com/p/9ff426a784ad