1. 是什麼?
synchronized 是 JAVA 關鍵字,用在方法或程式碼塊。控制方法或程式碼同一時間只有一個執行緒執行,用來解決多執行緒同時訪問出現的併發問題。
2. 使用分類(作用域)
物件鎖(程式碼塊)
- 表現形式1:加在方法上
private synchronized boolean sellTicketsWithSyncMethod(int count) {
if (totalTickets - count < 0) {
return false;
} else {
totalTickets = totalTickets - count;
return true;
}
}
複製程式碼
- 表現形式2:加在物件上
鎖多個物件時,相互不干擾
private final Object LOCK = new Object();
private boolean sellTicketsWithSyncObject(int count) {
synchronized (LOCK) {
if (totalTickets - count < 0) {
return false;
} else {
totalTickets = totalTickets - count;
return true;
}
}
}
複製程式碼
- 表現形式3:this當前物件上
private boolean sellTicketsWithSyncObject(int count) {
synchronized (this) {
if (totalTickets - count < 0) {
return false;
} else {
totalTickets = totalTickets - count;
return true;
}
}
}
複製程式碼
類鎖
- 表現形式1:static
public static synchronized void staticMethod() {
// todo
}
複製程式碼
- 表現形式2:synchronized(xxx.class)
public Class SyncClass {
private void method () {
synchronized (SyncClass.class) {
//todo
}
}
}
複製程式碼
物件鎖和類鎖區別?
物件鎖鎖的是一個物件。不同物件下互不干擾。而類只有一個。
3. synchronized 的特徵
- 它是一個可重入鎖。
- JAVA 語言的關鍵字,由虛擬機器位元組碼指令實現。(下面深入講解)
- 用 monitor 來控制進出
- moniterenter 進入
- Monitorexit 為退出
- 不可中斷,無法控制鎖的超時時間,可能會造成阻塞。而 ReentranLock 可以,這個後面講解。
- synchronized 只有非公平鎖。 公平鎖和非公平鎖的區別
- 粒度是物件,鎖的是物件而非變數。
3.1 可重入講解
指的是同一執行緒的外層函式獲得鎖之後,內層函式可以直接再次獲取該鎖。從而避免死鎖。
演示:
public class SynchronizedReentrantTest {
int i = 0;
public synchronized void method1() {
method2();
}
private synchronized void method2() {
}
private synchronized void reentrantSelf() {
if (i == 0) {
i++;
reentrantSelf();
}
}
public static void main(String[] args) {
SynchronizedReentrantTest instance = new SynchronizedReentrantTest();
// 重入其他方法
instance.method1();
// 自己重入自己
instance.reentrantSelf();
// 當然也可以重入其他類方法,這裡不演示了。
}
}
複製程式碼
4. 深入理解 synchronized (反編譯來看看)
如何反編譯?
- 編譯
javac xxxx.java
- 反編譯
javap -c -p -v xxxx.class
以下是擷取的部分原始碼:
public class SynchronizedMethodDif {
//...
// 作用於物件上
private static final Object LOCK = new Object();
private static boolean sellTicketsWithSyncObject(int count) {
synchronized (LOCK) {
if (totalTickets - count < 0) {
return false;
} else {
totalTickets = totalTickets - count;
return true;
}
}
}
// 作用於 static 方法上
private static synchronized void soutStatic() {
System.out.println(Thread.currentThread().getName() + " is sleeping ");
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : 1");
System.out.println(Thread.currentThread().getName() + " finish sleeping ");
}
// 作用於普通方法上
private synchronized void soutNonStatic() {
System.out.println(Thread.currentThread().getName() + " is sleeping ");
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : 1");
System.out.println(Thread.currentThread().getName() + " finish sleeping ");
}
//...
}
複製程式碼
反編譯後:
// 作用於物件上
private static boolean sellTicketsWithSyncObject(int);
descriptor: (I)Z
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #10 // Field LOCK:Ljava/lang/Object;
3: dup
4: astore_1
5: monitorenter
...
16: monitorexit
17: ireturn
...
27: aload_1
28: monitorexit
29: ireturn
...
31: aload_1
32: monitorexit
33: aload_2
34: athrow
...
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
...
// 作用於 static 上
private static synchronized void soutStatic();
descriptor: ()V
flags: (0x002a) ACC_PRIVATE, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
...
frame_type = 4 /* same */
...
// 作用於 普通方法 上
private synchronized void soutNonStatic();
descriptor: ()V
flags: (0x0022) ACC_PRIVATE, ACC_SYNCHRONIZED
Code:
stack=2, locals=2, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
...
frame_type = 4 /* same */
複製程式碼
4.1 原理分析
-> 作用於 程式碼塊(物件) 上:
通過 monitor 來控制,使用monitorenter 和 monitorexit 指令控制執行緒進出。
monitor 講解:
是一個監視器,執行是依賴於物件的鎖計數
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 的所有權。
-> 作用於 方法 上
- 方法使用 ACC_SYNCHRONIZED 標識
- static 方法,則鎖是作用在類上
- 非 static 方法,則鎖作用在類物件上