網(wǎng)站建設(shè)商標(biāo)保護(hù)成都網(wǎng)站快速排名
目錄
1. 樂觀鎖和悲觀鎖
2. 輕量級鎖與重量級鎖
3. 自旋鎖和掛起等待鎖
4. 互斥鎖和讀寫鎖
5.?可重入鎖與不可重入鎖
6. 死鎖
6.1 死鎖的必要條件?
6.2 如何避免死鎖
7. 公平鎖和非公平鎖
8. Synchronized原理及加鎖過程
8.1?Synchronized 小結(jié)
8.2 加鎖工作過程?
8.2.1 偏向鎖
8.2.2 輕量級鎖
8.2.3 重量級鎖
9. 鎖優(yōu)化
9.1 鎖消除
9.2 鎖粗化?
1. 樂觀鎖和悲觀鎖
鎖的實(shí)現(xiàn)者,預(yù)測接下來的沖突概率(鎖競爭)大還是不大,然后根據(jù)這個(gè)沖突的概率,來決定接下來應(yīng)該怎么做.
樂觀鎖:預(yù)測接下來的沖突概率不大
悲觀鎖:預(yù)測接下來的沖突概率很大
通常來說~~悲觀鎖一般要做的工作更多一些,效率會更低一些~(并不絕對)
樂觀鎖做的工作會更少一點(diǎn),效率更高一點(diǎn).
2. 輕量級鎖與重量級鎖
輕量級鎖加鎖解鎖過程更快更高效.
重量級鎖加鎖解鎖過程更慢,更低效
和樂觀悲觀雖然不是一回事,但是確實(shí)有一定的重合~
一個(gè)樂觀鎖很可能也是一個(gè)輕量級鎖(不絕對)
一個(gè)悲觀鎖很可能也是一個(gè)重量級鎖(不絕對)
3. 自旋鎖和掛起等待鎖
自旋鎖是輕量級鎖的一種典型實(shí)現(xiàn)
掛起等待鎖是重量級鎖的一種典型實(shí)現(xiàn)舉例:追女朋友
自旋鎖:小白像一個(gè)女孩表白失敗,被拒絕(因?yàn)檫@女生有男朋友(被加鎖成功)).但是一直沒有放棄,每天都在追求,時(shí)刻不停歇.(這就是一個(gè)不停的等待鎖的過程,)當(dāng)這個(gè)女生分手了(解鎖),小白就會第一時(shí)間得到通知,競爭到鎖的機(jī)會就很大.
掛起等待鎖:小白選擇了另一種方式,小白暫時(shí)先不去追這個(gè)女生,等過一段時(shí)間來問問女生是否分手(解鎖),此時(shí)就是不能立馬得到通知,很有可能之前的鎖解除了,但是小白沒有拿到,被別人拿到了.優(yōu)點(diǎn)就是這段時(shí)間小白不用每天都問是否分手,可以集中注意力做一些別的事情,比如學(xué)習(xí)Java.哈哈哈.
補(bǔ)充:
針對上述三組策略,synchronized這把鎖屬于那種呢?
?synchronized既是悲觀鎖,也是樂觀鎖,既是輕量級鎖,也是重量級鎖,輕量級鎖部分基于自旋鎖實(shí)現(xiàn),重量級鎖部分基于掛起等待鎖實(shí)現(xiàn)~~?
如果鎖沖突不激烈以輕量級鎖/樂觀鎖的狀態(tài)運(yùn)行
如果鎖沖突激烈以重量級鎖/悲觀鎖的狀態(tài)運(yùn)行
4. 互斥鎖和讀寫鎖
synchronized,是互斥鎖~~?加鎖,就只是單純的加鎖,沒有更細(xì)化的區(qū)分了~~
像synchronized只有兩個(gè)操作:
1.進(jìn)入代碼塊加鎖
2.出了代碼塊解鎖~~
一個(gè)線程對于數(shù)據(jù)的訪問, 主要存在兩種操作: 讀數(shù)據(jù) 和 寫數(shù)據(jù).
- 兩個(gè)線程都只是讀一個(gè)數(shù)據(jù), 此時(shí)并沒有線程安全問題. 直接并發(fā)的讀取即可.
- 兩個(gè)線程都要寫一個(gè)數(shù)據(jù), 有線程安全問題.
- 一個(gè)線程讀另外一個(gè)線程寫, 也有線程安全問題.
讀寫鎖:?是把讀操作和寫操作區(qū)分對待. Java 標(biāo)準(zhǔn)庫提供了 ReentrantReadWriteLock 類, 實(shí)現(xiàn)了讀寫鎖.?
- ReentrantReadWriteLock.ReadLock 類表示一個(gè)讀鎖. 這個(gè)對象提供了 lock / unlock 方法進(jìn)行加鎖解鎖.
- ReentrantReadWriteLock.WriteLock 類表示一個(gè)寫鎖. 這個(gè)對象也提供了 lock / unlock 方法進(jìn)行加鎖解鎖.
讀寫鎖約定:
- 讀加鎖和讀加鎖之間, 不互斥.(沒鎖競爭)
- 寫加鎖和寫加鎖之間, 互斥.(有鎖競爭)
- 讀加鎖和寫加鎖之間, 互斥.?(有鎖競爭)
生活場景應(yīng)用
讀寫鎖特別適合于 "頻繁讀, 不頻繁寫" 的場景中. (這樣的場景其實(shí)也是非常廣泛存在的).
比如教務(wù)系統(tǒng)?
5.?可重入鎖與不可重入鎖
可重入鎖.:如果一個(gè)鎖在一個(gè)線程中,連續(xù)對該鎖咔咔加鎖兩次不死鎖,就叫做可重入鎖.即允許同一個(gè)線程多次獲取同一把鎖。
不可重入鎖:如果死鎖了,就叫不可重入鎖~~
Java里只要以Reentrant開頭命名的鎖都是可重入鎖,而且JDK提供的所有現(xiàn)成的Lock實(shí)現(xiàn)類,包括synchronized關(guān)鍵字鎖都是可重入的.
6. 死鎖
????????死鎖是這樣一種情形:多個(gè)線程同時(shí)被阻塞,它們中的一個(gè)或者全部都在等待某個(gè)資源被釋放。由于線程被無限期地阻塞,因此程序不可能正常終止。
?
死鎖的情況:
1. 一個(gè)線程,一把鎖,可重入鎖不會發(fā)生死鎖,不可重入鎖發(fā)生死鎖
2. 兩個(gè)線程,即使是可重入鎖也有可能會發(fā)生死鎖
3. N個(gè)線程M把鎖就非常容易死鎖.
6.1 死鎖的必要條件?
?死鎖的必要條件:(缺一不可)
- 1.互斥使用.一個(gè)線程拿到一把鎖之后,另一個(gè)線程不能使用.(鎖的基本特點(diǎn))
- 2.不可搶占.一個(gè)線程拿到鎖,只能自己主動(dòng)釋放,不能是被其他線程強(qiáng)行占有~~[挖墻腳是不行滴](鎖的基本特點(diǎn))
- 3.請求和保持."吃著碗里的,惦記鍋里的”追到了1號女神之后,又對2號女神躍躍欲試,但是此時(shí)是絕不會放棄1號女神的~~(代碼的特點(diǎn))
- 4.循環(huán)等待.上面例子中的情況,邏輯依賴循環(huán)的.“鑰匙鎖車?yán)锪?#xff0c;車鑰匙鎖家里了”(代碼的特點(diǎn))
6.2 如何避免死鎖
死鎖是個(gè)比較嚴(yán)重的bug.實(shí)踐中如何避免出現(xiàn)死鎖呢?
一個(gè)簡單有效的辦法:破解循環(huán)等待這個(gè)條件~~
針對鎖進(jìn)行編號,如果需要同時(shí)獲取多把鎖,約定加鎖順序,務(wù)必是先對小的編號加鎖后對大的編號加鎖~~如果此時(shí)約定,先加鎖小的編號,后加鎖大的編號,此時(shí)只要所有線程都遵守這個(gè)順序就行了!
7. 公平鎖和非公平鎖
????????假設(shè)三個(gè)線程 A, B, C.? A 先嘗試獲取鎖, 獲取成功. 然后 B 再嘗試獲取鎖, 獲取失敗, 阻塞等待; 然后C 也嘗試獲取鎖, C 也獲取失敗, 也阻塞等待.當(dāng)線程 A 釋放鎖的時(shí)候, 會發(fā)生啥呢?
公平鎖: 遵守 "先來后到". B 比 C 先來的. 當(dāng) A 釋放鎖的之后, B 就能先于 C 獲取到鎖.
非公平鎖: 不遵守 "先來后到". B 和 C 都有可能獲取到鎖.操作系統(tǒng)內(nèi)部的線程調(diào)度就可以視為是隨機(jī)的. 如果不做任何額外的限制, 鎖就是非公平鎖. 如果要想實(shí)現(xiàn)公平鎖, 就需要依賴額外的數(shù)據(jù)結(jié)構(gòu)(隊(duì)列), 來記錄線程們的先后順序.公平鎖和非公平鎖沒有好壞之分, 關(guān)鍵還是看適用場景.
synchronized 是非公平鎖.
8. Synchronized原理及加鎖過程
8.1?Synchronized 小結(jié)
?8.2 加鎖工作過程?
? ? ? ? ? ?????????
8.2.1 偏向鎖
1) 偏向鎖
????????第一個(gè)嘗試加鎖的線程, 優(yōu)先進(jìn)入偏向鎖狀態(tài).偏向鎖不是真的 "加鎖", 只是給對象頭中做一個(gè) "偏向鎖的標(biāo)記", 記錄這個(gè)鎖屬于哪個(gè)線程.如果后續(xù)沒有其他線程來競爭該鎖, 那么就不用進(jìn)行其他同步操作了(避免了加鎖解鎖的開銷),如果后續(xù)有其他線程來競爭該鎖(剛才已經(jīng)在鎖對象中記錄了當(dāng)前鎖屬于哪個(gè)線程了, 很容易識別當(dāng)前申請鎖的線程是不是之前記錄的線程), 那就取消原來的偏向鎖狀態(tài), 進(jìn)入一般的輕量級鎖狀態(tài).偏向鎖本質(zhì)上相當(dāng)于 "延遲加鎖" . 能不加鎖就不加鎖, 盡量來避免不必要的加鎖開銷.但是該做的標(biāo)記還是得做的, 否則無法區(qū)分何時(shí)需要真正加鎖.
8.2.2 輕量級鎖
2) 輕量級鎖
隨著其他線程進(jìn)入競爭, 偏向鎖狀態(tài)被消除, 進(jìn)入輕量級鎖狀態(tài)(自適應(yīng)的自旋鎖).此處的輕量級鎖就是通過 CAS 來實(shí)現(xiàn).(后續(xù)會總結(jié)這個(gè)CAS(先理解為比較內(nèi)存和寄存器的值,相同就更新忙不相同就修改為內(nèi)存的值之后再對數(shù)據(jù)進(jìn)行操作))
- 通過 CAS 檢查并更新一塊內(nèi)存 (比如 null => 該線程引用)
- 如果更新成功, 則認(rèn)為加鎖成功
- 如果更新失敗, 則認(rèn)為鎖被占用, 繼續(xù)自旋式的等待(并不放棄 CPU).
自旋操作是一直讓 CPU 空轉(zhuǎn), 比較浪費(fèi) CPU 資源.
因此此處的自旋不會一直持續(xù)進(jìn)行, 而是達(dá)到一定的時(shí)間/重試次數(shù), 就不再自旋了.也就是所謂的 "自適應(yīng)"
8.2.3 重量級鎖
3) 重量級鎖
如果競爭進(jìn)一步激烈, 自旋不能快速獲取到鎖狀態(tài), 就會膨脹為重量級鎖此處的重量級鎖就是指用到內(nèi)核提供的 mutex .
- 執(zhí)行加鎖操作, 先進(jìn)入內(nèi)核態(tài).
- 在內(nèi)核態(tài)判定當(dāng)前鎖是否已經(jīng)被占用
- 如果該鎖沒有占用, 則加鎖成功, 并切換回用戶態(tài).
- 如果該鎖被占用, 則加鎖失敗. 此時(shí)線程進(jìn)入鎖的等待隊(duì)列, 掛起. 等待被操作系統(tǒng)喚醒.
- 經(jīng)歷了一系列的滄海桑田, 這個(gè)鎖被其他線程釋放了, 操作系統(tǒng)也想起了這個(gè)掛起的線程, 于是喚醒這個(gè)線程, 嘗試重新獲取鎖.
9. 鎖優(yōu)化
9.1 鎖消除
編譯器+JVM 判斷鎖是否可消除. 如果可以, 就直接消除.
什么是 "鎖消除"
有些應(yīng)用程序的代碼中, 用到了 synchronized, 但其實(shí)沒有在多線程環(huán)境下. (例如 StringBuffer))StringBuffer sb = new StringBuffer(); sb.append("a"); sb.append("b"); sb.append("c"); sb.append("d");
此時(shí)每個(gè) append 的調(diào)用都會涉及加鎖和解鎖. 但如果只是在單線程中執(zhí)行這個(gè)代碼, 那么這些加
鎖解鎖操作是沒有必要的, 白白浪費(fèi)了一些資源開銷.補(bǔ)充:StringBuffer相對于StringBuilder是相對線程安全的,因?yàn)镾tringBuffer把關(guān)鍵方法都加上了Synchronized關(guān)鍵字,但是不是絕對線程安全,看代碼怎么寫.
9.2 鎖粗化?
鎖粗化
一段邏輯中如果出現(xiàn)多次加鎖解鎖, 編譯器 + JVM 會自動(dòng)進(jìn)行鎖的粗化.?
舉例: