java AtomicLong原理解析

java AtomicLong原理解析

摘自

http://www.tuicool.com/articles/zuui6z

樂觀鎖與悲觀鎖

獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,它假設最壞的情況,並且只有在確保其它線程不會造成干擾的情況下執行,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。而另一個更加有效的鎖就是樂觀鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止。

volatile的問題

與鎖相比,volatile變量是一和更輕量級的同步機制,因爲在使用這些變量時不會發生上下文切換和線程調度等操作,但是volatile變量也存在一些侷限:不能用於構建原子的複合操作,因此當一個變量依賴舊值時就不能使用volatile變量。(參考:談談volatiile)

Java中的原子操作( atomic operations)

原子操作指的是在一步之內就完成而且不能被中斷。原子操作在多線程環境中是線程安全的,無需考慮同步的問題。在java中,下列操作是原子操作:

  • all assignments of primitive types except for long and double
  • all assignments of references
  • all operations of java.concurrent.Atomic* classes
  • all assignments to volatile longs and doubles

問題來了,爲什麼long型賦值不是原子操作呢?例如:

long foo = 65465498L;

實時上java會分兩步寫入這個long變量,先寫32位,再寫後32位。這樣就線程不安全了。如果改成下面的就線程安全了:

private volatile long foo;

因爲volatile內部已經做了synchronized.

CAS無鎖算法

要實現無鎖(lock-free)的非阻塞算法有多種實現方法,其中 CAS(比較與交換,Compare and swap) 是一種有名的無鎖算法。CAS, CPU指令,在大多數處理器架構,包括IA32、Space中採用的都是CAS指令,CAS的語義是“我認爲V的值應該爲A,如果是,那麼將V的值更新爲B,否則不修改並告訴V的值實際爲多少”,CAS是項 樂觀鎖 技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改爲B,否則什麼都不做。CAS無鎖算法的C實現如下:

int compare_and_swap (int* reg, int oldval, int newval) 
{
  ATOMIC();
  int old_reg_val = *reg;
  if (old_reg_val == oldval) 
     *reg = newval;
  END_ATOMIC();
  return old_reg_val;
}

CAS(樂觀鎖算法)的基本假設前提

CAS比較與交換的僞代碼可以表示爲:

do{   
       備份舊數據;  
       基於舊數據構造新數據;  
}while(!CAS( 內存地址,備份的舊數據,新數據 ))  

就是指當兩者進行比較時,如果相等,則證明共享數據沒有被修改,替換成新值,然後繼續往下運行;如果不相等,說明共享數據已經被修改,放棄已經所做的操作,然後重新執行剛纔的操作。容易看出 CAS 操作是基於共享數據不會被修改的假設,採用了類似於數據庫的 commit-retry 的模式。當同步衝突出現的機會很少時,這種假設能帶來較大的性能提升。

JVM對CAS的支持:AtomicInt, AtomicLong.incrementAndGet()

複製代碼
    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
  

     private volatile long value //value設置爲volatile變量,目的是每次變量的值變更時,其他線程再取該值時,始終爲內存中最新的值

    public final long getAndIncrement() {
    //不加鎖,當設置失敗時,利用死循環,再次嘗試,直至設置成功
while (true) { long current = get(); long next = current + 1;
       //調用compareAndSet方法
if (compareAndSet(current, next)) return current; } } /**
複製代碼
複製代碼
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *  
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */

public final boolean compareAndSet(long expect, long update) {
    
//valueOffSet爲內存中的值,expect的值爲舊的預期值,該線程執行getAndIncrement()函數時,通過get()獲取的當時的變量值
    //update=expect+1
    // 只有valueOffset=expect時纔會把變量的值設置爲update,設置成功返回true,否則返回false
    return unsafe.compareAndSwapLong(this, valueOffset, expect, update); 
}
複製代碼

 

二、ABA問題

比如說一個線程one從內存位置V中取出A,這時候另一個線程two也從內存中取出A,並且two進行了一些操作變成了B,然後two又將V位置的數據變成A,這時候線程one進行CAS操作發現內存中仍然是A,然後one操作成功。儘管線程one的CAS操作成功,但是不代表這個過程就是沒有問題的。如果鏈表的頭在變化了兩次後恢復了原值,但是不代表鏈表就沒有變化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。這允許一對變化的元素進行原子操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章