wordpress 群網(wǎng)絡(luò)公司優(yōu)化關(guān)鍵詞
synchronized進階原理
1.輕量級鎖
輕量級鎖的使用場景:如果一個對象雖然有多個線程訪問,但多線程訪問的時間是錯開的(也就是沒有競爭),那么可以使用輕量級鎖來優(yōu)化(如果出現(xiàn)競爭,操作系統(tǒng)會將輕量級鎖升級為重量級鎖)。輕量級鎖對使用者是透明的(由操作系統(tǒng)控制),即語法仍是synchronized。
假設(shè)有兩個方法同步塊,利用同一個對象加鎖
static final Object obj = new Object();
public static void method1(){synchronized(obj){//同步塊Amethod2();}
}
public static void method2(){synchronized(obj){//同步塊B}
}
創(chuàng)建鎖記錄(Lock Record)對象,每個線程的棧幀都會包含一個鎖記錄的結(jié)構(gòu),內(nèi)部可以存儲鎖定對象的Mark Word
讓鎖記錄中Object reference 指向鎖對象,并嘗試用cas替換Object的Mark Word,將Mark Word的值存入鎖記錄(01代表未上鎖,00代表上輕量級鎖)
如果cas替換成功,對象頭中存儲了鎖記錄地址和狀態(tài)00,表示由該線程給對象加鎖
如果cas失敗,有兩種情況
· 如果其它線程已經(jīng)持有了該Object的輕量級鎖,這時表明有競爭,進入鎖膨脹過程
· 如果是自己執(zhí)行了synchronized鎖重入(自己又對synchronized加鎖了),那么再添加一條Lock Record作為重入的計數(shù)
當(dāng)退出synchronized代碼塊(解鎖時)如果有取值為null的鎖記錄,表示有重入,這時重置鎖記錄,表示重入計數(shù)減一
當(dāng)退出synchronized代碼塊(解鎖時)鎖記錄的值不為null,這時使用cas將Mark Word的值恢復(fù)給對象頭
· 成功,則解鎖成功
· 失敗,說明輕量級鎖進行了膨脹或已經(jīng)升級為重量級鎖,進入重量級鎖解鎖流程
2.鎖膨脹
如果在嘗試加輕量級鎖的過程中,CAS操作無法成功,這時一種情況就是有其他線程為此對象加上了輕量級鎖(有競爭),這時需要進行鎖膨脹,將輕量級鎖變?yōu)橹亓考夋i。
static Object obj = new Object();
public static void method1(){synchronized(obj){//同步塊}
}
當(dāng)Thread-1進行輕量級鎖時,Thread-0已經(jīng)對該對象加了輕量級鎖
這時Thread-1加輕量級鎖失敗,進入鎖膨脹流程
· 即為Object對象申請Monitor鎖,讓Object指向重量級鎖地址
· 然后自己進入Monitor的EntryList中 BLOCKED
當(dāng)Thread-0退出同步代碼塊解鎖時,使用cas將Mark Word的值恢復(fù)給對象頭,失敗。這時會進入重量級解鎖流程,即按照Monitor地址找到Monitor對象,設(shè)置Owner為null,喚醒EntryList中BLOCKED線程,從它們中競爭出下一個與Owner關(guān)聯(lián)的線程
3.自旋優(yōu)化
重量級鎖競爭的時候,還可以使用自旋鎖來進行優(yōu)化,如果當(dāng)前線程自旋鎖成功(即這時候持鎖線程已經(jīng)退出了同步塊,釋放了鎖),這時當(dāng)前線程就可以避免阻塞。
自選重試成功的情況
自旋重試失敗的情況
在java6之后自旋鎖是自適應(yīng)的,比如對象剛剛的一次自旋操作成功過,那么認為這次自旋成功的可能性會高,就多自旋幾次;反之,就少自旋甚至不自旋。自旋會占用CPU時間,單核CPU自旋就是浪費,多核CPU自旋才能發(fā)揮優(yōu)勢。java7之后不能控制是否開啟自旋功能。
4.偏向鎖
輕量級鎖在沒有競爭時(就自己這個線程),每次重入仍需要執(zhí)行CAS操作。java6中引入了偏向鎖來做進一步優(yōu)化:只有第一次使用CAS將線程ID設(shè)置到對象的Mark Word頭,之后發(fā)現(xiàn)這個線程ID是自己的就表示沒有競爭,不用重新CAS。以后只要不發(fā)生競爭,這個對象就歸該線程所有。
static final Object obj = new Object();
public static void m1(){synchronized(obj){//同步塊Am2();}
}
public static void m2(){synchronized(obj){//同步塊Bm3();}
}
public static void m3(){synchronized(obj){//同步塊C}
}
偏向狀態(tài)
對象頭:
biased_lock 用于記錄是否啟動偏向鎖(0為未啟動)
thread 是線程id(操作系統(tǒng)給的,和java中自己設(shè)置的不同)
一個對象創(chuàng)建時:
· 如果啟動了偏向鎖(默認開啟),那么對象創(chuàng)建后,markword值為0x05即最后3位為101,這時它的thread、epoch、age都是0
· 偏向鎖默認是延遲的,不會在程序啟動時立即生效,如果想避免延遲,可以加VM參數(shù) - XX:BiasedLockingStartupDelay=0來禁止延遲
· 如果沒有開啟偏向鎖,那么對象創(chuàng)建后,markword值為0x01即最后3位為001,這時它的hashcode、age都為0,第一次用到hashcode時才會賦值
測試禁用:在上面測試代碼運行時在添加VM參數(shù) -XX: -UseBiasedLocking 禁用偏向鎖
測試hashCode:當(dāng)對用對象的hashCode()時,偏向鎖會被撤銷,對象就會變成普通狀態(tài)(當(dāng)輕量級鎖對象調(diào)用hashCode()時,hashCode會記錄在棧幀中的鎖記錄里,當(dāng)重量級鎖調(diào)用hashCode()時,hashCode會存在monitor對象里)
撤銷偏向鎖-其它線程使用對象
當(dāng)有其他線程使用偏向鎖對象時,會將偏向鎖升級為輕量級鎖。
撤銷偏向鎖-使用wait/notify
批量重偏向
如果對象雖然被多個線程訪問,但沒有競爭,這時偏向了線程T1的對象仍有機會重新偏向T2,重偏向會重置對象的Thread ID
當(dāng)撤銷偏向鎖閾值超過20次后,JVM會在給這些對象加鎖時重新偏向至加鎖線程。
批量撤銷
當(dāng)撤銷偏向鎖閾值超過40次后,JVM會覺得不該偏向。于是整個類的所有對象都會變?yōu)椴豢善虻?新建的對象也是不可偏向的。
鎖撤銷
java有即時編譯器(JIT),會對代碼進行優(yōu)化,將一些無用的鎖優(yōu)化掉了