鎖從宏觀上分類策严,分為悲觀鎖與樂觀鎖穗慕。
樂觀鎖
樂觀鎖是一種樂觀思想,即認為讀多寫少妻导,遇到并發(fā)寫的可能性低逛绵,每次去拿數(shù)據(jù)的時候都認為別人不會修改,所以不會上鎖栗竖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù)暑脆,采取在寫時先讀出當前版本號,然后加鎖操作(比較跟上一次的版本號狐肢,如果一樣則更新)添吗,如果失敗則要重復讀-比較-寫的操作。
java中的樂觀鎖基本都是通過CAS操作實現(xiàn)的份名,CAS是一種更新的原子操作碟联,比較當前值跟傳入值是否一樣,一樣則更新僵腺,否則失敗鲤孵。
悲觀鎖
悲觀鎖是就是悲觀思想,即認為寫多辰如,遇到并發(fā)寫的可能性高普监,每次去拿數(shù)據(jù)的時候都認為別人會修改,所以每次在讀寫數(shù)據(jù)的時候都會上鎖琉兜,這樣別人想讀寫這個數(shù)據(jù)就會block直到拿到鎖凯正。java中的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嘗試cas樂觀鎖去獲取鎖,獲取不到豌蟋,才會轉(zhuǎn)換為悲觀鎖廊散,如RetreenLock。
阻塞代價
java的線程是映射到操作系統(tǒng)原生線程之上的梧疲,如果要阻塞或喚醒一個線程就需要操作系統(tǒng)介入允睹,需要在戶態(tài)與核心態(tài)之間切換运准,這種切換會消耗大量的系統(tǒng)資源,因為用戶態(tài)與內(nèi)核態(tài)都有各自專用的內(nèi)存空間缭受,專用的寄存器等胁澳,用戶態(tài)切換至內(nèi)核態(tài)需要傳遞給許多變量、參數(shù)給內(nèi)核贯涎,內(nèi)核也需要保護好用戶態(tài)在切換時的一些寄存器值听哭、變量等慢洋,以便內(nèi)核態(tài)調(diào)用結(jié)束后切換回用戶態(tài)繼續(xù)工作塘雳。
如果線程狀態(tài)切換是一個高頻操作時,這將會消耗很多CPU處理時間普筹;
如果對于那些需要同步的簡單的代碼塊败明,獲取鎖掛起操作消耗的時間比用戶代碼執(zhí)行的時間還要長,這種同步策略顯然非常糟糕的太防。
synchronized會導致爭用不到鎖的線程進入阻塞狀態(tài)妻顶,所以說它是java語言中一個重量級的同步操縱,被稱為重量級鎖蜒车,為了緩解上述性能問題讳嘱,JVM從1.5開始,引入了輕量鎖與偏向鎖酿愧,默認啟用了自旋鎖沥潭,他們都屬于樂觀鎖。
自旋鎖
自旋鎖原理非常簡單嬉挡,如果持有鎖的線程能在很短時間內(nèi)釋放鎖資源钝鸽,那么那些等待競爭鎖的線程就不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換進入阻塞掛起狀態(tài),它們只需要等一等(自旋)庞钢,等持有鎖的線程釋放鎖后即可立即獲取鎖拔恰,這樣就避免用戶線程和內(nèi)核的切換的消耗。
但是線程自旋是需要消耗CPU的基括,說白了就是讓CPU在做無用功颜懊,線程不能一直占用CPU自旋做無用功,所以需要設(shè)定一個自旋等待的最大時間风皿。
如果持有鎖的線程執(zhí)行的時間超過自旋等待的最大時間扔沒有釋放鎖河爹,就會導致其它爭用鎖的線程在最大等待時間內(nèi)還是獲取不到鎖,這時爭用線程會停止自旋進入阻塞狀態(tài)揪阶。
JVM對于自旋周期的選擇昌抠,jdk1.5這個限度是一定的寫死的,在1.6引入了適應(yīng)性自旋鎖鲁僚,適應(yīng)性自旋鎖意味著自旋的時間不在是固定的了炊苫,而是由前一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態(tài)來決定裁厅,基本認為一個線程上下文切換的時間是最佳的一個時間,同時JVM還針對當前CPU的負荷情況做了較多的優(yōu)化
如果平均負載小于CPUs則一直自旋
如果有超過(CPUs/2)個線程正在自旋侨艾,則后來線程直接阻塞
如果正在自旋的線程發(fā)現(xiàn)Owner發(fā)生了變化則延遲自旋時間(自旋計數(shù))或進入阻塞
如果CPU處于節(jié)電模式則停止自旋
自旋時間的最壞情況是CPU的存儲延遲(CPU A存儲了一個數(shù)據(jù)执虹,到CPU B得知這個數(shù)據(jù)直接的時間差)
自旋時會適當放棄線程優(yōu)先級之間的差異
偏向鎖
偏向鎖是jdk1.6引入的一項鎖優(yōu)化,其中的“偏”是偏心的偏唠梨。它的意思就是說袋励,這個鎖會偏向于第一個獲得它的線程,在接下來的執(zhí)行過程中当叭,假如該鎖沒有被其他線程所獲取茬故,沒有其他線程來競爭該鎖,那么持有偏向鎖的線程將永遠不需要進行同步操作蚁鳖。
也就是說:
在此線程之后的執(zhí)行過程中磺芭,如果再次進入或者退出同一段同步塊代碼,并不再需要去進行加鎖或者解鎖操作醉箕,而是會做以下的步驟:
Load-and-test钾腺,也就是簡單判斷一下當前線程id是否與Markword當中的線程id是否一致.
如果一致,則說明此線程已經(jīng)成功獲得了鎖讥裤,繼續(xù)執(zhí)行下面的代碼.
如果不一致放棒,則要檢查一下對象是否還是可偏向,即“是否偏向鎖”標志位的值己英。
如果還未偏向间螟,則利用CAS操作來競爭鎖,也即是第一次獲取鎖時的操作剧辐。
如果此對象已經(jīng)偏向了寒亥,并且不是偏向自己,則說明存在了競爭荧关。此時可能就要根據(jù)另外線程的情況溉奕,可能是重新偏向,也有可能是做偏向撤銷忍啤,但大部分情況下就是升級成輕量級鎖了加勤。
可以看出,偏向鎖是針對于一個線程而言的同波,線程獲得鎖之后就不會再有解鎖等操作了鳄梅,這樣可以省略很多開銷。假如有兩個線程來競爭該鎖話未檩,那么偏向鎖就失效了戴尸,進而升級成輕量級鎖了。
為什么要這樣做呢冤狡?因為經(jīng)驗表明孙蒙,其實大部分情況下项棠,都會是同一個線程進入同一塊同步代碼塊的。這也是為什么會有偏向鎖出現(xiàn)的原因挎峦。
在Jdk1.6中香追,偏向鎖的開關(guān)是默認開啟的,適用于只有一個線程訪問同步塊的場景坦胶。