平??偸强吹礁魇礁鳂拥逆i類型,但是卻沒(méi)有一個(gè)固定或者清晰的分類。這里記錄一下平時(shí)有見(jiàn)到的討論的鎖類型以及一些典型的實(shí)現(xiàn)。
一 公平鎖/非公平鎖
當(dāng)某些資源被加鎖之后,爭(zhēng)用這個(gè)鎖的線程很多。這時(shí)爭(zhēng)用鎖的線程,由誰(shuí)來(lái)獲取到資源的問(wèn)題。公平鎖 的作用在于面對(duì)存在爭(zhēng)用的資源時(shí),讓爭(zhēng)用線程排隊(duì)獲取,類似一個(gè)FIFO隊(duì)列一樣按順序來(lái)獲取和使用資源。非公平鎖 則不會(huì)按照這種順序來(lái)獲取資源,處于爭(zhēng)用狀態(tài)的線程,哪一個(gè)率先獲得鎖是不確定的。
在Java中,ReentrantLock 是實(shí)現(xiàn)了這種模式的。默認(rèn)的構(gòu)造函數(shù)提供了一個(gè) 非公平鎖 的實(shí)現(xiàn)。如果你想要得到一個(gè)公平鎖,那么可以使用另一個(gè)含參的構(gòu)造函數(shù)
Reentrantlock(true)
來(lái)得到一個(gè)公平鎖。其實(shí)這里可以深究一下,為什么默認(rèn)的鎖實(shí)現(xiàn)會(huì)是一個(gè)非公平策略。從資源使用的角度上來(lái)講,公平鎖內(nèi)部還需要額外維護(hù)一個(gè)線程隊(duì)列,并且保證這個(gè)隊(duì)列內(nèi)的線程的有序性,這無(wú)疑是更復(fù)雜并且需要額外的內(nèi)存和開(kāi)銷的。相比較而言非公平策略的鎖更輕便性能更好。
所以在平常使用的時(shí)候,一般對(duì)資源爭(zhēng)用順序不敏感的場(chǎng)景最好都優(yōu)先使用非公平鎖。
二 自旋鎖
請(qǐng)求鎖的線程,線程在請(qǐng)求鎖失敗后會(huì)進(jìn)入 等待阻塞 狀態(tài)并讓出當(dāng)前的 CPU時(shí)間片。然而處理器進(jìn)行線程切換也是一筆開(kāi)銷。為了減少這種場(chǎng)景下的開(kāi)銷時(shí)間??梢允褂?自旋鎖。
其核心概念在于不讓等待資源的線程進(jìn)入等待阻塞狀態(tài),在短時(shí)間內(nèi)占用CPU資源并等待鎖的釋放。 這個(gè)設(shè)計(jì)的緣由,是設(shè)計(jì)者發(fā)現(xiàn)很多后臺(tái)線程占用鎖的時(shí)間并不是很長(zhǎng),也就是說(shuō)每一個(gè)線程占用鎖很短的時(shí)間后就會(huì)釋放這個(gè)鎖,比起頻繁的CPU線程切換,還不如讓等待線程進(jìn)行短暫的等待。
自旋鎖在JDK1.4.2中引入,使用-XX:+UseSpinning來(lái)開(kāi)啟。JDK6中已經(jīng)變?yōu)槟J(rèn)開(kāi)啟,并且引入了自適應(yīng)的自旋鎖。自適應(yīng)意味著自旋的時(shí)間不在固定了,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定。自旋等待的時(shí)間必須要有一定的限度,如果自旋超過(guò)了限定次數(shù)(默認(rèn)是10次,可以使用-XX:PreBlockSpin來(lái)更改)沒(méi)有成功獲得鎖,就應(yīng)當(dāng)使用傳統(tǒng)的方式去掛起線程了。
二 可重入鎖/不可重入鎖
在 Java 中可重入的概念是指某個(gè)線程在獲取到一個(gè)對(duì)象的鎖之后,可以再次獲取到這個(gè)對(duì)象的鎖,其重復(fù)獲取的次數(shù)大于1。在遞歸調(diào)用中可以利用到這個(gè)特點(diǎn)。
一般來(lái)說(shuō)某個(gè)鎖被線程持有后,其他的線程就不能在鎖被釋放之前去訪問(wèn)。但是有些線程需要多次調(diào)用加鎖的方法/代碼塊。會(huì)有一個(gè)計(jì)數(shù)器為重復(fù)獲取鎖的次數(shù)進(jìn)行計(jì)數(shù),每重復(fù)獲取一次就加一,沒(méi)釋放一次就減一。直到計(jì)數(shù)器值為 0 即表示這個(gè)鎖已經(jīng)被這個(gè)線程完全釋放掉。
典型的應(yīng)用:synchronized 關(guān)鍵字與 ReentrantLock 都是 可重入鎖。
三 樂(lè)觀鎖/悲觀鎖
這一組概念主要在于 對(duì)資源占用的預(yù)設(shè)態(tài)度 上。
樂(lè)觀鎖,即假設(shè)當(dāng)前資源沒(méi)有被多個(gè)線程爭(zhēng)用,假設(shè)不會(huì)發(fā)生沖突,只在提交的時(shí)候才校驗(yàn)數(shù)據(jù)的一致性。
悲觀鎖,與之對(duì)應(yīng),假設(shè)會(huì)發(fā)生沖突,并且主動(dòng)屏蔽可能違反數(shù)據(jù)一致性的操作。
這兩者本質(zhì)上來(lái)說(shuō)就是設(shè)計(jì)思想。不同的語(yǔ)言或者工具上的具體實(shí)現(xiàn)也各不一樣。Java 中的 synchronized 關(guān)鍵字、lock 對(duì)象都屬于悲觀鎖;auto 原子類則屬于樂(lè)觀鎖,其實(shí)現(xiàn)使用了 CAS。MySQL 數(shù)據(jù)庫(kù)中對(duì)于結(jié)構(gòu)性修改的操作也可以劃撥為使用悲觀鎖觀念的操作內(nèi)容。而 InnoDB 引擎下引入了基于 mvcc 版本控制的樂(lè)觀鎖,適用于查詢操作。mvcc 即通過(guò)給數(shù)據(jù)行附加一個(gè)版本號(hào)來(lái)確保數(shù)據(jù)對(duì)更新的敏感。InnoDB 會(huì)在每行數(shù)據(jù)后添加兩個(gè)額外的隱藏的值來(lái)實(shí)現(xiàn)MVCC。在實(shí)際操作中,存儲(chǔ)的并不是時(shí)間,而是事務(wù)的版本號(hào),每開(kāi)啟一個(gè)新事務(wù),事務(wù)的版本號(hào)就會(huì)遞增。
四 共享鎖與排它鎖
這是一個(gè)我在 MySQL 數(shù)據(jù)庫(kù)中看到的概念。兩個(gè)鎖之間的主要區(qū)別在于兼容性。數(shù)據(jù)被加了共享鎖,那么這部分?jǐn)?shù)據(jù)還可以被其他的線程或操作訪問(wèn)到,比如另一個(gè)操作也想要查詢這一部分?jǐn)?shù)據(jù)并向其加一個(gè)共享鎖。那么這個(gè)操作是可行的。但是一個(gè)更新數(shù)據(jù)的操作想要向這部分?jǐn)?shù)據(jù)加排它鎖并執(zhí)行更新操作就會(huì)失敗,因?yàn)檫@個(gè)時(shí)候數(shù)據(jù)集上已經(jīng)掛上了一個(gè)共享鎖。
具體的表現(xiàn)是在 MySQL-MyISAM 引擎下的表級(jí)鎖。
兩者之間的兼容性

五 讀寫鎖
// TODO