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就很有用了。這允許一對變化的元素進行原子操作。