Synchronized原理解析
synchronized(this) {
i++;
}
思考這三個問題?
- this對象加鎖的狀態如何記錄?
- 狀態被記錄到this對象裏面了嗎?
- 若鎖佔用,線程掛起;釋放鎖時,喚醒掛起的線程,是如何做到的?
堆中對象的存儲
代碼示例:
public class Demo5_main {
public static void main(String args[]){
int a = 1;
Teacher kody = new Teacher();
kody.stu = new Student();
}
}
class Teacher{
String name = "Kody";
int age = 40;
boolean gender = true;
Student stu;
}
class Student{
String name = "Emily";
int age = 18;
boolean gender = false;
}
在JVM中的存儲結構
- 方法中定義的局部變量都存放在虛擬機棧中的局部變量表,堆中存放的是具體的值,局部變量表通過引用指向堆中的值。
- 對象中除了存放值外,還存放了對象頭,對象頭指向方法區中具體的類信息,與堆內存中的對象進行綁定
- 如果新創建了一個String對象(String str = new String(“abc”)),str在局部變量表中通過引用指向堆內存中的引用,堆中的引用再指向方法區中的字符串常量池。
對象頭的概念
對象除了在堆中存儲值外,還開闢了一個內存區域來存放對象頭。
Class Metadata Address:就是通過這個來指向Class對象的
Array Length:數組對象的長度就存放在這裏
Mark Word:用來存放對象鎖的信息,長度爲32位或64位
Mark Word
Unlocked : 未鎖定
Light-weight locked : 輕量級鎖
Heavy-weight locked : 重量級鎖
輕量級加鎖過程原理分析
- 線程進行鎖的爭搶,會在線程獨佔內存開闢一個Lock Record(鎖記錄空間),會將對象的Mark Word(最開始的未鎖定狀態 hashcode|age|0),拷貝到Lock Record中
- Lock Record 會用**CAS(Hashcode|age|0, Lock record address)**操作對Mark Word進行更改,只有一個線程會成功修改成功,另一個CAS失敗,進行自旋
- 棧幀中的Lock Record內存中還有個owner,指向堆中對象的Mark Word,Mark Word也會指向這個owner,表示現在哪個線程佔有了這把鎖
- 當自旋達到一定次數,或者這個時候第三個線程又來爭搶鎖,鎖就會升級爲重量級鎖。(自旋是一個很消耗性能的操作)
重量級鎖加鎖原理分析
- 重量級鎖是對jvm底層的對象監視器(ObjectMonitor)來實現的,ObjectMonitor用C++來實現的
- owner :用來存放搶鎖成功的線程
- entryList:阻塞池,搶鎖失敗的線程會進入到裏面,這裏面的線程狀態爲Blocking
- waitSet:等待池,調用wait()方法就會進入到裏面,線程狀態爲waitting,進入等待池會釋放鎖
- count:synchronized是一個可重入鎖,用來記錄重入的次數
- 只有拿到對象的鎖,才能調用notify()喚醒等待池中的線程,喚醒的線程不一定會拿到鎖,所以synchronized是一個非公平鎖
通過這個原理我們可以更加深入的理解線程的6種狀態,以及調用wait()和notify()方法爲何必須要拿到對象的鎖