synchronized是java同步鎖,同一時刻多個執行緒對同一資源進行修改時,能夠保證同一時刻只有一個執行緒獲取到資源並對其進行修改,因此保證了執行緒安全性。
synchronized可以修飾方法和程式碼塊,底層實現的邏輯略有不同。
Object obj=new Object();
synchronized(obj){
//do soming
}
編譯後的程式碼為:
...
10 astore_2
11 monitorenter
12 aload_2
13 monitorexit
14 goto 22 (+8)
17 astore_3
18 aload_2
19 monitorexit
20 aload_3
21 athrow
22 return
當程式碼執行到synchronize(obj)
時,對應的位元組碼為monitorenter
進行加鎖操作,程式碼執行完後就是monitorexit
進行鎖的釋放。兩個 monitorexit
是正常退出和異常退出兩種情況下鎖的釋放。
public synchronized void test1(){
//do somthing
}
當修飾方法時是在編譯後的位元組碼上加上了synchronized
的存取標識
Monitor是一種同步機制,它的作用是保證同一時刻只有一個執行緒能存取到受保護的資源,JVM中的同步是基於進入和退出監視物件來實現的,是synchronized
的底層實現,每個物件範例都是一個Montor物件,Monitor對應的是底層的MonitorObject,是基於作業系統的互斥mutex
實現的。
ObjectMonitor中有幾個關鍵屬性
屬性 | 描述 |
---|---|
_owner | 指向持有ObjectMonitor物件的執行緒 |
_WaitSet | 存放處於wait狀態的執行緒佇列 |
_EntryList | 存放處於等待鎖block狀態的執行緒佇列 |
_recursions | 鎖的重入次數 |
_count | 用來記錄該執行緒獲取鎖的次數 |
Entry List
中,等待持有鎖的執行緒釋放鎖,owner
指向當前執行緒wait
時進入Wait Set
,等待鎖的持有者進行喚醒。synchronized
修飾的程式碼塊或方法時,首先通過monitor
去獲取物件範例的鎖物件頭
上新增鎖標識位entry list
中進行等待物件頭
上鎖標識位來進行釋放鎖wait
操作時,當前也會釋放鎖,然後進行wait set
區等待被喚醒entry list
中處理等待的執行緒再次進行鎖的競爭一個物件的建立要經過這幾步:
物件頭
、範例資料
和對齊填充
三部分組成物件標記(Mark Word)
和型別指標
組成,如果物件是陣列,物件頭中還有陣列的長度
hashcode碼
、GC年齡
、鎖標記
組成無鎖
的狀態下,前25位沒有使用,緊接著的32位元儲存了物件的hashcode
,在1位未使用,後面的4位元物件的GC年齡
,後面的3位是鎖標記位。在MarkWord中可以看出GC年齡標記只有4位元,二進位制表示就是:1111
,對應的十進位制就是15。
下面通過jol
進行檢視MarkWord的資訊,
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
import org.openjdk.jol.info.ClassLayout;
public class MarkWordTest {
public static void main(String[] args) {
Hummy hummy=new Hummy();
int hashCode = hummy.hashCode();
System.out.println(hashCode);
System.out.println("二進位制:"+Integer.toBinaryString(hashCode));
System.out.println("十六進位制: "+Integer.toHexString(hashCode));
System.out.println(ClassLayout.parseInstance(hummy).toPrintable());
}
}
class Hummy{}
列印出的結果如下:
可以看到物件的hashcode是:6f496d9f
,可以在左邊的Value的找到hashcode值,只不過是反過來的。
最後1位元組的00000001
包含了gc年齡和鎖標記位。
import org.openjdk.jol.info.ClassLayout;
public class MarkWordTest {
public static void main(String[] args) {
//java -XX:BiasedLockingStartupDelay=0
Hummy hummy=new Hummy();
synchronized (hummy){
System.out.println(ClassLayout.parseInstance(hummy).toPrintable());
}
}
}
class Hummy{}
最後一個00000101
的最後3位101
表示偏向鎖
jdk1.6之前只有重量級鎖,面在java1.6之後對synchronized的鎖進行了優化,有偏向鎖、輕量級鎖、重量級鎖,主要是因為重量級鎖需要用到作業系統mutex
,作業系統實現執行緒之間的切換需要從使用者態到核心態的,成本非常高。
鎖 | 鎖標識 | 場景 |
---|---|---|
無鎖 | 001 | 不受保護時 |
偏向鎖 | 101 | 只有一個線競爭時 |
輕量級鎖 | 00 | 競爭不激烈時 |
重量級鎖 | 10 | 競爭非常激烈 |
鎖升級的過程:
無鎖狀態(001)
或者在偏向鎖狀態下markword中的執行緒id與當前執行緒id是否一樣,如果是則把當前執行緒id通過CAS的方式設定到markword中編向鎖(101)
,執行同步內的方法安全點
時,檢查偏向鎖的狀態已退出同步方法
時,釋放原執行緒持有的鎖,變成無鎖狀態,到1處執行還在同步程式碼
中,則升級鎖為輕量級鎖(00)
,當前執行緒持有,另個執行緒通過CAS的方法進行獲取鎖,當自旋到一定次數(20)時,則升級為重量級鎖(10)
,進入堵塞狀態。