synchronized的三種形式
- 對于普通的同步方法, 鎖的是當(dāng)前的實(shí)例對象
- 對于靜態(tài)的同步方法, 鎖的是當(dāng)前的類對象
- 對于同步方法塊, 鎖的是給定傳入的對象(類或?qū)嵗?
synchronized的實(shí)現(xiàn)原理
同步塊中的實(shí)現(xiàn)
同步塊中, JVM的實(shí)現(xiàn)是以
monitorenter
和monitorexit
指令配對實(shí)現(xiàn)的.
當(dāng)進(jìn)入monitorenter
指令 時將嘗試獲取當(dāng)前對象的 monitor的持有權(quán),如果被其他線程占用而無法持有,當(dāng)前線程將被阻塞.monitorenter
指令執(zhí)行結(jié)束,則表示當(dāng)前線程擁有了該對象的monitor持有權(quán).而monitorexit
則表示釋放monitor的持有權(quán),即釋放了鎖.
// 源碼
void lockBlock() {
synchronized (Main.class) {
}
}
# 反匯編
void lockBlock();
descriptor: ()V
flags: (0x0000)
Code:
stack=2, locals=3, args_size=1
0: ldc #2
2: dup
3: astore_1
4: monitorenter # 進(jìn)入同步塊
5: aload_1
6: monitorexit # 正常退出同步塊
7: goto 15
10: astore_2
11: aload_1
12: monitorexit # 異常退出同步塊
13: aload_2
14: athrow
15: return
同步方法中的實(shí)現(xiàn)
靜態(tài)同步方法和普通同步方法,是通過方法表結(jié)構(gòu)中的訪問標(biāo)識
access_flag
設(shè)置為ACC_SYNCHRONIZED
來實(shí)現(xiàn)的.
當(dāng)方法調(diào)用時拓颓,調(diào)用指令將會 檢查方法的 ACC_SYNCHRONIZED 訪問標(biāo)志是否被設(shè)置蔼水,如果設(shè)置了,執(zhí)行線程將先持有monitor丢间, 然后再執(zhí)行方法姜挺,最后再方法完成(無論是正常完成還是非正常完成)時釋放monitor。
// 源碼
synchronized void lockMethod() {
}
# 反匯編
synchronized void lockMethod();
descriptor: ()V
flags: (0x0020) ACC_SYNCHRONIZED
Code:
stack=0, locals=1, args_size=1
0: return
synchronized使用注意事項(xiàng)
- 普通方法與同步方法調(diào)用互不關(guān)聯(lián)
當(dāng)一個線程進(jìn)入同步方法時,其他線程可以正常訪問其他非同步方法
- 所有同步方法同時只能被一個線程訪問
當(dāng)一個線程執(zhí)行同步方法時咧党,其他線程不能訪問該對象的任何同步方法
- 同一個鎖的同步代碼塊同一時刻只能被一個線程訪問
當(dāng)同步代碼塊都是同一個鎖時,方法可以被所有線程訪問陨亡,但同一個鎖的 同步代碼塊同一時刻只能被一個線程訪問.
void lockBlock() {
// todo... 這里所有的線程都能進(jìn)入
synchronized (Main.class) {
// todo... 同步代碼塊中,只有持有鎖的線程能進(jìn)入
}
}
- 線程間同時訪問同一個鎖的多個同步代碼的執(zhí)行順序不定
線程間同時訪問同一個鎖多個同步方法的執(zhí)行順序不定傍衡,即使是使用同一個對象鎖,因?yàn)橥椒椒ńY(jié)束后,鎖被釋放,所有線程都有可能競爭到鎖.
class Demo {
@Synchronized
fun synchronizedMethod1(name: String) {
TimeUnit.MILLISECONDS.sleep(100)
println("$name : synchronizedMethod1")
}
@Synchronized
fun synchronizedMethod2(name: String) {
TimeUnit.MILLISECONDS.sleep(100)
println("$name : synchronizedMethod2")
}
}
fun main(args: Array<String>) {
val demo = Demo()
thread {
demo.synchronizedMethod1(Thread.currentThread().name)
demo.synchronizedMethod2(Thread.currentThread().name)
}
thread {
demo.synchronizedMethod2(Thread.currentThread().name)
demo.synchronizedMethod1(Thread.currentThread().name)
}
}
// ##########
Thread-0 : synchronizedMethod1
Thread-1 : synchronizedMethod1
Thread-1 : synchronizedMethod2
Thread-0 : synchronizedMethod2
- 不同鎖之間訪問非阻塞
對于鎖類對象和鎖實(shí)例對象的鎖以及不同對象實(shí)例的鎖,都互不相關(guān)的鎖负蠕,相互之間不會有任何影響
- 同步塊鎖基本變量或者字面量時,變量的值改變,鎖的對象也會發(fā)生改變.
fun method() {
var lock = 1
synchronized(lock) {
// 如果此時 lock的值在外部被修改,如lock=2,值被修改成了2
// 即使當(dāng)前同步方法沒有執(zhí)行完,其他等待的線程可以進(jìn)入,
// 因?yàn)榇藭r鎖變成了 2 , 與之前的 1, 已經(jīng)不是一個鎖了
}
}
synchronized 特點(diǎn)
- 獨(dú)占性(互斥鎖)
synchronized
是屬于線程獨(dú)占類型的鎖,一個線程拿到對象的鎖后,其他線程試圖進(jìn)入對象鎖的臨界資源時蛙埂,將會處于阻塞狀態(tài).
如果 A方法中 去獲取B的鎖, B方法中去獲取A的鎖,會導(dǎo)致A,B相互等待,形成死鎖.
- 可重入性
當(dāng)一個線程再次請求自己持有對象鎖的臨界資源時,不需要通過鎖競爭,請求將會成功,這種情況屬于重入鎖.
因此在一個線程調(diào)用synchronized方法的同時在其方法體內(nèi)部調(diào)用該對象另一個synchronized方法遮糖,是允許的
- 非公平性
公平鎖 : 多個線程在等待同一個鎖時绣的,必須按照申請鎖的時間順序排隊(duì)等待.(cpu吞吐量較低)
非公平鎖 : 則不保證這點(diǎn),在鎖釋放時欲账,任何一個等待鎖的線程都有機(jī)會獲得鎖被辑。(cpu吞吐量高,極端情況下,某些等待線程將永遠(yuǎn)無法獲取到鎖)
- 不可中斷性
如果一個代碼塊被synchronized修飾了,當(dāng)一個線程獲取了對應(yīng)的鎖敬惦,并執(zhí)行該代碼塊時盼理,其他線程便只能一直等待,等待獲取鎖的線程釋放鎖.
等待的線程將不可被中斷(interrupt方法也無法生效),只有兩種情況下能釋放鎖.
- 獲取鎖的線程執(zhí)行完了該代碼塊俄删,然后線程釋放對鎖的占有
- 線程執(zhí)行發(fā)生異常宏怔,此時JVM會讓線程自動釋放鎖
jvm對synchronized的優(yōu)化
JDK1.5中,synchronized是性能低效的畴椰。因?yàn)檫@是一個重量級操作臊诊,它對性能最大的影響是阻塞的是實(shí)現(xiàn),掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成斜脂,這些操作給系統(tǒng)的并發(fā)性帶來了很大的壓力.
到了JDK1.6抓艳,發(fā)生了變化,對synchronized加入了很多優(yōu)化措施帚戳,有偏向鎖,輕量級鎖玷或,鎖消除,鎖升級片任,等等偏友。導(dǎo)致synchronized的效率大大的提升.
鎖的狀態(tài)總共有四種,無鎖狀態(tài)对供、偏向鎖位他、輕量級鎖和重量級鎖。隨著鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖鹅髓,再升級的重量級鎖舞竿,但是鎖的升級是單向的,也就是說只能從低到高升級窿冯,不會出現(xiàn)鎖的降級.
對象頭
synchronized用的鎖是存在Java對象頭里的炬灭。如果對象是數(shù)組類型,則虛擬機(jī)用3個字寬(Word)存儲對象頭靡菇,如果對象是非數(shù)組類型,則用2字寬存儲對象頭米愿。在32位虛擬機(jī)中厦凤,1字寬等于4字節(jié),即32bit.
在運(yùn)行期間育苟,Mark Word里存儲的數(shù)據(jù)會隨著鎖標(biāo)志位的變化而變化较鼓。Mark Word可能變
化為存儲以下4種數(shù)據(jù):
偏向鎖
在大多數(shù)情況下,鎖不僅不存在多線程競爭违柏,而且總是由同一線程多次獲得博烂,因此為了減少同一線程獲取鎖(會涉及到一些CAS操作,耗時)的代價而引入偏向鎖。
偏向鎖的核心思想是漱竖,如果一個線程獲得了鎖禽篱,那么鎖就進(jìn)入偏向模式,此時Mark Word 的結(jié)構(gòu)也變?yōu)槠蜴i結(jié)構(gòu)馍惹,當(dāng)這個線程再次請求鎖時躺率,無需再做任何同步操作 ,即獲取鎖的過程万矾,這樣就省去了大量有關(guān)鎖申請的操作悼吱,從而也就提供程序的性能。
對于鎖競爭比較激烈的場合良狈,偏向鎖將失效,這種情況下可能每次申請鎖的線程都是不相同的.偏向鎖失效后,會膨脹為輕量級鎖.
輕量級鎖
倘若偏向鎖失敗后添,虛擬機(jī)并不會立即升級為重量級鎖,它還會嘗試使用一種稱為輕量級鎖的優(yōu)化手段薪丁,此時Mark Word 的結(jié)構(gòu)也變?yōu)檩p量級鎖的結(jié)構(gòu)遇西。
線程嘗試使用CAS來講對象頭中的 Mark Word 替換為指向鎖記錄的指針.如果成功,表示當(dāng)前線程獲取到鎖,如果失敗,則表示其他線程正在競爭鎖,當(dāng)前線程嘗試使用自旋來獲取鎖(空循環(huán)50或100次),如果再失敗將膨脹值 重量級鎖.
即自旋+CAS操作來嘗試修改鎖標(biāo)志,成功則獲取到鎖,失敗則升級為重量級鎖.
鎖消除
消除鎖是虛擬機(jī)另外一種鎖的優(yōu)化,這種優(yōu)化更徹底严嗜,Java虛擬機(jī)在JIT編譯時(可以簡單理解為當(dāng)某段代碼即將第一次被執(zhí)行時進(jìn)行編譯努溃,又稱即時編譯),通過對運(yùn)行上下文的掃描阻问,去除不可能存在共享資源競爭的鎖梧税,通過這種方式消除沒有必要的鎖,可以節(jié)省毫無意義的請求鎖時間.
synchronized缺點(diǎn)
- 只能進(jìn)行互斥操作,不能實(shí)現(xiàn)共享鎖,在某些場景下效率低.比如對只讀數(shù)據(jù)的操作.
- 等待同步線程的不可中斷.
- 沒有超時獲取鎖
- 只能實(shí)現(xiàn)非公平鎖,無法實(shí)現(xiàn)公平鎖.
參考
CAS(Compare and Swap))
比較和交換. 需要輸入新值和舊值;如果內(nèi)存中的值與舊值相同,則內(nèi)存中的值更新為新值,否則不更新.
如果當(dāng)前狀態(tài)值等于預(yù)期值,則以原子方式將同步狀態(tài)設(shè)置為給定的更新值.
CAS操作具有volatile讀和寫的語義.
java中主要通過 UnSafe
類的compareAndSwap
系列方法來實(shí)現(xiàn),UnSafe
是系統(tǒng)保護(hù)類,不能直接使用,使用時,需要通過反射調(diào)用.
cas 將在處理器中添加
LOCK_
前綴的指令. 來保證執(zhí)行的原子性,禁止重排序,和刷新主內(nèi)存數(shù)據(jù).
三個問題 : ABA, 循環(huán)開銷大, 只保證一個共享變量的原子操作.