synchronized的工作原理(三)
- 2020 年 3 月 10 日
- 筆記
1. synchronized的鎖存儲以及鎖分類
存儲位置: 對象頭的Mark Work
- JVM的ObjectHeader資訊
- MarkWord: hashcode(哈希code) + age(分代年齡age) + biased_lock(偏向鎖標誌) + lock (鎖標誌)
- Class Metadata Address(類元資訊地址)
- Array Length: 如果對象是一個數組類型,則存儲數組長度

- 對象的Mark Word資訊

- JVM中synchronized使用的鎖
- 無鎖: 嚴格意義上應該說是正常對象,包含hashcode + 分代年齡age + 無偏向鎖標誌 + 鎖狀態標誌
- 輕量級鎖: 棧記錄的地址 + 鎖狀態
- 監視器鎖: 對象/監視器地址 + 鎖狀態
- GC標誌: GC鏈接地址等 + 鎖狀態
- 偏向鎖(JVM提供的): 當前執行的執行緒ID +支援偏向鎖標記的epoch + 分代年齡age + 偏向鎖標誌 + 鎖狀態
- JVM源碼關於MarkWord的說明
// markWord.hpp // 32 bits,32bit的MarkWord存儲資訊如下: // hash:25bit --------------->| age:4bit biased_lock:1bit lock:2bit (normal object) // JavaThread*:23bit epoch:2bit age:4bit biased_lock:1bit lock:2bit (biased object) // 64 bits, 64bit的MarkWord存儲的資訊資訊如下 // unused:25bit hash:31bit -->| unused_gap:1 age:4bit biased_lock:1bit lock:2bit (normal object) // JavaThread*:54bit epoch:2bit unused_gap:1 age:4bit biased_lock:1bit lock:2bit (biased object) // JVM底層程式碼定義的狀態鎖的值 // [ptr | 00] locked ptr points to real header on stack // [header | 0 | 01] unlocked regular object header // [ptr | 10] monitor inflated lock (header is wapped out) // [ptr | 11] marked used by markSweep to mark an object // not valid at any other time // 當前執行緒持有偏向鎖 // [JavaThread* | epoch | age | 1 | 01] lock is biased toward given thread // 匿名偏向鎖,說明當前執行緒不持有偏向鎖,但是對象已被其他執行緒設置為偏向鎖 // [0 | epoch | age | 1 | 01] lock is anonymously biased
jvm底層使用的鎖
// 源碼定義的鎖 class BasicLock // 輕量級鎖 class BasicObjectLock // 對象or監視器鎖,重量級鎖
synchronized細節問題說明
- 偏向鎖: JVM創建對象如果沒有發生競爭,則默認開啟偏向鎖,即偏向鎖標誌+無鎖狀態,如果創建對象多次(JVM經過統計)之後發現處於競爭狀態將會關閉偏向鎖,這是在JVM底層實現的,在JVM源碼中顯示為BiasedLock
- 輕量級鎖:JVM啟動並創建執行緒的時候,會在棧幀中為執行緒分配執行緒棧空間資訊,此時執行緒棧會開闢一個鎖記錄(Lock Record)空間,並且會將鎖定對象的mark word複製到鎖記錄空間中,jvm稱鎖記錄的mark word為displaced_mark_word,執行緒將通過CAS完成對象的mark word複製操作,成功則獲得鎖,失敗表示有其他鎖競爭,將會通過自旋鎖的方式獲取(不斷CAS循環獲取)
- 重量級鎖:如果上述的輕量級鎖自旋一定次數之後仍然獲取鎖失敗,便會升級稱為重量級鎖,使用重量級鎖的時候鎖將無法降級,這時候jvm提供使用快速獲取鎖的方式來實現,比如quick_enter/reenter/complete_exit,直接繞過slow-path的路徑,相當於走「捷徑」方法調用
2. synchronized加鎖原理
synchronized的enter加鎖源碼
// synchronizer.hpp // This is the "slow path" version of monitor enter and exit. // "slow path": 理解為緩慢路徑,也就是jvm會通過當前方法檢測對象是否處於競爭狀態來確定鎖的升級,以便於加快程式的性能(體現在響應時間和吞吐量) static void enter(Handle obj, BasicLock* lock, TRAPS); // 加鎖具體實現:synchronizer.cpp // 校驗是否開啟偏向鎖,默認是開啟偏向鎖的設置 if (UseBiasedLocking) { // 撤銷偏向鎖操作 if (!SafepointSynchronize::is_at_safepoint()) { // Java執行緒執行存在競爭,將當前的obj的markword設置為非偏向鎖狀態 BiasedLocking::revoke(obj, THREAD); } else { // is_at_safepoint: 程式的所有Java用戶執行緒都處於停止或者阻塞狀態,除了在VM執行緒和本地執行的Java執行緒可執行 // 阻塞所有Java執行緒,安全撤銷偏向鎖 BiasedLocking::revoke_at_safepoint(obj); } } markWord mark = obj->mark(); assert(!mark.has_bias_pattern(), "should not see bias pattern here"); if (mark.is_neutral()) { // 設置為輕量級鎖 lock->set_displaced_header(mark); // CAS自旋鎖,如果失敗將升級為重量級別鎖 if (mark == obj()->cas_set_mark(markWord::from_pointer(lock), mark)) { return; } } else if (mark.has_locker() && THREAD->is_lock_owned((address)mark.locker())) { // 鎖升級失敗,也就是不需要升級鎖,表示當前執行緒已經獲取鎖了 // 不需要再獲取相同的鎖,相當於重入鎖/鎖消除,直接設置對應的執行緒棧幀的displaced_mark_word為null assert(lock != mark.locker(), "must not re-lock the same lock"); assert(lock != (BasicLock*)obj->mark().value(), "don't relock with same BasicLock"); lock->set_displaced_header(markWord::from_pointer(NULL)); return; } // unused_mark : it is only used to be stored into BasicLock as the indicator that the lock is using heavyweight monitor // 升級為重量級鎖 lock->set_displaced_header(markWord::unused_mark()); // 鎖升級的原因為: inflate_cause_monitor_enter,執行緒發生競爭爭搶鎖 inflate(THREAD, obj(), inflate_cause_monitor_enter)->enter(THREAD);
synchronized偏向鎖的加鎖與撤銷流程

