一 java線程同步原理
java中的同步使用到了 Monitor(管程)機制
java會為每個object對象分配一個monitor,當某個對象的同步方法(synchronized methods)被多個線程調用時,該對象的monitor將負責處理這些訪問的并發獨占要求。
當一個線程調用一個對象的同步方法時,JVM會檢查該對象的monitor。如果monitor沒有被占用,那么這個線程就得到了monitor的占有權,可以繼續執行該對象的同步方法;如果monitor被其他線程所占用,那么該線程將被掛起,直到monitor被釋放。
當線程退出同步方法調用時,該線程會釋放monitor,這將允許其他等待的線程獲得monitor以使對同步方法的調用執行下去。
注意:Java對象的monitor機制和傳統的臨界檢查代碼區技術不一樣。java的一個同步方法并不意味著同時只有一個線程獨占執行,但臨界檢查代碼區技術確實會保證同步方法在一個時刻只被一個線程獨占執行。Java的monitor機制的準確含義是:任何時刻,對一個指定object對象的某同步方法只能由一個線程來調用。
java對象的monitor是跟隨object實例來使用的,而不是跟隨程序代碼。兩個線程可以同時執行相同的同步方法,比如:一個類的同步方法是xMethod(),有a,b兩個對象實例,一個線程執行a.xMethod(),另一個線程執行b.xMethod().互不沖突。
二 synchronized 和 volatile 、ReentrantLock 的區別
Synchronized和volatile的比較
? 1)Synchronized保證內存可見性和操作的原子性,Volatile只能保證內存可見性。
? 2)volatile不需要加鎖,比Synchronized更輕量級,并不會阻塞線程(volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。)
? 4)volatile標記的變量不會被編譯器優化,而synchronized標記的變量可以被編譯器優化(如編譯器重排序的優化).
? 5)volatile是變量修飾符,僅能用于變量,而synchronized是一個方法或塊的修飾符。
ReentrantLock 和 Synchronized
1、可重入性:
2、鎖的實現:
3、性能的區別:
4、功能區別:
5、鎖的細粒度和靈活度
1、可重入性:
從名字上理解,ReenTrantLock的字面意思就是再進入的鎖,其實synchronized關鍵字所使用的鎖也是可重入的,兩者關于這個的區別不大。兩者都是同一個線程沒進入一次,鎖的計數器都自增1,所以要等到鎖的計數器下降為0時才能釋放鎖。
2、鎖的實現:
Synchronized是依賴于JVM實現的,而ReenTrantLock是JDK實現的,有什么區別,說白了就類似于操作系統來控制實現和用戶自己敲代碼實現的區別。前者的實現是比較難見到的,后者有直接的源碼可供閱讀。
3、性能的區別:
在Synchronized優化以前,synchronized的性能是比ReenTrantLock差很多的,但是自從Synchronized引入了偏向鎖,輕量級鎖(自旋鎖)后,兩者的性能就差不多了,在兩種方法都可用的情況下,官方甚至建議使用synchronized,其實synchronized的優化我感覺就借鑒了ReenTrantLock中的CAS技術。都是試圖在用戶態就把加鎖問題解決,避免進入內核態的線程阻塞。
4、功能區別:
便利性:很明顯Synchronized的使用比較方便簡潔,并且由編譯器去保證鎖的加鎖和釋放,而ReenTrantLock需要手工聲明來加鎖和釋放鎖,為了避免忘記手工釋放鎖造成死鎖,所以最好在finally中聲明釋放鎖。
5、鎖的細粒度和靈活度:很明顯ReenTrantLock優于Synchronized
ReenTrantLock獨有的能力:
1、ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先獲得鎖。
2、ReenTrantLock提供了一個Condition(條件)類,用來實現分組喚醒需要喚醒的線程們,而不是像synchronized要么隨機喚醒一個線程要么喚醒全部線程。
3、ReenTrantLock提供了一種能夠中斷等待鎖的線程的機制,通過lock.lockInterruptibly()來實現這個機制。
ReenTrantLock實現的原理:
在網上看到相關的源碼分析,本來這塊應該是本文的核心,但是感覺比較復雜就不一一詳解了,簡單來說,ReenTrantLock的實現是一種自旋鎖,通過循環調用CAS操作來實現加鎖。它的性能比較好也是因為避免了使線程進入內核態的阻塞狀態。想盡辦法避免線程進入內核的阻塞狀態是我們去分析和理解鎖設計的關鍵鑰匙。
什么情況下使用ReenTrantLock:
答案是,如果你需要實現ReenTrantLock的三個獨有功能時。
三 悲觀鎖樂觀鎖
悲觀鎖
總是假設最壞的情況,每次去拿數據的時候都認為別人會修改, 每次都加鎖
共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程
Java中synchronized和ReentrantLock等獨占鎖就是悲觀鎖思想的實現。
樂觀鎖
假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量
兩種鎖的使用場景
樂觀鎖適用于多讀少寫,即沖突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。
悲觀鎖適用于多寫少讀的情況,一般會經常產生沖突,這就會導致上層應用會不斷的進行retry,這樣反倒是降低了性能
樂觀鎖常見的兩種實現方式
CAS 和 版本號管理,
AtomicReference類計語CAS實現, 但會有 ABA 的問題, AtomicStampedReference 同時有cas 和版本號管理的實現
樂觀鎖(CAS)的思想是不加鎖,那不加鎖如何確保某一變量的操作沒有被其他線程修改過?
這里就需要CAS操作(CompareAndSwap)來實現。
CAS有三個操作參數:內存地址,期望值,要修改的新值,當期望值和內存當中的值進行比較不相等的時候,表示內存中的值已經被別線程改動過,這時候失敗返回,只有相等時,才會將內存中的值改為新的值,并返回成功。
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
可以看到AtomicLong.getAndIncrement()的實現就是通過CAS循環操作的實現,只有期望值與真實值相同情況下,CAS操作才會成功執行,退出循環,如果失敗則繼續自旋,直到成功。
ABA問題
ABA問題是指在CAS操作時,其他線程將變量值A改為了B,但是又被改回了A,等到本線程使用期望值A與當前變量進行比較時,發現變量A沒有變,于是CAS就將A值進行了交換操作,但是實際上該值已經被其他線程改變過,這與樂觀鎖的設計思想不符合。ABA問題的解決思路是,每次變量更新的時候把變量的版本號加1,那么A-B-A就會變成A1-B2-A3,只要變量被某一線程修改過,改變量對應的版本號就會發生遞增變化,從而解決了ABA問題。