一 java線程同步原理
java中的同步使用到了 Monitor(管程)機制
java會為每個object對象分配一個monitor晃危,當某個對象的同步方法(synchronized methods)被多個線程調用時,該對象的monitor將負責處理這些訪問的并發(fā)獨占要求震叮。
當一個線程調用一個對象的同步方法時,JVM會檢查該對象的monitor朴则。如果monitor沒有被占用钓简,那么這個線程就得到了monitor的占有權外邓,可以繼續(xù)執(zhí)行該對象的同步方法;如果monitor被其他線程所占用侦啸,那么該線程將被掛起丧枪,直到monitor被釋放。
當線程退出同步方法調用時忘闻,該線程會釋放monitor齐佳,這將允許其他等待的線程獲得monitor以使對同步方法的調用執(zhí)行下去债沮。
注意:Java對象的monitor機制和傳統(tǒng)的臨界檢查代碼區(qū)技術不一樣。java的一個同步方法并不意味著同時只有一個線程獨占執(zhí)行硅蹦,但臨界檢查代碼區(qū)技術確實會保證同步方法在一個時刻只被一個線程獨占執(zhí)行。Java的monitor機制的準確含義是:任何時刻海雪,對一個指定object對象的某同步方法只能由一個線程來調用饲宛。
java對象的monitor是跟隨object實例來使用的,而不是跟隨程序代碼幕庐。兩個線程可以同時執(zhí)行相同的同步方法,比如:一個類的同步方法是xMethod()瑟由,有a,b兩個對象實例冤寿,一個線程執(zhí)行a.xMethod()督怜,另一個線程執(zhí)行b.xMethod().互不沖突。
二 synchronized 和 volatile 号杠、ReentrantLock 的區(qū)別
Synchronized和volatile的比較
? 1)Synchronized保證內存可見性和操作的原子性姨蟋,Volatile只能保證內存可見性。
? 2)volatile不需要加鎖神得,比Synchronized更輕量級哩簿,并不會阻塞線程(volatile不會造成線程的阻塞酝静;synchronized可能會造成線程的阻塞。)
? 4)volatile標記的變量不會被編譯器優(yōu)化,而synchronized標記的變量可以被編譯器優(yōu)化(如編譯器重排序的優(yōu)化).
? 5)volatile是變量修飾符宗苍,僅能用于變量讳窟,而synchronized是一個方法或塊的修飾符敞恋。
ReentrantLock 和 Synchronized
1、可重入性:
2补箍、鎖的實現(xiàn):
3坑雅、性能的區(qū)別:
4、功能區(qū)別:
5终蒂、鎖的細粒度和靈活度
1遥诉、可重入性:
從名字上理解突那,ReenTrantLock的字面意思就是再進入的鎖,其實synchronized關鍵字所使用的鎖也是可重入的早龟,兩者關于這個的區(qū)別不大猫缭。兩者都是同一個線程沒進入一次猜丹,鎖的計數器都自增1,所以要等到鎖的計數器下降為0時才能釋放鎖藏杖。
2脉顿、鎖的實現(xiàn):
Synchronized是依賴于JVM實現(xiàn)的艾疟,而ReenTrantLock是JDK實現(xiàn)的,有什么區(qū)別弟疆,說白了就類似于操作系統(tǒng)來控制實現(xiàn)和用戶自己敲代碼實現(xiàn)的區(qū)別盗冷。前者的實現(xiàn)是比較難見到的正塌,后者有直接的源碼可供閱讀。
3帜羊、性能的區(qū)別:
在Synchronized優(yōu)化以前鸠天,synchronized的性能是比ReenTrantLock差很多的稠集,但是自從Synchronized引入了偏向鎖,輕量級鎖(自旋鎖)后痹籍,兩者的性能就差不多了晦鞋,在兩種方法都可用的情況下悠垛,官方甚至建議使用synchronized,其實synchronized的優(yōu)化我感覺就借鑒了ReenTrantLock中的CAS技術斤讥。都是試圖在用戶態(tài)就把加鎖問題解決湾趾,避免進入內核態(tài)的線程阻塞搀缠。
4、功能區(qū)別:
便利性:很明顯Synchronized的使用比較方便簡潔蛉艾,并且由編譯器去保證鎖的加鎖和釋放勿侯,而ReenTrantLock需要手工聲明來加鎖和釋放鎖缴罗,為了避免忘記手工釋放鎖造成死鎖,所以最好在finally中聲明釋放鎖兵钮。
5掘譬、鎖的細粒度和靈活度:很明顯ReenTrantLock優(yōu)于Synchronized
ReenTrantLock獨有的能力:
1、ReenTrantLock可以指定是公平鎖還是非公平鎖睦焕。而synchronized只能是非公平鎖靴拱。所謂的公平鎖就是先等待的線程先獲得鎖袜炕。
2、ReenTrantLock提供了一個Condition(條件)類耕突,用來實現(xiàn)分組喚醒需要喚醒的線程們眷茁,而不是像synchronized要么隨機喚醒一個線程要么喚醒全部線程纵诞。
3浙芙、ReenTrantLock提供了一種能夠中斷等待鎖的線程的機制,通過lock.lockInterruptibly()來實現(xiàn)這個機制纸俭。
ReenTrantLock實現(xiàn)的原理:
在網上看到相關的源碼分析南窗,本來這塊應該是本文的核心万伤,但是感覺比較復雜就不一一詳解了,簡單來說简珠,ReenTrantLock的實現(xiàn)是一種自旋鎖虹钮,通過循環(huán)調用CAS操作來實現(xiàn)加鎖。它的性能比較好也是因為避免了使線程進入內核態(tài)的阻塞狀態(tài)氧映。想盡辦法避免線程進入內核的阻塞狀態(tài)是我們去分析和理解鎖設計的關鍵鑰匙攘宙。
什么情況下使用ReenTrantLock:
答案是蹭劈,如果你需要實現(xiàn)ReenTrantLock的三個獨有功能時线召。
三 悲觀鎖樂觀鎖
悲觀鎖
總是假設最壞的情況缓淹,每次去拿數據的時候都認為別人會修改, 每次都加鎖
共享資源每次只給一個線程使用,其它線程阻塞料仗,用完后再把資源轉讓給其它線程
Java中synchronized和ReentrantLock等獨占鎖就是悲觀鎖思想的實現(xiàn)立轧。
樂觀鎖
假設最好的情況躏吊,每次去拿數據的時候都認為別人不會修改比伏,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據葛躏,可以使用版本號機制和CAS算法實現(xiàn)悠菜。樂觀鎖適用于多讀的應用類型李剖,這樣可以提高吞吐量
兩種鎖的使用場景
樂觀鎖適用于多讀少寫,即沖突真的很少發(fā)生的時候偶芍,這樣可以省去了鎖的開銷,加大了系統(tǒng)的整個吞吐量椎麦。
悲觀鎖適用于多寫少讀的情況观挎,一般會經常產生沖突段化,這就會導致上層應用會不斷的進行retry显熏,這樣反倒是降低了性能
樂觀鎖常見的兩種實現(xiàn)方式
CAS 和 版本號管理,
AtomicReference類計語CAS實現(xiàn), 但會有 ABA 的問題, AtomicStampedReference 同時有cas 和版本號管理的實現(xiàn)
樂觀鎖(CAS)的思想是不加鎖,那不加鎖如何確保某一變量的操作沒有被其他線程修改過缓升?
這里就需要CAS操作(CompareAndSwap)來實現(xiàn)港谊。
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()的實現(xiàn)就是通過CAS循環(huán)操作的實現(xiàn)湖笨,只有期望值與真實值相同情況下慈省,CAS操作才會成功執(zhí)行眠菇,退出循環(huán)捎废,如果失敗則繼續(xù)自旋缕坎,直到成功篡悟。
ABA問題
ABA問題是指在CAS操作時搬葬,其他線程將變量值A改為了B,但是又被改回了A急凰,等到本線程使用期望值A與當前變量進行比較時女仰,發(fā)現(xiàn)變量A沒有變,于是CAS就將A值進行了交換操作抡锈,但是實際上該值已經被其他線程改變過疾忍,這與樂觀鎖的設計思想不符合。ABA問題的解決思路是床三,每次變量更新的時候把變量的版本號加1一罩,那么A-B-A就會變成A1-B2-A3撇簿,只要變量被某一線程修改過聂渊,改變量對應的版本號就會發(fā)生遞增變化,從而解決了ABA問題四瘫。