java鎖機制
1屁擅,什么是鎖,在并發(fā)環(huán)境下产弹,多個線程會對同一個資源進行爭搶那么就有可能導致數(shù)據(jù)不一致的問題派歌,所以我們就引入了鎖的機制,對資源進行鎖定痰哨?
簡單來說胶果,在java中每個object,也就是每個對象都擁有一把鎖斤斧,這把鎖存放在當前對象的頭中鎖記錄了當前對象被那個線程占用呢早抠?
我們了解一下對象的結(jié)構(gòu)吧
對象由對象頭,實例數(shù)據(jù)(實例化對象的時候設(shè)定的屬性和狀態(tài))撬讽,對齊填充字節(jié)(為滿足java的對象大小必須為8比特的倍數(shù)而設(shè)計的)
對象頭蕊连!重點 包含了倆部分 mark word(emmm鎖狀態(tài)呀,年齡呀游昼。gc標記啥的)和class pointer(指向當前對象的類型所在方法區(qū)的內(nèi)存數(shù)據(jù))相對于實例數(shù)據(jù)呢~他們被設(shè)定為額外的開銷甘苍,所以當初設(shè)計的時候被設(shè)計的極小用以提高效率
所以說對象中的鎖就存在mark word中
我們一會再說鎖的四種狀態(tài)吧
大家也許都知道java中農(nóng)synchronized關(guān)鍵字可以用來同步線程,那么synchronized在被編譯后會生成mointorenter和mointorexit兩個指令(字節(jié)碼)烘豌,來自于jvm編程思想(其實是自己想的)
Monitor是管程或者監(jiān)視器的意思载庭,想象為只能容納一個人的房間,而其他線程想象為想進入房間的客人,所以當一個客人進入了monitor后其他的線程只能等待囚聚,只有當線程退出其他的線程才能進入靖榕,假如A線程進入monitor那他現(xiàn)在就是活動狀態(tài),如果此時他有一個判斷條件需要暫時讓出執(zhí)行權(quán)靡挥,那他就是進圖wait set狀態(tài)序矩,狀態(tài)也會變成wait,此時entry set的線程就有機會進圖monitor跋破,此時我們再假設(shè)B進入monitor并且順利完成任務(wù)簸淀,此時就可以通過notify的形式來喚醒wait set中的線程A,讓A在此進入線程執(zhí)行任務(wù)直到退出。這即使synchronized的同步機制毒返,但是synchronized在編譯之后是一個mointorenter和mointorexit倆字節(jié)碼指令而monitor是依賴于操作系統(tǒng)的mutex lock來實現(xiàn)的(其實即是java線程實際上是通過對操作系統(tǒng)線程的映射)所以每當掛起或者喚醒一個線程都要切換操作系統(tǒng)的內(nèi)核態(tài)租幕,是重量級的在某些情況下甚至切換的時間甚至會超出線程執(zhí)行任務(wù)的時間,這樣往往得不償失拧簸。
所以我們引入主題劲绪,在java6之后引入了偏向鎖,輕量級鎖盆赤,所以鎖呀一共有四種狀態(tài)贾富,分別是無鎖,輕量級鎖牺六,偏向鎖颤枪,重量級鎖。對應(yīng)mark word的四種狀態(tài)淑际,N犯佟!春缕!噢噢鎖只能升級不能降級盗胀。
無鎖,顧名思義锄贼,就是沒有對資源進行鎖定票灰,所有的線程都能對資源進行改變,若某對象不出現(xiàn)在多線程環(huán)境或者在多線程環(huán)境不出現(xiàn)競爭的情況也就相安無事宅荤,直接給調(diào)用就行米间。若是存在競爭,我又不想鎖定膘侮,那我們咋辦?我們就要通過某種機制限制只有一個線程能修改資源成功的榛,其他修改失敗的線程都需要不斷重試(自旋)知道成功這就是著名的CAS琼了,其實cas在操作系統(tǒng)中是通過一條指令來實現(xiàn)的所以他能保證原子性,所以我們可以通過諸如cas的這種方式進行無鎖編程
偏向鎖:假設(shè)一個對象被枷鎖了,但是實際上在實際運行中只有一改線程會獲取這個對象鎖雕薪,那么我們最理想的方式就是不通過線程狀態(tài)的切換昧诱,也不要通過cas來獲得鎖,因為這樣多多少少也要耗費一些資源所袁,如果對象能認識這個線程是不是只要這個線程過來盏档,我就把鎖給他 我們即可認為這個鎖偏愛這個線程 那么我們?nèi)绾闻袛嗄亍啊保吭趍ark word是鎖標志位為01時我們判斷他倒數(shù)第三個比特是不是1若是1則是偏向鎖否則為無鎖燥爷,若是偏向鎖我們再去讀mark word的前23個比特蜈亩,也就是線程id通過線程id來確認當前想要獲得對象鎖的這個線程是不是老朋友,假如情況發(fā)生了變化前翎。對象發(fā)行目前不止有一個線程而是有多個線程同時在爭搶鎖稚配,那偏向鎖就會升級為輕量級鎖。
輕量級鎖:輕量級鎖如何判斷線程和鎖之間的綁定關(guān)系呢港华?變化線程id值嗎道川?不至于不至于~我們會變成前30個bit也就是指向棧中鎖記錄的指針?這是咋回事呢立宜?我們來思考一下冒萄,當一個線程想要獲得某個鎖的對象時,假如看到鎖標志位為00就知道了橙数,原理你是輕量級鎖尊流,這時線程會在自己的虛擬機棧中開辟一塊被稱為Lock Record 的空間(線程私有) Lock Record存放的其實就是mark word的副本以及owner的指針,線程通過cas嘗試獲取鎖商模,一旦獲得就會復制該對象頭中的md到lock record中并且將lock record中的owner指針指向該對象奠旺,另外對象頭前30位也會生成一個指針指向線程虛擬機棧中的lock record,這樣就實現(xiàn)了線程和對象鎖的綁定施流,他們就知道了對方的存在响疚。此時這個對象就被鎖定了,獲取了這個對象鎖的線程就可以執(zhí)行任務(wù)瞪醋。那此時其他線程也要該對象執(zhí)行某些任務(wù)呢忿晕?此時其他線程將會自旋等待!解釋一下银受,自旋就是輪詢践盼,線程自己在不斷的循環(huán)嘗試著去看一下目標對象的鎖有沒有被釋放呀,釋放了就獲取宾巍,沒有我就過會再問咕幻。這個方式區(qū)別于被操作系統(tǒng)掛起阻塞,因為若是對象的鎖很快就被釋放的話自旋就不用進行系統(tǒng)的中斷和現(xiàn)場恢復了所以他的效率更高顶霞!噢噢哦忘記說了肄程,自旋相當于cpu在空轉(zhuǎn)長時間自旋會浪費cpu的資源锣吼。所以出現(xiàn)了一種叫做適應(yīng)性自旋的優(yōu)化,其實就是自旋的時間不再固定了(又是操作系統(tǒng)里面的知識)而是又上一次同一個鎖上的自旋時間以及鎖狀態(tài)來判斷的蓝厌。若是等待的線程超過一個玄叠,那就會從輕量級鎖變成重量級鎖了,若被標記為重量級鎖拓提,那就和前面一樣被完全鎖定資源读恃。