1. 前言
synchronized在我們的程式中非常的常見,主要是為了解決多個執行緒搶佔同一個資源。那麼我們知道synchronized有多種用法,以下從實踐出發,題目由簡入深,看你能答對幾道題目?
2. 問題
呼叫程式碼如下
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024));
SyncTest st = new SyncTest();
// 注意是不同方法
executor.submit(() -> st.sync1_1());
executor.submit(() -> st.sync1_2());
executor.shutdown();
}
問題2.1
鎖lock全域性物件,會輸出什麼?
public static final Object LOCK = new Object();
public void sync1_1() {
// 鎖住lock物件
synchronized (LOCK) {
sleep("sync1_1");
}
}
public void sync1_2() {
// 鎖住lock物件
synchronized (LOCK) {
sleep("sync1_2");
}
}
public static void sleep(String name) {
try {
log.info(name + " get lock");
TimeUnit.SECONDS.sleep(1);
log.info(name + " release lock");
} catch (Exception e) {}
}
點選檢視答案(請思考後在點選檢視)
[INFO 2023-04-14 15:12:46.639] [pool-2-thread-1] [] - [SyncTest.java.sleep:86] [sync1_1 get lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-1] [] - [SyncTest.java.sleep:88] [sync1_1 release lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:86] [sync1_2 get lock]
[INFO 2023-04-14 15:12:48.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:88] [sync1_2 release lock]
等待執行緒A執行完成後,執行緒B才能執行,否則阻塞。符合我們預期。鎖的資源就是我們所謂的LOCK物件
問題2.2
鎖this物件,會輸出什麼?this是代表什麼?
public void sync2_1() {
synchronized (this) {
sleep("sync2_1");
}
}
public void sync2_2() {
synchronized (this) {
sleep("sync2_2");
}
}
點選檢視答案(請思考後在點選檢視)
[INFO 2023-04-14 15:12:46.639] [pool-2-thread-1] [] - [SyncTest.java.sleep:86] [sync2_1 get lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-1] [] - [SyncTest.java.sleep:88] [sync2_1 release lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:86] [sync2_2 get lock]
[INFO 2023-04-14 15:12:48.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:88] [sync2_2 release lock]
等待執行緒A執行完成後,執行緒B才能執行,否則阻塞。符合我們預期。鎖的是呼叫方,SyncTest st = new SyncTest(); 中的st物件。 st是SyncTest類的一個物件。也就是鎖的這個this資源
問題2.3
鎖方法,會輸出什麼? 鎖的又是什麼資源?
public synchronized void sync3_1() {
sleep("sync3_1");
}
public synchronized void sync3_2() {
sleep("sync3_2");
}
點選檢視答案(請思考後在點選檢視)
結論同 問題2.2問題2.4
鎖static方法,會輸出什麼? 鎖的又是什麼資源?
public static synchronized void sync4_1() {
sleep("sync4_1");
}
public static synchronized void sync4_2() {
sleep("sync4_2");
}
點選檢視答案(請思考後在點選檢視)
[INFO 2023-04-14 15:12:46.639] [pool-2-thread-1] [] - [SyncTest.java.sleep:86] [sync4_1 get lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-1] [] - [SyncTest.java.sleep:88] [sync4_1 release lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:86] [sync4_2 get lock]
[INFO 2023-04-14 15:12:48.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:88] [sync4_2 release lock]
等待執行緒A執行完成後,執行緒B才能執行,否則阻塞。符合我們預期。因為是在static上面加鎖,而static方法即是類方法,因此他鎖的是這個類,也就是對SyncTest這個this class加的鎖
問題2.5
鎖類的class,會輸出什麼? 鎖的又是什麼資源?
public void sync5_1() {
synchronized (SyncTest.class) {
sleep("sync5_1");
}
}
public void sync5_2() {
synchronized (SyncTest.class) {
sleep("sync5_2");
}
}
點選檢視答案(請思考後在點選檢視)
結論同問題2.43. 問題(修改呼叫方式)
其他全部程式碼不改變,僅修改呼叫方式。具體程式碼如下
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024));
// 這個是new了一個st1
SyncTest st1 = new SyncTest();
executor.submit(() -> st1.sync1_1());
// 這裡new了一個st2
SyncTest st2 = new SyncTest();
executor.submit(() -> st2.sync1_2());
executor.shutdown();
}
其他全部不變,問題從2.1 - 2.5重新全部呼叫一遍。結果又是否相同。
注意一下:呼叫方為 new 了兩個st1以及st2, 如果你完全理解上述問題,這個問題就非常簡單了。我們以問題1以及問題2為例:
問題2.1
[INFO 2023-04-14 15:12:46.639] [pool-2-thread-1] [] - [SyncTest.java.sleep:86] [sync1_1 get lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-1] [] - [SyncTest.java.sleep:88] [sync1_1 release lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:86] [sync1_2 get lock]
[INFO 2023-04-14 15:12:48.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:88] [sync1_2 release lock]
這是由於無論new幾個st,鎖的永遠是唯一資源lock,因此結論不變
問題2.2
[INFO 2023-04-14 15:26:31.676] [pool-2-thread-2] [] - [SyncTest.java.sleep:86] [sync2_1 get lock]
[INFO 2023-04-14 15:26:31.676] [pool-2-thread-1] [] - [SyncTest.java.sleep:86] [sync2_1 get lock]
[INFO 2023-04-14 15:26:32.688] [pool-2-thread-1] [] - [SyncTest.java.sleep:88] [sync2_1 release lock]
[INFO 2023-04-14 15:26:32.688] [pool-2-thread-2] [] - [SyncTest.java.sleep:88] [sync2_1 release lock]
這個結果就非常有意思了。注意看,執行緒B並沒有阻塞,而是直接獲取到了這個資源。也就是synchronized失效了。我們來分析一下為什麼synchronized失效了?
我們知道在問題2中,synchronized鎖的是this物件,而這個this物件分別為st1, 以及st2。那麼是不是就是兩個資源了。如果是兩個資源,就不存在互斥的作用了,也就是不會相互爭奪資源。
請各位讀者自己分析問題2.3 - 2.5的第二種程式碼呼叫方式的結果。
4. 結論
synchronized是我們工程中常用的一個方法。但對於其用法,如果深究,還是有非常多意外的驚喜。如果小夥伴有其他問題,隨時歡迎交流討論