文章目錄
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 ");
}
//...
}
反編譯後:
關注 monitorenter、monitorexit、ACC_SYNCHRONIZED 這三個指令。
// 作用於對象上
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 方法,則鎖作用在類對象上
喜歡就點個讚唄~