關(guān)于Java鎖性能提高(鎖升級(jí))機(jī)制的總結(jié)
Java鎖性能提高機(jī)制
鎖的使用很難避免,如何盡量提高鎖的性能就顯得比較重要了
鎖偏向
所謂的偏向鎖是指在對(duì)象實(shí)例的Mark Word(說(shuō)白了就是對(duì)象內(nèi)存中的開(kāi)頭幾個(gè)字節(jié)保留的信息,如果把一個(gè)對(duì)象序列化后明顯可以看見(jiàn)開(kāi)頭的這些信息),為了在線程競(jìng)爭(zhēng)不激烈的情況下,減少加鎖及解鎖的性能損耗(輕量級(jí)鎖涉及多次CAS操作)在Mark Word中有保存這上次使用這個(gè)對(duì)象鎖的線程ID信息,如果這個(gè)線程再次請(qǐng)求這個(gè)對(duì)象鎖,那么只需要讀取該對(duì)象上的Mark Word的偏向鎖信息(也就是線程id)跟線程本身的id進(jìn)行對(duì)比,如果是同一個(gè)id就直接認(rèn)為該id獲得鎖成功,而不需要在進(jìn)行真正的加解鎖操作。
其實(shí)說(shuō)白了就是你上次來(lái)過(guò)了,這次又來(lái),并且在這兩次之間沒(méi)有其他人來(lái)過(guò),對(duì)于這個(gè)線程來(lái)說(shuō),鎖對(duì)象的資源隨便用都是安全的。這次用緩存來(lái)?yè)Q取性能的做法,不過(guò)偏向鎖在鎖競(jìng)爭(zhēng)不激烈的情景下使用才能獲取比較高的效率。當(dāng)使用CAS競(jìng)爭(zhēng)偏向鎖失敗,表示競(jìng)爭(zhēng)比較激烈,偏向鎖升級(jí)為輕量級(jí)鎖。
輕量級(jí)鎖
所謂輕量級(jí)鎖是比偏向鎖更耗資源的鎖,實(shí)現(xiàn)機(jī)制是,線程在競(jìng)爭(zhēng)輕量級(jí)鎖前,在線程的棧內(nèi)存中分配一段空間作為鎖記錄空間(就是輕量級(jí)鎖對(duì)應(yīng)的對(duì)象的對(duì)象頭的字段的拷貝),拷貝好后,線程通過(guò)CAS去競(jìng)爭(zhēng)這個(gè)對(duì)象鎖,試圖把對(duì)象的對(duì)象頭子段改成指向所記錄空間,如果成功則說(shuō)明獲取輕量級(jí)鎖成功,如果失敗,則進(jìn)入自旋(說(shuō)白了就是循環(huán))取試著獲取鎖。如果自旋到一定次數(shù)還是不能獲取到鎖,則進(jìn)入重量級(jí)鎖。
自旋鎖
說(shuō)白了就是獲取鎖失敗后,為了避免直接讓線程進(jìn)入阻塞狀態(tài)而采取的循環(huán)一定次數(shù)去試著獲取鎖的行為。(線程進(jìn)入阻塞狀態(tài)和退出阻塞狀態(tài)是涉及到操作系統(tǒng)管理層面的,需要從用戶態(tài)進(jìn)入內(nèi)核態(tài),非常消耗系統(tǒng)資源),為什么能這樣做呢,是因?yàn)樵囼?yàn)證明,鎖的持有時(shí)間一般是非常短的,所以一般多次嘗試就能競(jìng)爭(zhēng)到鎖。
重量級(jí)鎖
所謂的重量級(jí)鎖,其實(shí)就是最原始和最開(kāi)始java實(shí)現(xiàn)的阻塞鎖。在JVM中又叫對(duì)象監(jiān)視器。
這時(shí)的鎖對(duì)象的對(duì)象頭字段指向的是一個(gè)互斥量,所有線程競(jìng)爭(zhēng)重量級(jí)鎖,競(jìng)爭(zhēng)失敗的線程進(jìn)入阻塞狀態(tài)(操作系統(tǒng)層面),并且在鎖對(duì)象的一個(gè)等待池中等待被喚醒,被喚醒后的線程再次去競(jìng)爭(zhēng)鎖資源。
小結(jié):
所謂的鎖升級(jí),其實(shí)就是從偏向鎖à輕量級(jí)鎖(自旋鎖)à重量級(jí)鎖,之前一直被這幾個(gè)概念困擾,網(wǎng)上的 文章解釋的又不通俗易懂,其實(shí)說(shuō)白了,一切一切的開(kāi)始源于java對(duì)synchronized同步機(jī)制的性能優(yōu)化,最原始的synchronized同步機(jī)制是直接跳過(guò)前幾個(gè)步驟,直接進(jìn)入重量級(jí)鎖的,而重量級(jí)鎖因?yàn)樾枰€程進(jìn)入阻塞狀態(tài)(從用戶態(tài)進(jìn)入內(nèi)核態(tài))這種操作系統(tǒng)層面的操作非常消耗資源,這樣的話,synchronized同步機(jī)制就顯得很笨重,效率不高。
那么為了解決這個(gè)問(wèn)題,java才引入了偏向鎖,輕量級(jí)鎖,自旋鎖這幾個(gè)概念。
拿這幾個(gè)鎖有何優(yōu)化呢?網(wǎng)上也沒(méi)有通俗易懂的解釋,其實(shí)說(shuō)白了就是,偏向鎖是為了避免CAS操作,盡量在對(duì)比對(duì)象頭就把加鎖問(wèn)題解決掉,只有沖突的情況下才指向一次CAS操作,而輕量級(jí)鎖和自旋鎖呢,其實(shí)兩個(gè)是一體使用的,為的是盡量避免線程進(jìn)入內(nèi)核的阻塞狀態(tài),這對(duì)性能非常不利,試圖用CAS操作和循環(huán)把加鎖問(wèn)題解決掉,而重量級(jí)鎖是最終的無(wú)奈解決方案,說(shuō)白了就是能通過(guò)內(nèi)存讀取判斷解決加速問(wèn)題優(yōu)于〉通過(guò)CAS操作和空循環(huán)優(yōu)于〉CPU阻塞,喚醒線程。
Java鎖升級(jí)簡(jiǎn)述
什么是鎖:在并發(fā)環(huán)境下,多線程針對(duì)同一個(gè)資源進(jìn)行爭(zhēng)搶,可能會(huì)導(dǎo)致數(shù)據(jù)出現(xiàn)異常,為了解決這個(gè)問(wèn)題,就引入了鎖機(jī)制。通過(guò)鎖對(duì)資源進(jìn)行鎖定。
在JVM當(dāng)中,寄存器、虛擬機(jī)棧、本地方法棧是線程獨(dú)享(安全)的。但是Java堆(保存所有的Java對(duì)象和數(shù)組)、方法區(qū)、運(yùn)行時(shí)常量池是線程共享的,就有可能出現(xiàn)線程安全問(wèn)題。每個(gè)object對(duì)象都有一把鎖,這個(gè)鎖在對(duì)象頭中,記錄著這個(gè)對(duì)象在被哪個(gè)線程占用著。
對(duì)象頭結(jié)構(gòu)
Java的對(duì)象分為三個(gè)部分:對(duì)象頭、實(shí)例數(shù)據(jù)、對(duì)齊填充字節(jié)。
對(duì)象頭存放了對(duì)象本身的運(yùn)行時(shí)信息。對(duì)象頭包含了兩部分:Mark Word和ClassPointer。相對(duì)于實(shí)際數(shù)據(jù),對(duì)象頭屬于一些額外的內(nèi)存開(kāi)銷。因此被設(shè)計(jì)的極小以提升效率。其中,Mark Word中,就包含了鎖信息。
鎖狀態(tài) | 25bit | 4bit | 1bit | 2bit | |
---|---|---|---|---|---|
23bit | 2bit | 是否偏向鎖 | 鎖標(biāo)志位 | ||
無(wú)鎖 | 對(duì)象的hashCode | 分代年齡 | 0 | 01 | |
偏向鎖 | 線程id | Epoch | 分代年齡 | 1 | 01 |
輕量級(jí)鎖 | 指向棧中鎖記錄的指針 | 00 | |||
重量級(jí)鎖 | 指向重量級(jí)鎖的指針 | 10 | |||
GC標(biāo)記 | 空 | 11 |
Mark Word(32位為例)
對(duì)象內(nèi)存存在一把鎖,鎖信息放在了對(duì)象頭的Mark Word當(dāng)中。在最后兩位中,代表了鎖標(biāo)志位:無(wú)鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖。
synchronized關(guān)鍵字
synchronized關(guān)鍵字可以用來(lái)同步線程,synchronized被編譯后,會(huì)生成monitorenter,monitorexit兩個(gè)字節(jié)碼指令。JVM以此來(lái)進(jìn)行線程同步。
monitor
monitor即為同步監(jiān)視器,一個(gè)線程進(jìn)入了monitor,其他線程只能夠等待,當(dāng)且只有這個(gè)線程退出,其他線程才有機(jī)會(huì)競(jìng)爭(zhēng)到所資源進(jìn)行執(zhí)行。
Entry Set中聚集了一些想要進(jìn)入Monitor的線程,它們處于waiting狀態(tài),假設(shè)某個(gè)名為A線程成功進(jìn)入了Monitor,那么它就處于active狀態(tài)。假設(shè)此時(shí)A線程執(zhí)行途中,遇到一個(gè)判斷條件,需要它暫時(shí)讓出執(zhí)行權(quán),那么它將進(jìn)入wait set,狀態(tài)也被標(biāo)記為waiting。這時(shí)entry set中的線程就有機(jī)會(huì)進(jìn)入monitor,假設(shè)一個(gè)線程B成功進(jìn)入并且順利完成,那么它可以通過(guò)notify的形式來(lái)喚醒wait set中的線程A,讓線程A再次進(jìn)入Monitor,執(zhí)行完成后便退出。
這就是synchronized的同步機(jī)制,但是synchronized可能存在性能問(wèn)題,因?yàn)閙onitor是依賴于操作系統(tǒng)的Mutex Lock來(lái)實(shí)現(xiàn)的,Java線程事實(shí)上是對(duì)操作系統(tǒng)線程的映射,所以每當(dāng)掛起或喚醒一個(gè)線程都要切換到操作系統(tǒng)的內(nèi)核態(tài),這個(gè)操作是比較重量級(jí)的。在一些情況下,甚至切換時(shí)間本身就會(huì)超出線程執(zhí)行任務(wù)的時(shí)間,這樣的話,使用synchronized將會(huì)對(duì)程序的性能產(chǎn)生影響。
從Java6開(kāi)始,synchronized關(guān)鍵字就進(jìn)行了優(yōu)化,引入了“偏向鎖”,“輕量級(jí)鎖”,所以鎖共有四種狀態(tài),分別為:無(wú)鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖。
鎖的四種狀態(tài)
無(wú)鎖
- 無(wú)鎖顧名思義就是沒(méi)有對(duì)資源進(jìn)行鎖定,所有線程都能夠訪問(wèn)同一資源。在出現(xiàn)資源競(jìng)爭(zhēng)的情況下,不想對(duì)資源進(jìn)行鎖定,但是還是想通過(guò)某種手段進(jìn)行多線程的控制。假如有一個(gè)累計(jì)值,我們不想通過(guò)鎖定資源的方式進(jìn)行限制,但是想控制只有一個(gè)線程能修改成功的話,就可以通過(guò)CAS操作(可以看AutomaticInteger中對(duì)unsafe類的操作)。但是當(dāng)出現(xiàn)循環(huán)的時(shí)候,某個(gè)時(shí)間的cpu占用率會(huì)變得很高,而且可能出現(xiàn)ABA問(wèn)題(通過(guò)版本號(hào)解決)。
偏向鎖
- 當(dāng)我們開(kāi)始為對(duì)象進(jìn)行加鎖,假如某對(duì)象被加鎖了,但在實(shí)際運(yùn)行的時(shí)候,只有一條線程會(huì)獲取這個(gè)對(duì)鎖,那么我們最理想的方式,是不要通過(guò)系統(tǒng)狀態(tài)切換,也不通過(guò)CAS獲取鎖,因?yàn)槟菢踊蚨嗷蛏龠€是消耗了一些資源。我們?cè)O(shè)想的是最好對(duì)象能夠認(rèn)識(shí)這個(gè)線程,只要是這個(gè)線程過(guò)來(lái),那么對(duì)象就直接把鎖交出去。我們可以認(rèn)為這個(gè)對(duì)象偏愛(ài)這個(gè)線程,所以被稱為“偏向鎖”。
- 那么偏向鎖是怎么實(shí)現(xiàn)的呢?在Mark Word中,當(dāng)鎖標(biāo)志為是01,那么判斷倒數(shù)第三個(gè)bit是否為1,如果是1,那么代表當(dāng)前對(duì)象的鎖狀態(tài)為偏向鎖,于是再去讀Mark Word的前23個(gè)bit,這23個(gè)bit就是線程ID,通過(guò)線程ID來(lái)確認(rèn)想要獲得對(duì)象鎖的線程是不是之前所記錄的線程ID。
輕量級(jí)鎖
- 假如情況發(fā)生了變化,對(duì)象發(fā)現(xiàn)目前不只有一個(gè)線程,而是有多個(gè)線程正在競(jìng)爭(zhēng)鎖,那么偏向鎖將會(huì)升級(jí)為輕量級(jí)鎖。Mark
- Word發(fā)生變化,指向棧中Lock Record的指針。
重量級(jí)鎖
- 倘若在輕量級(jí)鎖的情況下,線程自旋次數(shù)過(guò)多,這個(gè)時(shí)候就會(huì)從輕量級(jí)鎖轉(zhuǎn)換成為重量級(jí)鎖。Mark Word再次發(fā)生變化,指向重量級(jí)鎖的指針。這個(gè)時(shí)候,其他線程試圖獲取鎖時(shí)都會(huì)被阻塞,只有持有鎖的線程釋放鎖之后才會(huì)喚醒這些線程。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java 二維數(shù)組矩陣乘法的實(shí)現(xiàn)方法
java 二維數(shù)組矩陣乘法的實(shí)現(xiàn)方法,需要的朋友可以參考一下2013-03-03深入解析Java的Servlet過(guò)濾器的原理及其應(yīng)用
這篇文章主要介紹了深入解析Java的Servlet過(guò)濾器的原理及應(yīng)用,Java編寫(xiě)的Servlet通常是一個(gè)與網(wǎng)頁(yè)一起作用于瀏覽器客戶端的程序,需要的朋友可以參考下2016-01-01利用Maven入手Spring Boot第一個(gè)程序詳解
這篇文章主要給大家介紹了關(guān)于如何利用Maven入手Spring Boot第一個(gè)程序的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02java?spring?validation?自動(dòng)、手動(dòng)校驗(yàn)
HibernateValidator簡(jiǎn)化了Java開(kāi)發(fā)中的參數(shù)校驗(yàn)過(guò)程,提供自動(dòng)和手動(dòng)兩種校驗(yàn)方式,通過(guò)引入相關(guān)依賴并使用@Validated注解,可以實(shí)現(xiàn)自動(dòng)校驗(yàn),手動(dòng)校驗(yàn)則需要使用ValidatorUtils類,此方法有效減少代碼重復(fù),提高開(kāi)發(fā)效率2024-09-09