介紹CAS操作前,我們先簡單看一下樂觀鎖 與 悲觀鎖這兩個常見的鎖概念。
悲觀鎖:
從Java多線程角度,存在着“可見性、原子性、有序性”三個問題,悲觀鎖就是假設在實際情況中存在着多線程對同一共享的競爭,所以在操作前先占有共享資源(悲觀態度)。因此,悲觀鎖是阻塞,獨占的,存在着頻繁的線程上下文切換,對資源消耗較大。synchronized就是悲觀鎖的一種實現。
樂觀鎖:
如名一樣,每次操作都認為不會發生沖突,嘗試執行,並檢測結果是否正確。如果正確則執行成功,否則說明發生了沖突,回退再重新嘗試。樂觀鎖的過程可以分為兩步:沖突檢測 和 數據更新。在Java多線程中樂觀鎖一個常見實現即:CAS操作。
CAS
CAS,(Compare-And-Swap,比較和替換)。其具有三個操作數:內存地址V,舊的預期值A,新的預期值B。當V中的值和A相同時,則用新值B替換V中的值,否則不執行更新。(PS:上述的操作是原子性的,因為過程是:要么執行更新,要么不更新)
在JDK1.5新增的java.util.concurrent(J.U.C) 就是建立在CAS操作上的。CAS是一種非阻塞的實現(PS:樂觀鎖采用一種 “自旋鎖”的技術,其原理是:如果存在競爭,則沒有獲得資源的線程不立即掛起,而是采用讓線程執行一個忙循環(自旋)的方式,等待一段時間看是否能獲得鎖,如果超出規定時間再掛起),所以J.U.C在性能上有很大的提升。下面以J.U.C下的AtomicInteger的部分源碼為例,看一下CAS的過程究竟如何。
public class AtomicInteger extends Number implements java.io.Serializable { private volatile int value; public final int get() { return value; } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } // CAS操作 public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } }
以上截取自AtomicInteger的部分源碼。CAS操作的核心就在 getAndIncrement()方法中,在此方法調用的compareAndSet(int expect , int update) 的兩個參數可知,當current值符合expect時,用next替換了current。如果不符合,則在for中不斷嘗試知道成功為止。在這里的步驟,就相當於一個原子性的 ++i 了。(PS: 單純的 ++i 不具備原子性)
其中,compareAndSet方法的實現得益於硬件的發展:多條步驟的操作行為可以通過一條指令完成。(在此是利用JNI來完成CPU指令的操作)
CAS存在的問題
1. ABA問題
ABA問題值,內存地址V的值是A,線程one從V中取出了A,此時線程two也從V中取出了A,同時將A修改為B,但是又因為一些原因修改為A。而此時線程one仍看到V中的值為A,認為沒有發生變化,此為ABA問題。解決ABA問題一種方式是通過版本號(version)。每次執行數據修改時,都需要帶上版本號,如:1A,2B,3A。通過比較版本號可知是否有發生過操作,也就解決了ABA問題。
2. 未知的等待時長
因為CAS采取失敗重試的策略,所以不確定會發生多少次循環重試。如果在競爭激烈的環境下,其重試次數可能大幅增加。此時效率也就降低了。
總結
樂觀鎖、悲觀鎖是一種思想,CAS是樂觀鎖的一種實現。前者是非阻塞同步,非獨占,而后者是阻塞同步,獨占鎖。在可預知的情況下,如果競爭沖突發生較少,樂觀鎖是個不錯的選擇。而如果競爭激烈,悲觀鎖應得到考慮。
關於對象的創建分配內存,因為多線程分配對象空間並不安全,如分配A,B兩對象,當給A分配內存時,指針還沒修改,就切換到給B分配同一塊內存,引發錯誤。其中一種解決方法就是通過底層的CAS操作來保證分配的原子性。
如有錯誤,敬請斧正,以防誤導他人。
參考:http://www.importnew.com/20472.html