以前的文章中介紹了
JAVA
中一些併發鎖使用方法以及裏面的介紹。同時以後還介紹了字節碼的操做碼,讓你們先了解下里面的指令,我這裏也是從表面中去講解下鎖底層操做碼的實現。java
package com.montos.detail;
public class SynchronizedDemo {
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
demo.demo();
}
public void demo() {
synchronized (this) {
System.out.println("this is demo");
}
}
}
複製代碼
對其反編譯:數組
public class com.montos.detail.SynchronizedDemo {
public com.montos.detail.SynchronizedDemo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/montos/detail/SynchronizedDemo
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method demo:()V
12: return
public void demo();
Code:
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #6 // String this is demo
9: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
}
複製代碼
查看上面反編譯的結果,咱們能夠看到反編譯裏面是存在monitorenter
以及monitorexit
的操做碼,這兩個操做碼的做用就是:併發
monitorenter
:每一個對象都是一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的全部權,過程以下:monitorexit
:執行monitorexit的線程必須是objectref所對應的monitor的全部者。指令執行時,monitor的進入數減1,若是減1後進入數爲0,那線程退出monitor,再也不是這個monitor的全部者。其餘被這個monitor阻塞的線程能夠嘗試去獲取這個 monitor 的全部權。從而達到線程之間的串行執行,同時我能夠看到裏面有兩次monitorexit
操做碼:第1次爲同步正常退出釋放鎖;第2次爲發生異步退出釋放鎖;這上面鎖住的就是this。異步
public class SynchronizedDemo {
public synchronized void method() {
System.out.println("this is demo");
}
}
複製代碼
反編譯:佈局
public com.montos.detail.SynchronizedDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public synchronized void method();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String this is demo
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
複製代碼
經過上面反編譯,咱們發現沒有以前的兩個操做碼了,多出來的是有標識ACC_SYNCHRONIZED
,這裏其實也是經過上面兩個操做碼完成的。這個方法也只是比普通的方法在常量池中多了ACC_SYNCHRONIZED
字段。性能
當方法調用時,調用指令將會檢查方法的ACC_SYNCHRONIZED
訪問標誌是否被設置,若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其餘任何線程都沒法再得到同一個monitor對象。this
上面的兩種操做本質上沒有區別,只是方法的同步是一種隱式方式操做的,兩個指令的執行是JVM經過調用操做系統的互斥原語mutex來實現,被阻塞的線程會被掛起、等待從新調度,會致使「用戶態和內核態」兩個態之間來回切換,對性能有較大影響。spa
對象在內存中佈局主要有:對象頭,實例數據以及對齊填充。操作系統
Java
對象頭通常佔有2個機器碼(在32位虛擬機中,1個機器碼等於4字節,也就是32bit,在64位虛擬機中,1個機器碼是8個字節,也就是64bit),可是 若是對象是數組類型,則須要3個機器碼,由於JVM虛擬機能夠經過Java對象的元數據信息肯定Java對象的大小,可是沒法從數組的元數據來確認數組的大小,因此用一塊來記錄數組長度。
Synchronized
用的鎖就是存在Java
對象頭裏的,那麼什麼是Java
對象頭呢?Hotspot虛擬機的對象頭主要包括兩部分數據:Mark Word
(標記字段)、Class Pointer
(類型指針)。其中Class Pointer
是對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例,Mark Word
用於存儲對象自身的運行時數據,它是實現輕量級鎖和偏向鎖的關鍵。線程
這裏面咱們主要注意的是Mark Word
這個存儲結構。
每個
Java
對象建立出來就帶了一把看不見的鎖,它叫作內部鎖或者Monitor鎖
。Monitor
對象存在於每一個Java
對象的對象頭Mark Word
中(存儲的指針的指向),Synchronized
鎖即是經過這種方式獲取鎖的,也是爲何Java
中任意對象能夠做爲鎖的緣由,同時notify/notifyAll/wait
等方法會使用到Monitor
鎖對象,因此必須在同步代碼塊中使用。