1. 應用方式:
主要有以下3種應用方式:
- 修飾實例方法,作用於當前實例加鎖,進入同步代碼前要獲得當前實例的鎖
- 修飾靜態方法,作用於當前類對象加鎖,進入同步代碼前要獲得當前類對象的鎖
- 修飾代碼塊,指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖
2. synchronized代碼塊底層原理
定義一個synchronized修飾的同步代碼塊,在代碼塊中操作共享變量i,如下
public class SyncCodeBlock {
public int i;
public void syncTask(){
//同步代碼塊
synchronized (this){
i++;
}
}
}
編譯上述代碼並使用javap反編譯後得到字節碼如下(這裏我們省略一部分沒有必要的信息):
//===========主要看看syncTask方法實現================
public void syncTask();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter //注意此處,進入同步方法
4: aload_0
5: dup
6: getfield #2 // Field i:I
9: iconst_1
10: iadd
11: putfield #2 // Field i:I
14: aload_1
15: monitorexit //注意此處,退出同步方法
16: goto 24
19: astore_2
20: aload_1
21: monitorexit //注意此處,退出同步方法
22: aload_2
23: athrow
24: return
Exception table:
//省略其他字節碼.......
}
SourceFile: "SyncCodeBlock.java"
主要關注字節碼中的如下代碼
3: monitorenter //進入同步方法
//..........省略其他
15: monitorexit //退出同步方法
16: goto 24
//省略其他.......
21: monitorexit //退出同步方法
從字節碼中可知同步語句塊的實現使用的是monitorenter 和 monitorexit 指令;
monitorenter指令
每個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:
- 如果monitor的進入數爲0,則該線程進入monitor,然後將進入數設置爲1,該線程即爲monitor的所有者。
- 如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1.
- 如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再重新嘗試獲取monitor的所有權。
monitorexit指令
執行monitorexit的線程必須是objectref(即對象鎖)所對應的monitor的所有者。
指令執行時,monitor的進入數減1,如果減1後進入數爲0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor 的所有權。
3. synchronized方法底層原理
方法級的同步是隱式,即無需通過字節碼指令來控制的,它實現在方法調用和返回操作之中。
JVM可以從方法常量池中的方法表結構(method_info Structure) 中的 ACC_SYNCHRONIZED 訪問標誌區分一個方法是否同步方法。
當方法調用時,調用指令將會 檢查方法的 ACC_SYNCHRONIZED訪問標誌是否被設置,如果設置了,執行線程將先持有monitor(虛擬機規範中用的是管程一詞),然後再執行方法,最後再方法完成(無論是正常完成還是非正常完成)時釋放monitor。在方法執行期間,執行線程持有了monitor,其他任何線程都無法再獲得同一個monitor。如果一個同步方法執行期間拋出了異常,並且在方法內部無法處理此異常,那這個同步方法所持有的monitor將在異常拋到同步方法之外時自動釋放。下面我們看看字節碼層面如何實現:
public class SyncMethod {
public int i;
public synchronized void syncTask(){
i++;
}
}
使用javap反編譯後的字節碼如下:
Classfile /***/src/main/java/com/zejian/concurrencys/SyncMethod.class
Last modified 2017-6-2; size 308 bytes
MD5 checksum f34075a8c059ea65e4cc2fa610e0cd94
Compiled from "SyncMethod.java"
public class com.hc.concurrencys.SyncMethod
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool;
//省略沒必要的字節碼
//==================syncTask方法======================
public synchronized void syncTask();
descriptor: ()V
//方法標識ACC_PUBLIC代表public修飾,ACC_SYNCHRONIZED指明該方法爲同步方法
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: return
LineNumberTable:
line 12: 0
line 13: 10
}
SourceFile: "SyncMethod.java"
參看:Synchronized及其實現原理、synchronized實現原理、http://cmsblogs.com/?p=2071