Java synchronized 鎖的 8 個經(jīng)典問題小結(jié)
一、前言
synchronized 的“鎖的 8 個問題”幾乎是并發(fā)面試必考。題目表面是在問“先打印短信還是郵件”,本質(zhì)考察的是:對象鎖 vs 類鎖、同一對象 vs 不同對象、靜態(tài)同步 vs 普通同步方法 等關(guān)鍵概念。下面用 8 個最典型的小實驗,把底層鎖定邏輯講清楚。
二、代碼基礎(chǔ)模板
資源類 Phone:包含兩個同步方法(鎖 this)和一個普通方法(不加鎖)。
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");
}
}
簡單試跑:
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 個問題(逐一驗證)
下列 8 個小實驗通過微調(diào)代碼,驗證不同鎖語義。為更穩(wěn)定觀察先后順序,部分用到小延遲。
(1)標準訪問:先短信還是郵件?
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é)論: 兩個同步方法、同一對象 → 同一把對象鎖(this),誰先拿鎖誰先執(zhí)行(通常按啟動時機)。
(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 先啟動拿到鎖
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
new Thread(phone::sendEmail, "BB").start();
}
}
現(xiàn)象:
發(fā)送短信
發(fā)送郵件
結(jié)論: 同一對象的兩個同步方法互斥,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)兩部手機(兩個對象),各自同步方法
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é)論: 每個實例都有獨立的對象鎖,不同對象互不干擾 → 可以并行。
(5)兩個靜態(tài)同步方法,一部手機
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)同步方法鎖的是類對象 Phone.class(類鎖),全類唯一 → 串行。
(6)兩個靜態(tài)同步方法,兩部手機
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é)論: 仍是同一個 Phone.class 的鎖,和多少實例無關(guān) → 串行。
(7)一個靜態(tài)同步 + 一個普通同步,一部手機
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)與對象鎖(this)是兩把不同的鎖 → 可并行。
(8)一個靜態(tài)同步 + 一個普通同步,兩部手機
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)與某個實例的對象鎖(p2.this)互不影響 → 并行。
四、八大結(jié)論一覽
| 編號 | 場景描述 | 鎖對象 | 是否同一鎖 | 執(zhí)行特征 |
|---|---|---|---|---|
| 1 | 同一對象兩個同步方法 | 對象鎖(this) | ? 是 | 串行 |
| 2 | 同一對象,一個方法內(nèi)睡眠 | 對象鎖(this) | ? 是 | 串行 |
| 3 | 同一對象,一個同步 + 一個普通方法 | 對象鎖 + 無鎖 | ? 否 | 普通先 |
| 4 | 兩個對象,各自同步方法 | 兩個對象鎖 | ? 否 | 并行 |
| 5 | 一對象,兩個靜態(tài)同步方法 | 類鎖(Phone.class) | ? 是 | 串行 |
| 6 | 兩對象,兩個靜態(tài)同步方法 | 類鎖(Phone.class) | ? 是 | 串行 |
| 7 | 一對象:一個靜態(tài)同步 + 一個普通同步 | 類鎖 + 對象鎖 | ? 否 | 并行 |
| 8 | 兩對象:一個靜態(tài)同步 + 一個普通同步 | 類鎖 + 對象鎖 | ? 否 | 并行 |
五、核心知識小結(jié)
- 對象鎖:synchronized 實例方法 / synchronized(this) → 鎖的是當前實例。
- 類鎖:static synchronized / synchronized(Phone.class) → 鎖的是類對象,全類唯一。
- 普通方法:不參與加鎖。
- 代碼塊鎖:synchronized(obj) 可自定義鎖粒度,取決于 obj。
六、要點
- synchronized 鎖的是對象,不是方法。
- 實例鎖與類鎖互不影響;不同實例的對象鎖也互不影響。
- 判斷是否互斥的核心是:鎖對象是否相同。
以上 8 個實驗覆蓋了 synchronized 的主流鎖粒度考點。理解“鎖定誰”這一件事,就能解釋為什么有些代碼會阻塞,有些能并行。
牢記:鎖不是給代碼加的,而是給對象加的。
到此這篇關(guān)于Java synchronized 鎖的 8 個經(jīng)典問題小結(jié)的文章就介紹到這了,更多相關(guān)Java synchronized 鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java布爾值Boolean和boolean之間轉(zhuǎn)換實例用法
在本篇文章里小編給大家整理的是一篇關(guān)于Java布爾值Boolean和boolean之間轉(zhuǎn)換實例用法內(nèi)容,有需要的朋友們跟著學習參考下。2021-06-06
關(guān)于Javaweb的轉(zhuǎn)發(fā)和重定向詳解
這篇文章主要介紹了關(guān)于Javaweb的轉(zhuǎn)發(fā)和重定向詳解,請求的轉(zhuǎn)發(fā),是指服務(wù)器收到請求后,從一個服務(wù)器端資源跳轉(zhuǎn)到同一個服務(wù)器端另外一個資源的操作,需要的朋友可以參考下2023-05-05
基于自定義BufferedReader中的read和readLine方法
下面小編就為大家分享一篇基于自定義BufferedReader中的read和readLine方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12
springmvc和js前端的數(shù)據(jù)傳遞和接收方式(兩種)
本文介紹了springmvc和js前端的數(shù)據(jù)傳遞和接收方式(兩種),詳細的介紹了兩種方式,一種是json格式傳遞,另一種是Map傳遞,具有一定的參考價值,有興趣的可以了解一下2017-12-12

