鎖
鎖是用來做並發的最簡單的方式,其代價也是最高的,java 在JDK1.5之前都是通過synchronized關鍵字來保證同步的,他是一種獨占鎖,使用synchronized同步鎖進行線程阻塞和喚醒切換以及用戶態內核態間的切換操作額外浪費消耗cpu資源,鎖還存在着其它一些缺點,當一個線程正在等待鎖時,它不能做任何事。如果一個線程在持有鎖的情況下被延遲執行,那么所有需要這個鎖的線程都無法執行下去。
CAS
無鎖的非堵塞算法采用一種比較交換技術CAS(compare and swap)來鑒別線程沖突,一旦檢測到沖突,就充實當前操作指導沒有沖突為止。CAS基於硬件實現,不需要進入內核,不需要切換線程,因此可以獲得更高的性能。但對於資源競爭嚴重的情況,CAS自旋的概率會比較大,從而浪費更多的CPU資源。
CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。
CAS(比較並交換)是CPU指令級的操作,只有一步原子操作,CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。CAS語義是“我認為V的值應該是A,如果是,那么就將V的值更新成B, 否則不更新,並告訴V的實際是是多少”。
偽代碼可以這樣表示:
do{
備份舊數據;
基於舊數據構造新數據;
}while(!CAS( 內存地址,備份的舊數據,新數據 ))
就是指當兩者進行比較時,如果相等,則證明共享數據沒有被修改,替換成新值,然后繼續往下運行;如果不相等,說明共享數據已經被修改,放棄已經所做的操作,然后重新執行剛才的操作。容易看出 CAS 操作是基於共享數據不會被修改的假設,采用了類似於數據庫的 commit-retry 的模式。當同步沖突出現的機會很少時,這種假設能帶來較大的性能提升。
CAS缺點
雖然CAS有效的解決了原子操作的問題,但是其仍然有三個劣勢:
1、ABA問題:因為CAS需要在操作前檢查下值有沒有發生變化,如果沒有則更新。但是如果一個值開始的時候是A,變成了B,又變成了A,那么使用CAS進行檢查的時候會發現它的值沒有發生變化,但是事實卻不是如此。
ABA問題的解決思路是使用版本號,如A-B-A變成1A-2B-3A
2、循環時間長開銷大:自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。
3、只能保證一個共享變量的原子操作:對一個共享變量可以使用CAS進行原子操作,但是多個共享變量的原子操作就無法使用CAS,這個時候只能使用鎖。
Java中的原子操作( atomic operations)
原子操作指的是在一步之內就完成而且不能被中斷。原子操作在多線程環境中是線程安全的,無需考慮同步的問題。在java中,下列操作是原子操作:
all assignments of primitive types except for long and double(基本數據類型除了long 和double)
all assignments of references(引用類型)
all operations of java.concurrent.Atomic* classes(原子類)
all assignments to volatile longs and doubles(volatile 修飾的long 和double)
為什么long型賦值不是原子操作呢?例如:
long foo = 65465498L;
實時上java會分兩步寫入這個long變量,先寫32位,再寫后32位。這樣就線程不安全了。如果改成下面的就線程安全了:
private volatile long foo;
因為volatile內部已經做了synchronized.
JVM 對CAS支持
在 JDK5.0 之前,如果不使用本機代碼,就不能用 Java 語言編寫無等待、無鎖定的算法。在JDK1.5中引入了底層的支持,在int、long和對象的引用等類型上都公開了CAS的操作,並且JVM把它們編譯為底層硬件提供的最有效的方法,在運行CAS的平台上,運行時把它們編譯為相應的機器指令。
//JDK 8 - AtomicInteger
//fetch and add
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//JDK7 - AtomicInteger
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
