Java中的synchronized重量級(jí)鎖解析
1. 背景
在JDK1.6以前,synchronized 的工作方式都是這種重量級(jí)的鎖。它的實(shí)現(xiàn)原理就是利用 kernel 中的互斥量,mutex。主要是內(nèi)核中的mutex 能夠保證它是一個(gè)互斥的量。如果線程1拿到了 mutex,那么線程2就拿不到了。這是內(nèi)核幫我們保證的。
至于為什么可以,可以去了解一下內(nèi)核中的互斥量。
2. 為啥叫做重量級(jí)鎖
內(nèi)核需要去申請(qǐng)這個(gè)互斥量,必須要進(jìn)入內(nèi)核態(tài)。也就是這里需要用戶態(tài),內(nèi)核態(tài)的切換。狀態(tài)的切換,開銷是比較大的。這就是重型鎖的一個(gè)弊端。對(duì)于重型鎖的一些主要的封裝都是在 c 語(yǔ)言中的 pthread 這樣一個(gè)庫(kù)中,比如mutex_init, mutex_lock 等。之所以叫重量級(jí)鎖,就是因?yàn)樾枰M(jìn)入到內(nèi)核態(tài)。
3. 能否在用戶態(tài)實(shí)現(xiàn)一把互斥鎖
我們不需要進(jìn)入到內(nèi)核態(tài)就能夠獲取到這樣的一把鎖,也就是在用戶態(tài)就可以實(shí)現(xiàn)一把鎖。
比如說(shuō),現(xiàn)在就有一把鎖,就叫做 state。0:表示未被使用,1 表示鎖被占用了。
lock 的實(shí)現(xiàn)
如果鎖當(dāng)前是被占用的狀態(tài),那么程序就一直死循環(huán)。如果某個(gè)時(shí)刻,有一個(gè)線程把鎖釋放了,那么就退出死循環(huán),執(zhí)行下一行 state = 1。而且持有者=當(dāng)前線程。也就是 state = 0的時(shí)候,就可以任務(wù)當(dāng)前線程可以拿到這把鎖了。
unlock 的實(shí)現(xiàn)
釋放鎖的時(shí)候,首先需要判斷一下當(dāng)前持有鎖的線程是不是當(dāng)前線程。如果是當(dāng)前線程,那么就將 state 置為0, 持有者 = null。
當(dāng)前設(shè)計(jì)方式存在的問(wèn)題
while(state==1); state = 1;其實(shí)可以認(rèn)為這個(gè)是比較賦值倆步操作,先比較,符合條件了,再進(jìn)行賦值。那么其實(shí)這不是原子性的。比如說(shuō)在比較的時(shí)候,倆個(gè)線程同時(shí)進(jìn)行了比較,這倆個(gè)線程同時(shí)發(fā)現(xiàn)state = 0,那么這倆個(gè)線程同時(shí)都去會(huì)執(zhí)行 state = 1的操作,這倆個(gè)線程都認(rèn)為自己拿到了鎖,那么這個(gè)就產(chǎn)生了并發(fā)的問(wèn)題了。
如何改進(jìn)這個(gè)問(wèn)題呢?
我們看到比較和賦值不是原子性的,在軟件層面,我們也無(wú)法保證這倆步的原子性。所以,計(jì)算機(jī)給我們提供了原語(yǔ),也就是 CAS。=
那么我們來(lái)看一下如何改進(jìn)呢? 我們把比較賦值這倆步操作,變成了一個(gè)原語(yǔ)操作,CAS。比較并交換。CAS中有三個(gè)參數(shù),cas(state,0,1)。比較state的當(dāng)前值是不是0,如果是0,那么就賦值為1。
再看看一下 unlock的操作。 在unlock操作中,其實(shí)也是一個(gè)比較賦值的操作,首先判斷當(dāng)前持有者是不是當(dāng)前線程,如果是,再進(jìn)行賦值。
那為什么這里不需要用cas呢?因?yàn)楸囟ㄊ浅钟墟i的線程才能執(zhí)行到下一行。
而且賦值操作中,先賦值 持有者 = null。再賦值 state = 0。
因?yàn)槿绻葓?zhí)行 state = 0, 那么就相當(dāng)于先釋放掉這把鎖了,另外一個(gè)線程就會(huì)執(zhí)行 lock 成功,拿到鎖,持有者 = 當(dāng)前線程。
那么就可能會(huì)帶來(lái)一些問(wèn)題。因?yàn)榇藭r(shí)釋放掉的鎖并不是當(dāng)前線程持有的鎖。
這種鎖也叫做自旋鎖。
自旋鎖的優(yōu)點(diǎn)與缺點(diǎn)
自旋鎖,spinLock。自旋鎖不需要進(jìn)入到內(nèi)核態(tài),整個(gè)程序的執(zhí)行都是在用戶態(tài)的,包括cas。但是cas不是計(jì)算機(jī)的原語(yǔ)嗎?因?yàn)橛?jì)算機(jī)的指令和用戶態(tài),內(nèi)核態(tài)沒(méi)有關(guān)系。
自旋鎖當(dāng)然也有缺點(diǎn)。 lock 操作如果一直沒(méi)有拿到鎖的話,會(huì)一直嘗試。這是需要消耗cpu資源的。 所以自旋鎖對(duì)于鎖競(jìng)爭(zhēng)比較激烈的情況下,是不適用的。
總結(jié)
mutex鎖和自旋鎖各有優(yōu)缺點(diǎn),那么我們能不能把這倆者結(jié)合一下呢?
JDK 1.4 以前是借助內(nèi)核中的 Mutex 互斥量; JDK1.4 以后是利用自旋鎖,自旋n次以后,還是沒(méi)有拿到鎖的話,就切換到mutex。
也就是將自旋鎖和mutex進(jìn)行了一個(gè)結(jié)合。因?yàn)閙utex 加鎖失敗以后,會(huì)掛起,讓出cpu資源。這樣的話,算是對(duì)資源的一個(gè)合理利用。
JDK1.4以前,我們是可以設(shè)置自旋次數(shù)的,但是1.6以后,JDK可以自適應(yīng)自旋,不用設(shè)置這個(gè)參數(shù)了。
當(dāng)然現(xiàn)在我們所說(shuō)的都是重量級(jí)鎖。包括mutext, 自旋鎖,自適應(yīng)自旋鎖。
到此這篇關(guān)于Java中的synchronized重量級(jí)鎖解析的文章就介紹到這了,更多相關(guān)synchronized重量級(jí)鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot如何實(shí)現(xiàn)定時(shí)任務(wù)的動(dòng)態(tài)增刪啟停詳解
這篇文章主要給大家介紹了關(guān)于Spring Boot如何實(shí)現(xiàn)定時(shí)任務(wù)的動(dòng)態(tài)增刪啟停的相關(guān)資料,文中通過(guò)示例代碼以及圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07JAVA實(shí)現(xiàn)Excel和PDF上下標(biāo)的操作代碼
這篇文章主要介紹了JAVA實(shí)現(xiàn)Excel和PDF上下標(biāo),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09Java數(shù)據(jù)結(jié)構(gòu)中關(guān)于AVL樹的實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)中關(guān)于AVL樹的實(shí)現(xiàn)方法,AVL樹是高度平衡的二叉樹,它的特點(diǎn)是AVL樹中任何節(jié)點(diǎn)的兩個(gè)子樹的高度最大差別為1,本文主要給大家介紹了Java語(yǔ)言如何實(shí)現(xiàn)AVL樹,需要的朋友可以參考下2024-02-02java利用pdfbox+poi往pdf插入數(shù)據(jù)
這篇文章主要給大家介紹了關(guān)于java利用pdfbox+poi如何往pdf插入數(shù)據(jù)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02chatgpt java環(huán)境調(diào)用源碼實(shí)現(xiàn)demo
這篇文章主要介紹了chatgpt java環(huán)境調(diào)用源碼實(shí)現(xiàn)demo,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02springboot整合redisson實(shí)現(xiàn)延時(shí)隊(duì)列(附倉(cāng)庫(kù)地址)
延時(shí)隊(duì)列用于管理需要定時(shí)執(zhí)行的任務(wù),對(duì)于大數(shù)據(jù)量和高實(shí)時(shí)性需求,使用延時(shí)隊(duì)列比定時(shí)掃庫(kù)更高效,Redisson提供一種高效的延時(shí)隊(duì)列實(shí)現(xiàn)方式,本文就來(lái)詳細(xì)的介紹一下,感興趣都可以了解學(xué)習(xí)2024-10-10Eclipse遠(yuǎn)程debug的步驟與注意事項(xiàng)
今天小編就為大家分享一篇關(guān)于Eclipse遠(yuǎn)程debug的步驟與注意事項(xiàng),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03Spring如何利用@Value注解讀取yml中的map配置
這篇文章主要介紹了Spring如何利用@Value注解讀取yml中的map配置,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02SpringBoot實(shí)現(xiàn)WebSocket服務(wù)并讓客戶端實(shí)時(shí)接收
使用SpringBoot和WebSocket可創(chuàng)建實(shí)時(shí)消息推送服務(wù),首先添加WebSocket依賴至pom.xml,配置WebSocket端點(diǎn)和邏輯處理器,通過(guò)WebSocketHandler處理消息,使用AnnouncementController模擬消息推送,支持HTML和微信小程序客戶端接收消息,感興趣的可以了解一下2024-10-10