java中,synchronized永遠都是鎖定的一個對象,那麼jvm是怎麼判斷一個對象是被鎖定的呢。java
Java的對象由對象頭,對象體和填充空間(Padding)組成。數組
下圖爲32bit的JVM虛擬機中,Mark Word的組成:
多線程
MarkWord經過標誌位來記錄對象當前的鎖信息。不一樣標示位的狀況下,Mark Word記錄的數據不同。jvm
無鎖狀態下,對象不具有排他性,此時全部線程均可以直接訪問這個對象。性能
偏向鎖實際上就是無鎖,此時標記字記錄的是當前操做這個對象的線程ID。線程
此時對象被鎖定,標記字指向持有當前對象的線程的地址。等待的線程會進入自旋狀態,經過CAS來爭搶鎖。指針
此時對象被鎖定,標記字指向當前對象的Monitor(對象監視器)的地址。等待的線程會進入阻塞狀態。對象
Java的鎖只會升級,不會降級。當鎖所有被釋放後,會回到初始狀態,等待再次被升級。內存
JDK6開始,JVM默認打開了偏向鎖,所以默認狀況下,一個對象被建立時,對象頭中是偏向鎖的信息。虛擬機
此時JVM默認永遠只有這個線程使用這個對象,爲了減小性能消耗,會進入偏向鎖狀態,實際上並不會上鎖。
當出現了鎖爭奪時,會升級爲輕量級鎖,此時兩個線程會嘗試修改標記字本身的線程地址,修改爲功的線程獲取到鎖,修改失敗的線程進入到自旋狀態,經過CAS操做來重試修改標記字。
此時JVM會把對象的鎖升級爲重量級鎖,標記字會指向對象的對象監視器(Monitor),全部爭奪失敗的線程進入阻塞狀態。
每一個對象都有一個對應的對象監視器,用來控制對象的多線程訪問。
多個線程競爭對象的鎖,爭奪成功的線程引用放入到Owner中,其餘線程放入到鎖池中
執行完成後,調用monitorExit,釋放鎖。
此時從鎖池中按照先進先出原則,取出下一個線程,和新進的線程(若是有的話)來爭奪鎖。
調用wait()方法,線程進入掛起狀態,線程的引用會被放入到waitSet中,從鎖池中按照先進先出原則,取出下一個線程,和新進的線程(若是有的話)來爭奪鎖。
調用notify()方法後,從waitSet中拿到一個線程(具體拿哪一個取決於JVM的配置),當即去爭奪對象的鎖,若是失敗則進入到鎖池。
調用notify()方法後,從waitSet中拿出全部線程,當即去爭奪對象的鎖,若是失敗則進入到鎖池。
除非明確知道只有一個線程出於wait狀態,不然就是用notifyAll方法,防止線程等待過久或永遠等下去。