1. 是什么?
synchronized 是 JAVA 關(guān)鍵字,用在方法或代碼塊??刂品椒ɑ虼a同一時(shí)間只有一個(gè)線程執(zhí)行,用來解決多線程同時(shí)訪問出現(xiàn)的并發(fā)問題。
2. 使用分類(作用域)
對(duì)象鎖(代碼塊)
- 表現(xiàn)形式1:加在方法上
private synchronized boolean sellTicketsWithSyncMethod(int count) {
if (totalTickets - count < 0) {
return false;
} else {
totalTickets = totalTickets - count;
return true;
}
}
- 表現(xiàn)形式2:加在對(duì)象上
鎖多個(gè)對(duì)象時(shí),相互不干擾
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;
}
}
}
- 表現(xiàn)形式3:this當(dāng)前對(duì)象上
private boolean sellTicketsWithSyncObject(int count) {
synchronized (this) {
if (totalTickets - count < 0) {
return false;
} else {
totalTickets = totalTickets - count;
return true;
}
}
}
類鎖
- 表現(xiàn)形式1:static
public static synchronized void staticMethod() {
// todo
}
- 表現(xiàn)形式2:synchronized(xxx.class)
public Class SyncClass {
private void method () {
synchronized (SyncClass.class) {
//todo
}
}
}
對(duì)象鎖和類鎖區(qū)別?
對(duì)象鎖鎖的是一個(gè)對(duì)象。不同對(duì)象下互不干擾。而類只有一個(gè)。
3. synchronized 的特征
- 它是一個(gè)可重入鎖。
- JAVA 語言的關(guān)鍵字,由虛擬機(jī)字節(jié)碼指令實(shí)現(xiàn)。(下面深入講解)
- 用 monitor 來控制進(jìn)出
- moniterenter 進(jìn)入
- Monitorexit 為退出
- 不可中斷,無法控制鎖的超時(shí)時(shí)間,可能會(huì)造成阻塞。而 ReentranLock 可以,這個(gè)后面講解。
- synchronized 只有非公平鎖。 公平鎖和非公平鎖的區(qū)別
- 粒度是對(duì)象,鎖的是對(duì)象而非變量。
3.1 可重入講解
指的是同一線程的外層函數(shù)獲得鎖之后,內(nèi)層函數(shù)可以直接再次獲取該鎖。從而避免死鎖。
演示:
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();
// 當(dāng)然也可以重入其他類方法,這里不演示了。
}
}
4. 深入理解 synchronized (反編譯來看看)
如何反編譯?
- 編譯
javac xxxx.java - 反編譯
javap -c -p -v xxxx.class
以下是截取的部分源碼:
public class SynchronizedMethodDif {
//...
// 作用于對(duì)象上
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 ");
}
//...
}
反編譯后:
// 作用于對(duì)象上
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 原理分析
-> 作用于 代碼塊(對(duì)象) 上:
通過 monitor 來控制,使用monitorenter 和 monitorexit 指令控制線程進(jìn)出。
monitor 講解:
是一個(gè)監(jiān)視器,執(zhí)行是依賴于對(duì)象的鎖計(jì)數(shù)
-
monitorenter:每個(gè)對(duì)象都是一個(gè)監(jiān)視器鎖(monitor)。當(dāng)monitor被占用時(shí)就會(huì)處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時(shí)嘗試獲取monitor的所有權(quán),過程如下:- 如果monitor的進(jìn)入數(shù)為0,則該線程進(jìn)入monitor,然后將進(jìn)入數(shù)設(shè)置為1,該線程即為monitor的所有者
- 如果線程已經(jīng)占有該monitor,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1
- 如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0,再重新嘗試獲取monitor的所有權(quán)
-
monitorexit:執(zhí)行monitorexit的線程必須是objectref所對(duì)應(yīng)的monitor的所有者。指令執(zhí)行時(shí),monitor的進(jìn)入數(shù)減1,如果減1后進(jìn)入數(shù)為0,那線程退出monitor,不再是這個(gè)monitor的所有者。其他被這個(gè)monitor阻塞的線程可以嘗試去獲取這個(gè) monitor 的所有權(quán)。
-> 作用于 方法 上
- 方法使用 ACC_SYNCHRONIZED 標(biāo)識(shí)
- static 方法,則鎖是作用在類上
- 非 static 方法,則鎖作用在類對(duì)象上