Synchronized關(guān)鍵字的底層原理?
1. synchronized的基本使用
在現(xiàn)實場景中,搶票代碼,如果不加鎖,就會出現(xiàn)超賣或者一張票賣給多個人
Synchronized對象鎖采用互斥的方式讓同一時刻至多只有一個線程能持有對象鎖,其它線程再想獲取這個對象鎖時就會阻塞住,代碼如下
public class synchronizedTest {
// 創(chuàng)建一個靜態(tài)對象作為鎖
static Object lock = new Object();
// 初始票數(shù)
int ticketNum = 20;
// 獲取票的方法,使用 synchronized 修飾確保線程安全
public synchronized void getTicket() {
// 使用當前對象作為鎖
synchronized (this) {
// 如果票數(shù)已經(jīng)為零,則返回
if (ticketNum <= 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "搶到一張票,剩余:" + ticketNum);
// 非原子性操作,扣除一張票
ticketNum--;
}
}
public static void main(String[] args) {
// 創(chuàng)建 synchronizedTest 實例
synchronizedTest synchronizedTest = new synchronizedTest();
// 創(chuàng)建并啟動 20 個線程
for (int i = 0; i < 20; i++) {
// 調(diào)用獲取票的方法
new Thread(() -> synchronizedTest.getTicket()).start();
}
}
}
通過以上代碼,加synchronized鎖,就可以防止超賣
特別說明:synchronized 關(guān)鍵字的底層實現(xiàn)涉及到 Java 虛擬機中的監(jiān)視器(Monitor)機制。每個 Java 對象都與一個 Monitor 相關(guān)聯(lián),Monitor 負責對象的鎖定和解鎖,以及線程的阻塞和喚醒。
2. Monitor
Monitor 被翻譯為監(jiān)視器,是由jvm提供,c++語言實現(xiàn)
使用一下簡單代碼中查看monitor,通過javap命令查看clsss的字節(jié)碼
public class MonitorTest {
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock) {
counter++;
}
}
}
圖片
- monitorenter: 上鎖開始的地方
- monitorexit: 解鎖的地方
- 其中被monitorenter和monitorexit包圍住的指令就是上鎖的代碼
思考:為什么會出現(xiàn)兩個monitorexit
有兩個monitorexit的原因,第二個monitorexit是為了防止鎖住的代碼拋異常后不能及時釋放鎖在使用了synchornized代碼塊時需要指定一個對象,所以synchornized也被稱為對象鎖
monitor主要就是跟這個對象產(chǎn)生關(guān)聯(lián),如下圖
圖片
Monitor內(nèi)部具體的存儲結(jié)構(gòu):
- Owner:存儲當前獲取鎖的線程的,只能有一個線程可以獲取
- EntryList:關(guān)聯(lián)沒有搶到鎖的線程,處于Blocked狀態(tài)的線程
- WaitSet:關(guān)聯(lián)調(diào)用了wait方法的線程,處于Waiting狀態(tài)的線程
具體的流程:
- 代碼進入synchorized代碼塊,先讓lock(對象鎖)關(guān)聯(lián)的monitor,然后判斷Owner是否有線程持有
- 如果沒有線程持有,則讓當前線程持有,表示該線程獲取鎖成功
- 如果有線程持有,則讓當前線程進入entryList進行阻塞,如果Owner持有的線程已經(jīng)釋放了鎖,在EntryList中的線程去競爭鎖的持有權(quán)(非公平)
- 如果代碼塊中調(diào)用了wait()方法,則會進去WaitSet中進行等待
3.面試題
面試官:synchronized關(guān)鍵字的底層原理?
- Synchronized【對象鎖】
- 采用互斥的方式讓同一時刻至多只有一個線程能持有【對象鎖】
- 它的底層由monitor實現(xiàn)的,monitor是jvm級別的對象( C++實現(xiàn)),線程獲得鎖需要使用對象(鎖)關(guān)聯(lián)monitor
- 在monitor內(nèi)部有三個屬性,分別是owner、entrylist、waitset
- 其中owner是關(guān)聯(lián)的獲得鎖的線程,并且只能關(guān)聯(lián)一個線程;entrylist關(guān)聯(lián)的是處于阻塞狀態(tài)的線程;waitset關(guān)聯(lián)的是處于Waiting狀態(tài)的線程