參考文檔:http://www.importnew.com/19472.html
公平鎖和非公平鎖
公平鎖是指多個(gè)線程在等待同一個(gè)鎖時(shí),必須按照申請(qǐng)鎖的先后順序來(lái)一次獲得鎖。
公平鎖的好處是等待鎖的線程不會(huì)餓死,但是整體效率相對(duì)低一些;非公平鎖的好處是整體效率相對(duì)高一些,但是有些線程可能會(huì)餓死或者說(shuō)很早就在等待鎖,但要等很久才會(huì)獲得鎖。
自旋鎖和阻塞鎖
自旋鎖:是指當(dāng)線程獲取鎖失敗的時(shí)候,去執(zhí)行一個(gè)無(wú)意義的循環(huán),循環(huán)結(jié)束后再重新去競(jìng)爭(zhēng)鎖,如果競(jìng)爭(zhēng)不到則繼續(xù)循環(huán)。整個(gè)過(guò)程中線程一直處于運(yùn)行(running)狀態(tài)。
阻塞鎖:和自旋鎖相對(duì),指當(dāng)線程獲取鎖失敗時(shí),線程進(jìn)入阻塞(blocking)狀態(tài),當(dāng)獲取相應(yīng)的信號(hào)時(shí)(喚醒,時(shí)間),進(jìn)入線程的準(zhǔn)備就緒狀態(tài),準(zhǔn)備就緒狀態(tài)的所有線程,通過(guò)競(jìng)爭(zhēng),進(jìn)入運(yùn)行狀態(tài)。
自旋鎖的出現(xiàn)原因:線程的阻塞和喚醒需要CPU從用戶(hù)態(tài)轉(zhuǎn)為核心態(tài),頻繁的阻塞和喚醒對(duì)CPU來(lái)說(shuō)是一件負(fù)擔(dān)很重的工作。而且,很多對(duì)象鎖的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間,例如整數(shù)的自加操作,在很短的時(shí)間內(nèi)阻塞并喚醒線程顯然不值得,為此引入了自旋鎖。
適用情況:自旋等待不能代替阻塞。自旋等待本身雖然,但它是要占用處理器時(shí)間的,因此,如果鎖被占用的時(shí)間很短,自旋當(dāng)代的效果就會(huì)非常好,反之,如果鎖被占用的時(shí)間很長(zhǎng),那么自旋的線程只會(huì)白白浪費(fèi)處理器資源。
可重入鎖和不可重入鎖
可重入鎖:指的是同一線程外層函數(shù)獲得鎖之后 ,內(nèi)層遞歸函數(shù)仍然有獲取該鎖的代碼,但不受影響。即獲取鎖的粒度是線程而不是調(diào)用。
不可重入鎖:和可重入鎖相反,指的是同一線程外層函數(shù)獲得鎖之后,不能再次獲取該鎖。
可重入鎖最大的作用是避免死鎖。
在JAVA環(huán)境下 ReentrantLock 和synchronized 都是可重入鎖。
類(lèi)鎖和對(duì)象鎖
在Java程序運(yùn)行時(shí)環(huán)境中,JVM需要對(duì)兩類(lèi)線程共享的數(shù)據(jù)進(jìn)行協(xié)調(diào):
1)保存在堆中的實(shí)例變量
2)保存在方法區(qū)中的類(lèi)變量
為了實(shí)現(xiàn)監(jiān)視器的排他性監(jiān)視能力,java虛擬機(jī)為每一個(gè)對(duì)象和類(lèi)都關(guān)聯(lián)一個(gè)鎖。代表任何時(shí)候只允許一個(gè)線程擁有的特權(quán)。線程訪問(wèn)實(shí)例變量或者類(lèi)變量不需鎖。
類(lèi)鎖實(shí)際上用對(duì)象鎖來(lái)實(shí)現(xiàn)。當(dāng)虛擬機(jī)裝載一個(gè)class文件的時(shí)候,它就會(huì)創(chuàng)建一個(gè)java.lang.Class類(lèi)的實(shí)例。當(dāng)鎖住一個(gè)對(duì)象的時(shí)候,實(shí)際上鎖住的是那個(gè)類(lèi)的Class對(duì)象。
事實(shí)上,synchronized修飾非靜態(tài)方法、同步代碼塊的synchronized (this)用法和synchronized (非this對(duì)象)的用法鎖的是對(duì)象,線程想要執(zhí)行對(duì)應(yīng)同步代碼,需要獲得對(duì)象鎖。
synchronized修飾靜態(tài)方法以及同步代碼塊的synchronized (類(lèi).class)用法鎖的是類(lèi),線程想要執(zhí)行對(duì)應(yīng)同步代碼,需要獲得類(lèi)鎖。
悲觀鎖和樂(lè)觀鎖
悲觀鎖:假定會(huì)發(fā)生并發(fā)沖突,屏蔽一切可能違反數(shù)據(jù)完整性的操作。
樂(lè)觀鎖:假定不會(huì)發(fā)生并發(fā)沖突,只在提交操作時(shí)檢測(cè)是否違反數(shù)據(jù)完整性。(使用版本號(hào)或者時(shí)間戳來(lái)配合實(shí)現(xiàn))
共享鎖和排它鎖
共享鎖:如果事務(wù)T對(duì)數(shù)據(jù)A加上共享鎖后,則其他事務(wù)只能對(duì)A再加共享鎖,不能加排它鎖。獲準(zhǔn)共享鎖的事務(wù)只能讀數(shù)據(jù),不能修改數(shù)據(jù)。
排它鎖:如果事務(wù)T對(duì)數(shù)據(jù)A加上排它鎖后,則其他事務(wù)不能再對(duì)A加任何類(lèi)型的鎖。獲得排它鎖的事務(wù)即能讀數(shù)據(jù)又能修改數(shù)據(jù)。
讀寫(xiě)鎖
讀寫(xiě)鎖是一個(gè)資源能夠被多個(gè)讀線程訪問(wèn),或者被一個(gè)寫(xiě)線程訪問(wèn)但不能同時(shí)存在讀線程。Java當(dāng)中的讀寫(xiě)鎖通過(guò)ReentrantReadWriteLock實(shí)現(xiàn)。
互斥鎖
所謂互斥鎖就是指一次最多只能有一個(gè)線程持有的鎖。在JDK中synchronized和JUC的Lock就是互斥鎖。
偏向鎖、輕量級(jí)鎖和重量級(jí)鎖
synchronized的鎖可以分為偏向鎖、輕量級(jí)鎖以及重量級(jí)鎖,通過(guò)Java對(duì)象頭實(shí)現(xiàn)。
偏向鎖是JDK6中引入的一項(xiàng)鎖優(yōu)化,它的目的是消除數(shù)據(jù)在無(wú)競(jìng)爭(zhēng)情況下的同步原語(yǔ),進(jìn)一步提高程序的運(yùn)行性能。
偏向鎖會(huì)偏向于第一個(gè)獲得它的線程,如果在接下來(lái)的執(zhí)行過(guò)程中,該鎖沒(méi)有被其他的線程獲取,則持有偏向鎖的線程將永遠(yuǎn)不需要同步。大多數(shù)情況下,鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖。當(dāng)有另外一個(gè)線程去嘗試獲取這個(gè)鎖時(shí),偏向模式就宣告結(jié)束。
輕量級(jí)鎖:線程在執(zhí)行同步塊之前,JVM會(huì)先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中,官方稱(chēng)為Displaced Mark Word。然后線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,則自旋獲取鎖,當(dāng)自旋獲取鎖仍然失敗時(shí),表示存在其他線程競(jìng)爭(zhēng)鎖(兩條或兩條以上的線程競(jìng)爭(zhēng)同一個(gè)鎖),則輕量級(jí)鎖會(huì)膨脹成重量級(jí)鎖。
重量級(jí)鎖:在JVM中又叫對(duì)象監(jiān)視器(Monitor),它很像C中的Mutex,除了具備Mutex(0|1)互斥的功能,它還負(fù)責(zé)實(shí)現(xiàn)了Semaphore(信號(hào)量)的功能,也就是說(shuō)它至少包含一個(gè)競(jìng)爭(zhēng)鎖的隊(duì)列,和一個(gè)信號(hào)阻塞隊(duì)列(wait隊(duì)列),前者負(fù)責(zé)做互斥,后一個(gè)用于做線程同步。
整個(gè)synchronized鎖流程如下:
- 檢測(cè)Mark Word里面是不是當(dāng)前線程的ID,如果是,表示當(dāng)前線程處于偏向鎖
- 如果不是,則使用CAS將當(dāng)前線程的ID替換Mard Word,如果成功則表示當(dāng)前線程獲得偏向鎖,置偏向標(biāo)志位1
- 如果失敗,則說(shuō)明發(fā)生競(jìng)爭(zhēng),撤銷(xiāo)偏向鎖,進(jìn)而升級(jí)為輕量級(jí)鎖。
- 當(dāng)前線程使用CAS將對(duì)象頭的Mark Word替換為鎖記錄指針,如果成功,當(dāng)前線程獲得鎖
- 如果失敗,表示其他線程競(jìng)爭(zhēng)鎖,當(dāng)前線程便嘗試使用自旋來(lái)獲取鎖。
- 如果自旋成功則依然處于輕量級(jí)狀態(tài)。
- 如果自旋失敗,則升級(jí)為重量級(jí)鎖。
| 鎖 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場(chǎng)景 |
|---|---|---|---|
| 偏向鎖 | 加鎖和解鎖不需要額外的消耗,和執(zhí)行非同步方法比僅存在納秒級(jí)的差距 | 如果線程間存在鎖競(jìng)爭(zhēng),會(huì)帶來(lái)額外的鎖撤銷(xiāo)的消耗 | 適用于只有一個(gè)線程訪問(wèn)同步塊場(chǎng)景 |
| 輕量級(jí)鎖 | 競(jìng)爭(zhēng)的線程不會(huì)阻塞,提高了程序的響應(yīng)速度 | 如果始終得不到鎖競(jìng)爭(zhēng)的線程使用自旋會(huì)消耗CPU | 追求響應(yīng)時(shí)間,鎖占用時(shí)間很短 |
| 重量級(jí)鎖 | 線程競(jìng)爭(zhēng)不使用自旋,不會(huì)消耗CPU | 線程阻塞,響應(yīng)時(shí)間緩慢 | 追求吞吐量,鎖占用時(shí)間較長(zhǎng) |
系統(tǒng)優(yōu)化措施
鎖消除
鎖消除是虛擬機(jī)JIT在運(yùn)行時(shí),對(duì)一些代碼上要求同步,但是被檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行消除。鎖消除的主要判斷依據(jù)是來(lái)源于逃逸分析的數(shù)據(jù)支持,如果判斷在一段代碼中,堆上的所有數(shù)據(jù)都不會(huì)逃逸出去從而能被其他線程訪問(wèn)到,那就可以把他們當(dāng)做棧上數(shù)據(jù)對(duì)待,認(rèn)為他們是線程私有的,同步加鎖自然就無(wú)需進(jìn)行。
鎖粗化
原則上,我們?cè)诰帉?xiě)代碼的時(shí)候,總是推薦將同步塊的作用范圍限制的盡量小——只在共享數(shù)據(jù)的實(shí)際作用域中才進(jìn)行同步,這樣是為了使得需要同步的操作數(shù)量盡可能變小,如果存在鎖禁止,那等待的線程也能盡快拿到鎖。大部分情況下,這些都是正確的。但是,如果一些列的聯(lián)系操作都是同一個(gè)對(duì)象反復(fù)加上和解鎖,甚至加鎖操作是出現(xiàn)在循環(huán)體中的,那么即使沒(méi)有線程競(jìng)爭(zhēng),頻繁地進(jìn)行互斥同步操作也導(dǎo)致不必要的性能損耗。虛擬機(jī)探測(cè)到有這樣的情況的話,會(huì)把加鎖同步的范圍擴(kuò)展到整個(gè)操作序列的外部,這樣的一個(gè)鎖范圍擴(kuò)展的操作就稱(chēng)之為鎖粗化。