一、鎖機制
常用的鎖機制有兩種:
1、悲觀鎖:假定會發生並發沖突,屏蔽一切可能違反數據完整性的操作。悲觀鎖的實現,往往依靠底層提供的鎖機制;悲觀鎖會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。
2、樂觀鎖:假設不會發生並發沖突,每次不加鎖而是假設沒有沖突而去完成某項操作,只在提交操作時檢查是否違反數據完整性。如果因為沖突失敗就重試,直到成功為止。樂觀鎖大多是基於數據版本記錄機制實現。為數據增加一個版本標識,比如在基於數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來實現。讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大於數據庫表當前版本號,則予以更新,否則認為是過期數據。
樂觀鎖的缺點是不能解決臟讀的問題。
在實際生產環境里邊,如果並發量不大且不允許臟讀,可以使用悲觀鎖解決並發問題;但如果系統的並發非常大的話,悲觀鎖定會帶來非常大的性能問題,所以我們就要選擇樂觀鎖定的方法.
鎖機制存在以下問題:
(1)在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題。
(2)一個線程持有鎖會導致其它所有需要此鎖的線程掛起。
(3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先級倒置,引起性能風險。
二、CAS 操作
JDK 5之前Java語言是靠synchronized關鍵字保證同步的,這是一種獨占鎖,也是是悲觀鎖。java.util.concurrent(J.U.C)種提供的atomic包中的類,使用的是樂觀鎖,用到的機制就是CAS,CAS(Compare and Swap)有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。
現代的CPU提供了特殊的指令,允許算法執行讀-修改-寫操作,而無需害怕其他線程同時修改變量,因為如果其他線程修改變量,那么CAS會檢測它(並失敗),算法可以對該操作重新計算。而 compareAndSet() 就用這些代替了鎖定。
以AtomicInteger為例,研究在沒有鎖的情況下是如何做到數據正確性的。
- public class AtomicInteger extends Number implements java.io.Serializable {
- private volatile int value;
- public final int get() {
- return value;
- }
- public final int getAndIncrement() {
- for (;;) {
- int current = get();
- int next = current + 1;
- if (compareAndSet(current, next))
- return current;
- }
- }
- public final boolean compareAndSet(int expect, int update) {
- return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
- }
字段value需要借助volatile原語,保證線程間的數據是可見的(共享的)。這樣在獲取變量的值的時候才能直接讀取。然后來看看++i是怎么做到的。getAndIncrement采用了CAS操作,每次從內存中讀取數據然后將此數據和+1后的結果進行CAS操作,如果成功就返回結果,否則重試直到成功為止。而compareAndSet利用JNI來完成CPU指令的操作。
- public final boolean compareAndSet(int expect, int update) {
- return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
- }
整體的過程就是這樣子的,利用CPU的CAS指令,同時借助JNI來完成Java的非阻塞算法。其它原子操作都是利用類似的特性完成的。
而整個J.U.C都是建立在CAS之上的,因此對於synchronized阻塞算法,J.U.C在性能上有了很大的提升,是非阻塞的。
轉載:https://blog.csdn.net/heyutao007/article/details/19975665