synchronized鎖升級流程(輕量級鎖升級到重量級鎖過程)

3.synchronized解鎖過程
synchronized的exit源碼
// synchronizer.hpp static void exit(oop obj, BasicLock* lock, Thread* THREAD); // 具體實現:synchronized.cpp void ObjectSynchronizer::exit(oop object, BasicLock* lock, TRAPS) { // 對於偏向鎖的處理,撤銷操作是加鎖的過程,這裡僅僅是對偏向鎖存在的驗證 // 省略對應程式碼 .... // 對markword的狀態進行診斷判讀,沒有任何其他工作, // 省略程式碼 ..... // 釋放輕量級鎖,本質上就是一個逆向恢復markword的過程 if (mark == markWord::from_pointer(lock)) { assert(dhw.is_neutral(), "invariant"); if (object->cas_set_mark(dhw, mark) == mark) { return; } } // 釋放重量級鎖,需要通過slow path的方式進行釋放鎖 // We have to take the slow-path of possible inflation and then exit. inflate(THREAD, object, inflate_cause_vm_internal)->exit(true, THREAD); }
synchronized解鎖的流程

4. 小結:JVM對synchronized的優化策略
鎖優化目的
- 提升響應時間
- 增加程式的吞吐量
- 基於上述程式碼中,jvm底層程式碼存在緩慢路徑加鎖,說明也存在更快速地加鎖方式,避免更多的加鎖處理流程,提供鎖升級的方式來走「捷徑」調用加鎖方法
優化手段
- 使用偏向鎖,如果使用資源沒有存在競爭狀態,那麼將開啟偏向鎖的方式進行加鎖,通過上述可以看到偏向鎖的流程,並無需消耗過多的資源,僅操作使用資源的markword資訊,適用於單執行緒或者是並發量不多的場景下
- 輕量級鎖:如果使用資源存在競爭狀態(有一定的並發基礎),那麼jvm底層就會關閉偏向鎖的設置,開啟使用輕量級鎖,通過將在執行緒已經開啟Lock Record記錄使用資源對象的markword資訊,同時將markword的bitfields設置為棧幀引用地址,但是輕量級鎖會出現CAS自旋消耗CPU資源,使用輕量級鎖可以在並發條件下提升響應速度
- 重量級鎖:執行緒不自旋轉,不消耗CPU資源,jvm底層同時在鎖升級之後會直接使用重量級鎖對應的加鎖和解鎖方式,也就是走「捷徑」方式調用,但是會阻塞執行緒
- 自旋鎖: 可以看到這個是在使用CAS對使用資源進行循環compare and set的操作,主要是為了防止執行緒在作業系統底層產生阻塞,採用消耗CPU的方式來不斷對使用資源進行CAS操作,但是會存在一些問題,一個是什麼時候獲取鎖是一個未知數,在jvm底層會通過計數器來設置上限,達到上限之後會採用重量級鎖的方式進行加鎖;另一個就是ABA的問題(就是執行緒無法知道使用資源是否被變更,無法辨別前後的使用資源是否一致)
- 鎖消除:在VM進行JIT編譯時,會通過上下文的掃描來消除不存在共享資源競爭的鎖,避免產生加鎖的耗時操作
感謝花時間閱讀,如果有用歡迎轉發或者點個好看,謝謝!!!