java cas机制_Java CAS机制详解
CAS目的:
在多線程中為了保持數據的準確性,避免多個線程同時操作某個變量,很多情況下利用關鍵字synchronized實現同步鎖,使用synchronized關鍵字修可以使操作的線程排隊等待運行,可以說是一種悲觀策略,認為線程會修改數據,所以開始就把持有鎖的線程鎖住,其他線程只能是掛起狀態,等待鎖的釋放,所以同步鎖帶來了問題:
主要的效率問題:在線程執行的時候,獲得鎖的線程在運行,其他被掛起的線程只能等待著持有鎖的線程釋放鎖才有機會運行(現在JVM可能根據持有鎖的時間來操作線程是否是被掛起還是自旋等待),在效率上都浪費在等待上。很可能這種鎖是沒有必要的,其他線程沒有修改數據。在很多的線程切換的時候,由于有同步鎖,就要涉及到鎖的釋放,加鎖,這又是一個很大的時間開銷。這里涉及到操作系統上的知識,關于線程之間的切換(被掛起和恢復)中間還要經歷中斷,時間片等等。
上面說了這么多,現在我們追求的是一種效率高,還要保證數據的安全的一種方法。
與鎖(阻塞機制)的方式相比有一種更有效地方法,非阻塞機制,同步鎖帶來了線程執行時候之間的阻塞,而這種非阻塞機制在多個線程競爭同一個數據的時候不會發生阻塞的情況,這樣在時間上就可以節省出很多的時間。
想到這里,知道volatile的可能會想到用volatile,使用volatile不會造成阻塞,volatile保證了線程之間的內存可見性和程序執行的有序性可以說已經很好的解決了上面的問題,但是一個很重要的問題就是,volatile不能保證原子性,對于復合操作,例如i++這樣的程序包含三個原子操作:取指,增加,賦值。在《Java并發編程實戰》這本書上有這樣的一句話:變量的新值依賴于舊值時就不能使用volatile變量。實際上就是說的類似于i++這樣的操作。具體詳見:volatile關鍵字解析
什么是CAS:
現在采取的是CAS(Compare And Swap比較和交換)解決了volatile不能保證原子性。CAS通常比鎖定要快得多,但這取決于爭用的程度。因為如果讀取和比較之間的值發生變化,CAS可能會強制重試,所以不能說某個方法就是絕對的好。CAS的主要問題是編程比鎖定更困難。還好jdk提供了一些類用于完成基本的操作。
CAS主要包含三個操作數,內存位置V,進行比較的原值A,和新值B。當位置V的值與A相等時,CAS才會通過原子方式用新值B來更新V,否則不會進行任何操作。無論位置V的值是否等于A,都將返回V原有的值。通俗點說:我認為V地址的值應該是A,如果是,V地址的值更新為B,否則不修改并告訴V的值實際為多少(無論如何這個值都會通知到V)。上面說到了同步鎖 是一種悲觀策略,CAS是一種樂觀策略,每次都開放自己,不用擔心其他線程會修改變量等數據,如果其他線程修改了數據,那么CAS會檢測到并利用算法重新計算。CAS也是同時允許一個線程修改變量,其他的線程試圖修改都將失敗,但是相比于同步鎖,CAS對于失敗的線程不會將他們掛起,他們下次仍可以參加競爭,這也就是非阻塞機制的特點。
下面用代碼簡單的實現CAS原理:
/**
* Created with IDEA
*
* @author DuzhenTong
* @Date 2018/2/1
* @Time 11:52
*/
public class SimpleCAS {
private int value;
public SimpleCAS(int value) {
this.value = value;
}
public synchronized int get(){
return value;
}
public synchronized int compareAndSwap(int expectedValue, int newValue){
int oldValue = value;//獲取舊值
if(oldValue == expectedValue){//如果期望值與當前V位置的值相同就給予新值
value = newValue;
}
return oldValue;//返回V位置原有的值
}
public synchronized boolean compareAndSet(int expectedValue, int newValue){
return (expectedValue == compareAndSwap(expectedValue, newValue));
}
public static void main(String[] args) {
SimpleCAS simpleCAS = new SimpleCAS(3);
simpleCAS.compareAndSet(5, 10);
System.out.println(simpleCAS.get());//3
SimpleCAS simpleCAS1 = new SimpleCAS(1);
simpleCAS1.compareAndSet(1, 6);
System.out.println(simpleCAS1.get());//6
}
}
從運行結果可以看出代碼的原理:設置一個初始值(內存位置),期望值和新值進行比較,如果期望值和初始值一致,返回新值,否則返回初始值。意思是你在修改在一個變量A,假如它原來的值是3,所以你預期它是3,如果在你修改的時候,它被別的線程更新為5,那么就不符合你的預期,你的修改也不會生效
從Java5開始引入了底層的支持,在這之前需要開發人員編寫相關的代碼才可以實現CAS。在原子變量類Atomic***中(例如AtomicInteger、AtomicLong)可以看到CAS操作的代碼,在這里的代碼都是調用了底層(核心代碼調用native修飾的方法)的實現方法。在AtomicInteger源碼中可以看getAndSet方法和compareAndSet方法之間的關系,compareAndSet方法調用了底層的實現,該方法可以實現與一個volatile變量的讀取和寫入相同的效果。在前面說到了volatile不支持例如i++這樣的復合操作,在Atomic***中提供了實現該操作的方法。JVM對CAS的支持通過這些原子類(Atomic***)暴露出來,供我們使用。
CAS帶來的問題:
ABA問題:CAS在操作的時候會檢查變量的值是否被更改過,如果沒有則更新值,但是帶來一個問題,最開始的值是A,接著變成B,最后又變成了A。經過檢查這個值確實沒有修改過,因為最后的值還是A,但是實際上這個值確實已經被修改過了。為了解決這個問題,在每次進行操作的時候加上一個版本號,每次操作的就是兩個值,一個版本號和某個值,A——>B——>A問題就變成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference類解決ABA問題,用Pair這個內部類實現,包含兩個屬性,分別代表版本號和引用,在compareAndSet中先對當前引用進行檢查,再對版本號標志進行檢查,只有全部相等才更新值。
時間問題:看起來CAS比鎖的效率高,從阻塞機制變成了非阻塞機制,減少了線程之間等待的時間。每個方法不能絕對的比另一個好,在線程之間競爭程度大的時候,如果使用CAS,每次都有很多的線程在競爭,而鎖可以避免這些狀況,相反的情況,如果線程之間競爭程度小,使用CAS是一個很好的選擇。
總結
以上是生活随笔為你收集整理的java cas机制_Java CAS机制详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IM应用中如何计算富文本的高度
- 下一篇: matlab中除法的错误使用