Java-技術專題-synchronized關鍵字

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1. synchronized特點"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.1 簡介"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" Synchronized是基於monitor實現的,Synchronized經過編譯後,會在同步塊前後分別形成monitorenter和monitorexit兩個字節碼指令,在執行monitorenter指令時,首先要嘗試獲取對象鎖,如果對象沒有別鎖定,或者當前已經擁有這個對象鎖,把鎖的計數器加1,相應的在執行monitorexit指令時,會將計數器減1,當計數器爲0時,鎖就被釋放了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 如果獲取鎖失敗,那當前線程就要阻塞,直到對象鎖被另一個線程釋放爲止。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"synchronized底層語義原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" Java 虛擬機中的同步(Synchronization)基於進入和退出管程(Monitor)對象實現, 無論是顯式同步(有明確的 monitorenter 和 monitorexit 指令,即同步代碼塊)還是隱式同步都是如此。在 Java 語言中,同步用的最多的地方可能是被 synchronized 修飾的同步方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}},{"type":"strong"}],"text":"同步方法 並不是由 monitorenter 和 monitorexit 指令來實現同步的,而是由方法調用指令讀取運行時常量池中方法的 "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"ACC_SYNCHRONIZED "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}},{"type":"strong"}],"text":"標誌來隱式實現的,關於這點,稍後詳細分析。"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"下面先來了解一個概念Java對象頭,這對深入理解synchronized實現原理非常關鍵。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.2 特點"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"可重入鎖"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"排他鎖"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"屬於jvm,由jvm實現"}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 可重入鎖實現可重入性原理或機制是:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"每一個鎖關聯一個線程持有者和計數器,當計數器爲 0 時表示該鎖沒有被任何線程持有,那麼任何線程都可能獲得該鎖而調用相應的方法;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"當某一線程請求成功後,JVM會記下鎖的持有線程,並且將計數器置爲 1;此時其它線程請求該鎖,則必須等待;而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增;"},{"type":"text","text":"當線程退出同步代碼塊時,計數器會遞減,如果計數器爲 0,則釋放該鎖。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.3 synchronized保證可見性"}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JMM關於synchronized的兩條規定:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"1)線程解鎖前,必須把共享變量的最新值刷新到主內存中"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"2)線程加鎖時,將清空工作內存中共享變量的值,從而使用共享變量時需要從主內存中重新獲取最新的值"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.4 synchronized保證可見性"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"HashTable"}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"2. JVM 對Synchronized的優化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 鎖的狀態總共有四種,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"無鎖狀態、偏向鎖、輕量級鎖和重量級鎖"},{"type":"text","text":"。隨着鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖,但是鎖的升級是單向的,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"也就是說只能從低到高升級,不會出現鎖的降級。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.1 Java對象頭與Monitor"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 在JVM中,對象在內存中的佈局分爲三塊區域:對象頭、實例數據和對齊填充。如下:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/65/65af70e7cc1cab6d19588a881767e2b7.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"實例變量:存放類的屬性數據信息,包括父類的屬性信息,如果是數組的實例部分還包括數組的長度,這部分內存按4字節對齊。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"填充數據:由於虛擬機要求對象起始地址必須是8字節的整數倍。填充數據不是必須存在的,僅僅是爲了字節對齊,這點了解即可。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Java對象頭"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}}],"text":"Java頭對象"},{"type":"text","text":",它實現"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"synchronized"},{"type":"text","text":"的鎖對象的基礎,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"synchronized"},{"type":"text","text":"使用的鎖對象是存儲在Java對象頭裏的,"},{"type":"text","marks":[{"type":"strong"}],"text":"jvm中採用2個字來存儲對象頭(如果對象是數組則會分配3個字,多出來的1個字記錄的是數組長度),其主要結構是由Mark Word 和 Class Metadata Address 組成,"},{"type":"text","text":"其結構說明如下表:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"虛擬機位數 頭對象結構 說明\n32/64bit Mark Word 存儲對象的hashCode、鎖信息或分代年齡或GC標誌等信息\n32/64bit Class Metadata Address 類型指針指向對象的類元數據,JVM通過這個指針確定該對象是哪個類的實例。\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 其中Mark Word在默認情況下存儲着對象的HashCode、分代年齡、鎖標記位等以下是32位JVM的Mark Word默認存儲結構"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"鎖狀態 25bit 4bit 1bit是否是偏向鎖 2bit 鎖標誌位\n無鎖狀態 對象HashCode 對象分代年齡 0 01\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 由於對象頭的信息是與對象自身定義的數據沒有關係的額外存儲成本,因此考慮到JVM的空間效率,Mark Word 被設計成爲一個非固定的數據結構,以便存儲更多有效的數據,它會根據對象本身的狀態複用自己的存儲空間,如32位JVM下,除了上述列出的Mark Word默認存儲結構外,還有如下可能變化的結構:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e1/e187d673558721c0e02261ecf32e4118.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.2 java重量級鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【重量級鎖】"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"說明:\n 1)java6之前的synchronized屬於重量級鎖,效率低下,因爲monitor是依賴操作系統的Mutex Lock(互斥量)來實現的。\n 2)多線程競爭鎖時,會引起線程的上下文切換(即在cpu分配的時間片還沒有用完的情況下進行了上下文切換)。\n 3)操作系統實現線程的上下文切換需要從用戶態轉換到核心態,這個狀態之間的轉換需要相對較長的時間,時間成本相對較高。\n 4)在互斥狀態下,沒有得到鎖的線程會被掛起阻塞,而掛起線程和恢復線程的操作都需要從用戶態轉入內核態中完成。\n\n優點:線程競爭不使用自旋,不會消耗cpu。\n缺點:線程阻塞,響應時間緩慢。\n應用:追求吞吐量,同步塊執行速度較長\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"輕量級鎖和偏向鎖是Java 6 對 synchronized 鎖進行優化後新增加的,重量級鎖也就是通常說synchronized的對象鎖,鎖標識位爲10,其中指針指向的是monitor對象(也稱爲管程或監視器鎖)的起始地址。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 每個對象都存在着一個 monitor 與之關聯,對象與其 monitor 之間的關係有存在多種實現方式,如monitor可以與對象一起創建銷燬或當線程試圖獲取對象鎖時自動生成,但當一個 monitor 被某個線程持有後,它便處於鎖定狀態。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 在Java虛擬機(HotSpot)中,monitor是由"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"ObjectMonitor"},{"type":"text","text":"實現的,其主要數據結構如下(位於HotSpot虛擬機源碼"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"ObjectMonitor.hpp"},{"type":"text","text":"文件,C++實現的)"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"ObjectMonitor() {\n _header = NULL;\n _count = 0; //記錄個數\n _waiters = 0,\n _recursions = 0;\n _object = NULL;\n _owner = NULL;\n _WaitSet = NULL; //處於wait狀態的線程,會被加入到_WaitSet\n _WaitSetLock = 0 ;\n _Responsible = NULL ;\n _succ = NULL ;\n _cxq = NULL ;\n FreeNext = NULL ;\n _EntryList = NULL ; //處於等待鎖block狀態的線程,會被加入到該列表\n _SpinFreq = 0 ;\n _SpinClock = 0 ;\n OwnerIsThread = 0 ;\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":" ObjectMonitor"},{"type":"text","text":"中有兩個隊列,_WaitSet 和 _EntryList,用來保存ObjectWaiter對象列表( 每個等待鎖的線程都會被封裝成ObjectWaiter對象),_owner指向持有ObjectMonitor對象的線程,當多個線程同時訪問一段同步代碼時。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 首先會進入 "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"_EntryList "},{"type":"text","text":"集合,當線程獲取到對象的"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"monitor"},{"type":"text","text":"後進入"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"_Owner"},{"type":"text","text":"區域並把monitor中的owner變量設置爲當前線程同時monitor中的計數器count加1,若線程調用 wait() 方法,將釋放當前持有的monitor,owner變量恢復爲null,count自減1,同時該線程進入 WaitSet集合中等待被喚醒。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 若當前線程執行完畢也將釋放monitor(鎖)並復位變量的值,以便其他線程進入獲取monitor(鎖)。如下圖所示:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/78/78a179f9ab10d6d03ac858f5138dfbfb.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 由此看來,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"monitor對象存在於每個Java對象的對象頭中(存儲的指針的指向)"},{"type":"text","text":","},{"type":"text","marks":[{"type":"strong"}],"text":"synchronized鎖便是"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"通過這種方式獲取鎖的,也是爲什麼Java中任意對象可以作爲鎖的原因,同時也是notify/notifyAll/wait等方法存在於頂級對象Object中的原因(關於這點稍後還會進行分析)"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"link","attrs":{"href":"https://links.jianshu.com/go?to=https%3A%2F%2Fblog.csdn.net%2Fjavazejian%2Farticle%2Fdetails%2F72828483","title":null},"content":[{"type":"text","text":"synchronized代碼塊底層原理"}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.3 偏向鎖"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"1)背景:大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,爲了讓線程獲得鎖的代價更低而引入了偏向鎖。\n2)概念:核心思想就是鎖會偏向第一個獲取它的線程,如果在接下來的執行過程中沒有其它的線程獲取該鎖,則持有偏向鎖的線程永遠不需要同步。\n3)目的:偏向鎖實際上是一種優化鎖,其目的是爲了減少數據在無競爭情況下的性能損耗。\n4)原理:\n 1>當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID。\n 2>以後該線程在進入和退出同步塊時就不需要進行CAS操作來加鎖和解鎖,只需簡單地判斷一下對象頭的Mark Word裏是否存儲着指向當前線程的偏向鎖。\n5)偏向鎖的獲取:\n 1>訪問Mark Word中偏向鎖的標識位是否爲1,如果是1,則確定爲偏向鎖。\n 說明:\n [1]如果偏向鎖的標識位爲0,說明此時是處於無鎖狀態,則當前線程通過CAS操作嘗試獲取偏向鎖,如果獲取鎖成功,則將Mark Word中的偏向線程ID設置爲當前線程ID;並且將偏向標識位設爲1。\n [2]如果偏向鎖的標識位不爲1,也不爲0(此時偏向鎖的標識位沒有值),說明發生了競爭,偏向鎖已經膨脹爲輕量級鎖,這時使用CAS操作嘗試獲得鎖。\n 2>如果是偏向鎖,則判斷Mark Word中的偏向線程ID是否指向當前線程,如果偏向線程ID指向當前線程,則表明當前線程已經獲取到了鎖;\n 3>如果偏向線程ID並未指向當前線程,則通過CAS操作嘗試獲取偏向鎖,如果獲取鎖成功,則將Mark Word中的偏向線程ID設置爲當前線程ID;\n 4>如果CAS獲取偏向鎖失敗,則表示有競爭。當到達全局安全點時(在這個時間點上沒有正在執行的字節碼),獲得偏向鎖的線程被掛起,偏向鎖升級爲輕量級鎖,然後被阻塞在安全點的線程繼續往下執行同步代碼。\n6)偏向鎖的釋放: \n 1>當其它的線程嘗試獲取偏向鎖時,持有偏向鎖的線程纔會釋放偏向鎖。 \n 2>釋放偏向鎖需要等待全局安全點(在這個時間點上沒有正在執行的字節碼)。 \n 3>過程:\n 首先暫停擁有偏向鎖的線程,然後檢查持有偏向鎖的線程是否活着,如果線程不處於活動狀態,則將對象頭設置成無鎖狀態,\n 如果線程還活着,說明此時發生了競爭,則偏向鎖升級爲輕量級鎖,然後剛剛被暫停的線程會繼續往下執行同步代碼。\n7)優點:加鎖和解鎖不需要額外的消耗,和執行非同步方法相比僅存在納秒級的差距\n8)缺點:如果線程間存在鎖競爭,鎖撤銷會帶來額外的消耗。\n9)說明:\n 1)偏向鎖默認在應用程序啓動幾秒鐘之後才激活。\n 2)可以通過設置 -XX:BiasedLockingStartupDelay=0 來關閉延遲。\n 3)可以通過設置 -XX:-UseBiasedLocking=false 來關閉偏向鎖,程序默認會進入輕量級鎖狀態。(如果應用程序裏的鎖大多情況下處於競爭狀態,則應該將偏向鎖關閉)"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.4 輕量級鎖"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"1)原理:\n 1>當使用輕量級鎖(鎖標識位爲00)時,線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中(注:鎖記錄中的標識字段稱爲Displaced Mark Word)。\n 2>將對象頭中的MarkWord複製到棧楨中的鎖記錄中之後,虛擬機將嘗試使用CAS將對象頭中Mark Word替換爲指向該線程虛擬機棧中鎖記錄的指針,此時如果沒有線程佔有鎖或者沒有線程競爭鎖,則當前線程成功獲取到鎖,然後執行同步塊中的代碼。\n 3>如果在獲取到鎖的線程執行同步代碼的過程中,另一個線程也完成了棧楨中鎖記錄的創建,並且已經將對象頭中的MarkWord複製到了自己的鎖記錄中,然後嘗試使用CAS將對象頭中的MarkWord修改爲指向自己的鎖記錄的指針,但是由於之前獲取到鎖的線程已經將對象頭中的MarkWord修改過了(並且現在還在執行同步體中的代碼,即仍然持有着鎖),所以此時對象頭中的MarkWord與當前線程鎖記錄中MarkWord的值不同,導致CAS操作失敗,然後該線程就會不停地循環使用CAS操作試圖將對象頭中的MarkWord替換爲自己鎖記錄中MarkWord的值,(當循環次數或循環時間達到上限時停止循環)如果在循環結束之前CAS操作成功,那麼該線程就可以成功獲取到鎖,如果循環結束之後依然獲取不到鎖,則鎖獲取失敗,對象頭中的MarkWord會被修改爲指向重量級鎖的指針,然後這個獲取鎖失敗的線程就會被掛起,阻塞了。\n 4>當持有鎖的那個線程執行完同步體之後,使用CAS操作將對象頭中的MarkWord還原爲最初的狀態時(將對象頭中指向鎖記錄的指針替換爲Displaced Mark Word ),發現MarkWord已被修改爲指向重量級鎖的指針,因此CAS操作失敗,該線程會釋放鎖並喚起阻塞等待的線程,開始新一輪奪鎖之爭,而此時,輕量級鎖已經膨脹爲重量級鎖,所有競爭失敗的線程都會阻塞,而不是自旋。\n 自旋鎖: \n 1)所謂自旋鎖,就是讓沒有獲得鎖的進程自己運行一段時間自循環(默認開啓),但是不掛起線程。 \n 2)自旋的代價就是該線程會一直佔用處理器如果鎖佔用的時間很短,自旋等待的效果很好,反之,自旋鎖會消耗大量處理器資源。 \n 3)因此,自旋的等待時間必須有一定限度,超過限度還沒有獲得鎖,就要掛起線程。 \n \n優點:在沒有多線程競爭的前提下,減少傳統的重量級鎖帶來的性能損耗。 \n缺點:競爭的線程如果始終得不到鎖,自旋會消耗cpu。 \n應用:追求響應時間,同步塊執行速度非常快。 \n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.5 鎖消除"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 概念:JVM在JIT編譯(即時編譯)時,通過對運行上下文的掃描,去除掉那些不可能發生共享資源競爭的鎖,從而節省了線程請求這些鎖的時間。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"舉例:"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" StringBuffer的append方法是一個同步方法,如果StringBuffer類型的變量是一個局部變量,則該變量就不會被其它線程所使用,即對局部變量的操作是不會發生線程不安全的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 在這種情景下,JVM會在JIT編譯時自動將append方法上的鎖去掉。"}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.6 鎖粗化"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"概念:將多個連續的加鎖、解鎖操作連接在一起,擴展成一個範圍更大的鎖,即將加鎖的粒度放大。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"舉例:在for循環裏的加鎖/解鎖操作,一般需要放到for循環外。"}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.7 中斷與synchronized"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 事實上線程的中斷操作對於正在等待獲取的鎖對象的synchronized方法或者代碼塊並不起作用,也就是對於synchronized來說,如果一個線程在等待鎖,那麼結果只有兩種,要麼它獲得這把鎖繼續執行,要麼它就保存等待,即使調用中斷線程的方法,也不會生效。演示代碼如下"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"public class SynchronizedBlocked implements Runnable{\n\n public synchronized void f() {\n System.out.println(\"Trying to call f()\");\n while(true) // Never releases lock\n Thread.yield();\n }\n\n /**\n * 在構造器中創建新線程並啓動獲取對象鎖\n */\n public SynchronizedBlocked() {\n //該線程已持有當前實例鎖\n new Thread() {\n public void run() {\n f(); // Lock acquired by this thread\n }\n }.start();\n }\n public void run() {\n //中斷判斷\n while (true) {\n if (Thread.interrupted()) {\n System.out.println(\"中斷線程!!\");\n break;\n } else {\n f();\n }\n }\n }\n\n\n public static void main(String[] args) throws InterruptedException {\n SynchronizedBlocked sync = new SynchronizedBlocked();\n Thread t = new Thread(sync);\n //啓動後調用f()方法,無法獲取當前實例鎖處於等待狀態\n t.start();\n TimeUnit.SECONDS.sleep(1);\n //中斷線程,無法生效\n t.interrupt();\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 我們在SynchronizedBlocked構造函數中創建一個新線程並啓動獲取調用f()獲取到當前實例鎖,由於SynchronizedBlocked自身也是線程,啓動後在其run方法中也調用了f(),但由於對象鎖被其他線程佔用,導致t線程只能等到鎖,此時我們調用了t.interrupt();但並不能中斷線程。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.8 等待喚醒機制與synchronized"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 所謂等待喚醒機制本篇主要指的是notify/notifyAll和wait方法,在使用這3個方法時,必須處於synchronized代碼塊或者synchronized方法中,否則就會拋出IllegalMonitorStateException異常,這是因爲調用這幾個方法前必須拿到當前對象的監視器monitor對象,也就是說notify/notifyAll和wait方法依賴於monitor對象,在前面的分析中,我們知道monitor 存在於對象頭的Mark Word 中(存儲monitor引用指針),而synchronized關鍵字可以獲取 monitor ,這也就是爲什麼notify/notifyAll和wait方法必須在synchronized代碼塊或者synchronized方法調用的原因。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"synchronized (obj) {\n obj.wait();\n obj.notify();\n obj.notifyAll(); \n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 需要特別理解的一點是,與sleep方法不同的是wait方法調用完成後,線程將被暫停,但wait方法將會釋放當前持有的監視器鎖("},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"monitor"},{"type":"text","text":"),直到有線程調用notify/notifyAll方法後方能繼續執行,而sleep方法只讓線程休眠並不釋放鎖。同時notify/notifyAll方法調用後,並不會馬上釋放監視器鎖,而是在相應的synchronized(){}/synchronized方法執行結束後才自動釋放鎖。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"3. synchronized不禁止指令重排序卻能保證有序性"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"synchronized是排他鎖,"},{"type":"text","marks":[{"type":"strong"}],"text":"執行前加鎖,執行完後釋放,保證單線程執行"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"滿足as-if-serial語義,保證單線程執行,單線程有序性天然存在"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#BAE7A1","name":"green"}},{"type":"strong"}],"text":"as-if-serial語義:不管怎麼重排序,單線程程序的執行結果不能被改變。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章