Synchronized的實現(xiàn)原理
對于被Synchronized修飾的方法/代碼塊炊邦,會多出三個匯編指令:monitorEnter(代碼執(zhí)行前)舷夺、monitorExit(代碼執(zhí)行后)、monitorExit(拋出異常時)。
monitorEnter是獲取鎖伞鲫,monitorExit釋放鎖。
為什么每一個對象都能成為鎖签舞,鎖存在什么地方
Synchronized修飾方法秕脓,那么這個鎖就是類層面的鎖,鎖的是當前類的class對象儒搭。對于Synchronized代碼塊吠架,則Synchronized(obj)中的obj就是被鎖的對象。每一個java對象在內(nèi)存中存儲的布局可以分為三塊區(qū)域:對象頭(Header)搂鲫、實例數(shù)據(jù)(Instance Data)和對齊填充(Padding)傍药。而鎖存在于對象頭。 對象頭(Object Header)包括兩部分信息,第一部分用于存儲對象自身的運行時數(shù)據(jù)拐辽, 如哈希碼(HashCode)拣挪、GC分代年齡、鎖狀態(tài)標志俱诸、線程持有的鎖菠劝、偏向線程ID、偏向時間戳等等睁搭,這部分數(shù)據(jù)的長度在32位和64位的虛擬機(暫 不考慮開啟壓縮指針的場景)中分別為32個和64個Bits赶诊,官方稱它為“Mark Word”。對象頭的另外一部分是類型指針园骆,即是對象指向它的類的元數(shù)據(jù)的指針甫何,虛擬機通過這個指針來確定這個對象是哪個類的實例。
獲取鎖的流程
首先在markword中有個鎖標志來表明當前是哪種類型的鎖遇伞,而走不同的流程。
鎖升級和鎖膨脹的流程:偏向鎖->輕量級鎖+自旋鎖->重量級鎖
偏向鎖:為了減少輕量級鎖的性能消耗(輕量級鎖每次申請捶牢、釋放鎖都至少需要一次CAS鸠珠,但偏向鎖只有初始化時需要一次CAS):一定時間內(nèi)只有一個線程執(zhí)行同步代碼,不存在競爭秋麸。在markword中記錄了線程ID和epoch值渐排。線程從無鎖狀態(tài)進入同步代碼塊時判斷有沒有記錄線程ID,有記錄則判斷是不是當前線程ID灸蟆,是就直接獲得鎖驯耻,無記錄則進行CAS操作將當前線程ID存入markword中,操作失敗則存在競爭升級為輕量級鎖炒考。重偏向(待補充)
輕量級鎖:為了減少以下場景的性能消耗:線程無實際競爭或競爭的時間很短(時間內(nèi)自旋可以獲得鎖)可缚。包括系統(tǒng)調(diào)用引起的內(nèi)核態(tài)與用戶態(tài)切換、線程阻塞造成的線程切換等斋枢。將Mark Word中的部分字節(jié)CAS更新指向線程棧中的Lock Record帘靡,如果更新成功,則輕量級鎖獲取成功瓤帚,記錄鎖狀態(tài)為輕量級鎖描姚;否則,說明已經(jīng)有線程獲得了輕量級鎖戈次,此時CAS操作自旋一段時間轩勘,仍然失敗則膨脹為重量級鎖。
重量級鎖:此時會初始化一個objectMonitor對象監(jiān)視器怯邪,markword中的部分字節(jié)會指向這個monitor绊寻。monitor包含以下信息:markword,當前對象,當前線程榛斯,重入次數(shù)观游,cxq隊列,entryList,waitSet等待隊列驮俗。首先線程獲取鎖判斷owner是否為空懂缕,為空則記錄為當前線程,不為空判斷是不是當前線程王凑,是則重入次數(shù)加1搪柑。不是則將線程包裝成ObjectWaiter對象放入cxq隊列,并調(diào)用park掛起這個線程索烹。鎖釋放會根據(jù)策略不同從cxq隊列或entryList中取一個線程去喚醒去再次競爭鎖
wait與notify
wait時線程會被掛起放入waitset隊列并釋放鎖工碾;
notify時從waitSet中取出第一個,根據(jù)Policy的不同百姓,將這個線程放入_EntryList或者_cxq隊列中的起始或末尾位置渊额,然后根據(jù)QMode的不同,將ObjectWaiter從_cxq或者_EntryList中取出后喚醒再次去競爭鎖垒拢。