Java synchronized 鎖的 8 個(gè)經(jīng)典問題小結(jié)
一、前言
synchronized 的“鎖的 8 個(gè)問題”幾乎是并發(fā)面試必考。題目表面是在問“先打印短信還是郵件”,本質(zhì)考察的是:對(duì)象鎖 vs 類鎖、同一對(duì)象 vs 不同對(duì)象、靜態(tài)同步 vs 普通同步方法 等關(guān)鍵概念。下面用 8 個(gè)最典型的小實(shí)驗(yàn),把底層鎖定邏輯講清楚。
二、代碼基礎(chǔ)模板
資源類 Phone:包含兩個(gè)同步方法(鎖 this)和一個(gè)普通方法(不加鎖)。
class Phone {
// 發(fā)送短信(同步方法:鎖 this)
public synchronized void sendSMS() {
System.out.println(Thread.currentThread().getName() + " → 發(fā)送短信");
}
// 發(fā)送郵件(同步方法:鎖 this)
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + " → 發(fā)送郵件");
}
// 普通方法(無鎖)
public void getHello() {
System.out.println(Thread.currentThread().getName() + " → hello");
}
}
簡(jiǎn)單試跑:
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(phone::sendSMS, "AA").start();
new Thread(phone::sendEmail, "BB").start();
}
}
三、鎖的 8 個(gè)問題(逐一驗(yàn)證)
下列 8 個(gè)小實(shí)驗(yàn)通過微調(diào)代碼,驗(yàn)證不同鎖語義。為更穩(wěn)定觀察先后順序,部分用到小延遲。
(1)標(biāo)準(zhǔn)訪問:先短信還是郵件?
class Phone {
public synchronized void sendSMS() { System.out.println("發(fā)送短信"); }
public synchronized void sendEmail() { System.out.println("發(fā)送郵件"); }
}
public class Lock8_1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(phone::sendSMS, "AA").start();
new Thread(phone::sendEmail, "BB").start();
}
}
現(xiàn)象:
發(fā)送短信
發(fā)送郵件
結(jié)論: 兩個(gè)同步方法、同一對(duì)象 → 同一把對(duì)象鎖(this),誰先拿鎖誰先執(zhí)行(通常按啟動(dòng)時(shí)機(jī))。
(2)短信方法內(nèi)停 4 秒:誰先?
class Phone {
public synchronized void sendSMS() {
try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
System.out.println("發(fā)送短信");
}
public synchronized void sendEmail() { System.out.println("發(fā)送郵件"); }
}
public class Lock8_2 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(phone::sendSMS, "AA").start();
// 小延遲,確保 AA 先啟動(dòng)拿到鎖
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
new Thread(phone::sendEmail, "BB").start();
}
}
現(xiàn)象:
發(fā)送短信
發(fā)送郵件
結(jié)論: 同一對(duì)象的兩個(gè)同步方法互斥,AA 持鎖期間 BB 必須等待 → 串行。
(3)普通方法 + 同步方法:誰先?
class Phone {
public synchronized void sendSMS() {
try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
System.out.println("發(fā)送短信");
}
public void getHello() { System.out.println("hello"); }
}
public class Lock8_3 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(phone::sendSMS, "AA").start();
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
new Thread(phone::getHello, "BB").start();
}
}
現(xiàn)象:
hello
發(fā)送短信
結(jié)論: 普通方法不加鎖,不受同步影響 → 直接執(zhí)行,通常先于同步方法的輸出。
(4)兩部手機(jī)(兩個(gè)對(duì)象),各自同步方法
public class Lock8_4 {
public static void main(String[] args) {
Phone p1 = new Phone();
Phone p2 = new Phone();
new Thread(p1::sendSMS, "AA").start();
new Thread(p2::sendEmail, "BB").start();
}
}
現(xiàn)象:
發(fā)送郵件
發(fā)送短信
結(jié)論: 每個(gè)實(shí)例都有獨(dú)立的對(duì)象鎖,不同對(duì)象互不干擾 → 可以并行。
(5)兩個(gè)靜態(tài)同步方法,一部手機(jī)
class Phone {
public static synchronized void sendSMS() {
try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
System.out.println("發(fā)送短信");
}
public static synchronized void sendEmail() { System.out.println("發(fā)送郵件"); }
}
public class Lock8_5 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(Phone::sendSMS, "AA").start();
new Thread(Phone::sendEmail, "BB").start();
}
}
現(xiàn)象:
發(fā)送短信
發(fā)送郵件
結(jié)論: 靜態(tài)同步方法鎖的是類對(duì)象 Phone.class(類鎖),全類唯一 → 串行。
(6)兩個(gè)靜態(tài)同步方法,兩部手機(jī)
public class Lock8_6 {
public static void main(String[] args) {
Phone p1 = new Phone();
Phone p2 = new Phone();
new Thread(Phone::sendSMS, "AA").start();
new Thread(Phone::sendEmail, "BB").start();
}
}
現(xiàn)象:
發(fā)送短信
發(fā)送郵件
結(jié)論: 仍是同一個(gè) Phone.class 的鎖,和多少實(shí)例無關(guān) → 串行。
(7)一個(gè)靜態(tài)同步 + 一個(gè)普通同步,一部手機(jī)
class Phone {
public static synchronized void sendSMS() {
try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
System.out.println("發(fā)送短信");
}
public synchronized void sendEmail() { System.out.println("發(fā)送郵件"); }
}
public class Lock8_7 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(Phone::sendSMS, "AA").start();
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
new Thread(phone::sendEmail, "BB").start();
}
}
現(xiàn)象:
發(fā)送郵件
發(fā)送短信
結(jié)論: 類鎖(Phone.class)與對(duì)象鎖(this)是兩把不同的鎖 → 可并行。
(8)一個(gè)靜態(tài)同步 + 一個(gè)普通同步,兩部手機(jī)
public class Lock8_8 {
public static void main(String[] args) {
Phone p1 = new Phone();
Phone p2 = new Phone();
new Thread(Phone::sendSMS, "AA").start();
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
new Thread(p2::sendEmail, "BB").start();
}
}
現(xiàn)象:
發(fā)送郵件
發(fā)送短信
結(jié)論: 類鎖(Phone.class)與某個(gè)實(shí)例的對(duì)象鎖(p2.this)互不影響 → 并行。
四、八大結(jié)論一覽
| 編號(hào) | 場(chǎng)景描述 | 鎖對(duì)象 | 是否同一鎖 | 執(zhí)行特征 |
|---|---|---|---|---|
| 1 | 同一對(duì)象兩個(gè)同步方法 | 對(duì)象鎖(this) | ? 是 | 串行 |
| 2 | 同一對(duì)象,一個(gè)方法內(nèi)睡眠 | 對(duì)象鎖(this) | ? 是 | 串行 |
| 3 | 同一對(duì)象,一個(gè)同步 + 一個(gè)普通方法 | 對(duì)象鎖 + 無鎖 | ? 否 | 普通先 |
| 4 | 兩個(gè)對(duì)象,各自同步方法 | 兩個(gè)對(duì)象鎖 | ? 否 | 并行 |
| 5 | 一對(duì)象,兩個(gè)靜態(tài)同步方法 | 類鎖(Phone.class) | ? 是 | 串行 |
| 6 | 兩對(duì)象,兩個(gè)靜態(tài)同步方法 | 類鎖(Phone.class) | ? 是 | 串行 |
| 7 | 一對(duì)象:一個(gè)靜態(tài)同步 + 一個(gè)普通同步 | 類鎖 + 對(duì)象鎖 | ? 否 | 并行 |
| 8 | 兩對(duì)象:一個(gè)靜態(tài)同步 + 一個(gè)普通同步 | 類鎖 + 對(duì)象鎖 | ? 否 | 并行 |
五、核心知識(shí)小結(jié)
- 對(duì)象鎖:synchronized 實(shí)例方法 / synchronized(this) → 鎖的是當(dāng)前實(shí)例。
- 類鎖:static synchronized / synchronized(Phone.class) → 鎖的是類對(duì)象,全類唯一。
- 普通方法:不參與加鎖。
- 代碼塊鎖:synchronized(obj) 可自定義鎖粒度,取決于 obj。
六、要點(diǎn)
- synchronized 鎖的是對(duì)象,不是方法。
- 實(shí)例鎖與類鎖互不影響;不同實(shí)例的對(duì)象鎖也互不影響。
- 判斷是否互斥的核心是:鎖對(duì)象是否相同。
以上 8 個(gè)實(shí)驗(yàn)覆蓋了 synchronized 的主流鎖粒度考點(diǎn)。理解“鎖定誰”這一件事,就能解釋為什么有些代碼會(huì)阻塞,有些能并行。
牢記:鎖不是給代碼加的,而是給對(duì)象加的。
到此這篇關(guān)于Java synchronized 鎖的 8 個(gè)經(jīng)典問題小結(jié)的文章就介紹到這了,更多相關(guān)Java synchronized 鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用spring boot 整合kafka,延遲啟動(dòng)消費(fèi)者
這篇文章主要介紹了使用spring boot 整合kafka,延遲啟動(dòng)消費(fèi)者的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
springboot集成druid,多數(shù)據(jù)源可視化,p6spy問題
這篇文章主要介紹了springboot集成druid,多數(shù)據(jù)源可視化,p6spy問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
java List出現(xiàn)All elements are null問題及解決
這篇文章主要介紹了java List出現(xiàn)All elements are null問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-11-11
Java中實(shí)現(xiàn)時(shí)間類型轉(zhuǎn)換的代碼詳解
這篇文章主要為大家詳細(xì)介紹了Java中實(shí)現(xiàn)時(shí)間類型轉(zhuǎn)換的相關(guān)方法,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下2023-09-09
Java大文本并行計(jì)算實(shí)現(xiàn)過程解析
這篇文章主要介紹了Java大文本并行計(jì)算如何實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
Java聊天室之使用Socket實(shí)現(xiàn)傳遞圖片
這篇文章主要為大家詳細(xì)介紹了Java簡(jiǎn)易聊天室之使用Socket實(shí)現(xiàn)傳遞圖片功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以了解一下2022-10-10
Java修飾符abstract與static及final的精華總結(jié)
abstract、static、final三個(gè)修飾符是經(jīng)常會(huì)使用的,對(duì)他們的概念必須非常清楚,弄混了會(huì)產(chǎn)生些完全可以避免的錯(cuò)誤,比如final和abstract不能一同出現(xiàn),static和abstract不能一同出現(xiàn),下面我們來詳細(xì)了解2022-04-04
spring中在xml配置中加載properties文件的步驟
這篇文章主要介紹了在spring中如何在xml配置中加載properties文件,本文分步驟給大家介紹在XML配置中加載properties文件的方法,需要的朋友可以參考下2023-07-07

