<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                我在[上一講](http://time.geekbang.org/column/article/8799)對比和分析了 synchronized 和 ReentrantLock,算是專欄進入并發編程階段的熱身,相信你已經對線程安全,以及如何使用基本的同步機制有了基礎,今天我們將深入了解 synchronize 底層機制,分析其他鎖實現和應用場景。 今天我要問你的問題是 ,synchronized 底層如何實現?什么是鎖的升級、降級? ## 典型回答 在回答這個問題前,先簡單復習一下上一講的知識點。synchronized 代碼塊是由一對兒 monitorenter/monitorexit 指令實現的,Monitor 對象是同步的基本實現[單元](https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#d5e13622)。 在 Java 6 之前,Monitor 的實現完全是依靠操作系統內部的互斥鎖,因為需要進行用戶態到內核態的切換,所以同步操作是一個無差別的重量級操作。 現代的(Oracle)JDK 中,JVM 對此進行了大刀闊斧地改進,提供了三種不同的 Monitor 實現,也就是常說的三種不同的鎖:偏斜鎖(Biased Locking)、輕量級鎖和重量級鎖,大大改進了其性能。 所謂鎖的升級、降級,就是 JVM 優化 synchronized 運行的機制,當 JVM 檢測到不同的競爭狀況時,會自動切換到適合的鎖實現,這種切換就是鎖的升級、降級。 當沒有競爭出現時,默認會使用偏斜鎖。JVM 會利用 CAS 操作([compare and swap](https://en.wikipedia.org/wiki/Compare-and-swap)),在對象頭上的 Mark Word 部分設置線程 ID,以表示這個對象偏向于當前線程,所以并不涉及真正的互斥鎖。這樣做的假設是基于在很多應用場景中,大部分對象生命周期中最多會被一個線程鎖定,使用偏斜鎖可以降低無競爭開銷。 如果有另外的線程試圖鎖定某個已經被偏斜過的對象,JVM 就需要撤銷(revoke)偏斜鎖,并切換到輕量級鎖實現。輕量級鎖依賴 CAS 操作 Mark Word 來試圖獲取鎖,如果重試成功,就使用普通的輕量級鎖;否則,進一步升級為重量級鎖。 我注意到有的觀點認為 Java 不會進行鎖降級。實際上據我所知,鎖降級確實是會發生的,當 JVM 進入安全點([SafePoint](http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html))的時候,會檢查是否有閑置的 Monitor,然后試圖進行降級。 ## 考點分析 今天的問題主要是考察你對 Java 內置鎖實現的掌握,也是并發的經典題目。我在前面給出的典型回答,涵蓋了一些基本概念。如果基礎不牢,有些概念理解起來就比較晦澀,我建議還是盡量理解和掌握,即使有不懂的也不用擔心,在后續學習中還會逐步加深認識。 我個人認為,能夠基礎性地理解這些概念和機制,其實對于大多數并發編程已經足夠了,畢竟大部分工程師未必會進行更底層、更基礎的研發,很多時候解決的是知道與否,真正的提高還要靠實踐踩坑。 后面我會進一步分析: * 從源碼層面,稍微展開一些 synchronized 的底層實現,并補充一些上面答案中欠缺的細節,有同學反饋這部分容易被問到。如果你對 Java 底層源碼有興趣,但還沒有找到入手點,這里可以成為一個切入點。 * 理解并發包中 java.util.concurrent.lock 提供的其他鎖實現,畢竟 Java 可不是只有 ReentrantLock 一種顯式的鎖類型,我會結合代碼分析其使用。 ## 知識擴展 我在[上一講](http://time.geekbang.org/column/article/8799)提到過 synchronized 是 JVM 內部的 Intrinsic Lock,所以偏斜鎖、輕量級鎖、重量級鎖的代碼實現,并不在核心類庫部分,而是在 JVM 的代碼中。 Java 代碼運行可能是解釋模式也可能是編譯模式(如果不記得,請復習[專欄第 1 講](http://time.geekbang.org/column/article/6845)),所以對應的同步邏輯實現,也會分散在不同模塊下,比如,解釋器版本就是: [src/hotspot/share/interpreter/interpreterRuntime.cpp](http://hg.openjdk.java.net/jdk/jdk/file/6659a8f57d78/src/hotspot/share/interpreter/interpreterRuntime.cpp) 為了簡化便于理解,我這里會專注于通用的基類實現: [src/hotspot/share/runtime/](http://hg.openjdk.java.net/jdk/jdk/file/6659a8f57d78/src/hotspot/share/runtime/) 另外請注意,鏈接指向的是最新 JDK 代碼庫,所以可能某些實現與歷史版本有所不同。 首先,synchronized 的行為是 JVM runtime 的一部分,所以我們需要先找到 Runtime 相關的功能實現。通過在代碼中查詢類似“monitor\_enter”或“Monitor Enter”,很直觀的就可以定位到: * [sharedRuntime.cpp](http://hg.openjdk.java.net/jdk/jdk/file/6659a8f57d78/src/hotspot/share/runtime/sharedRuntime.cpp)/hpp,它是解釋器和編譯器運行時的基類。 * [synchronizer.cpp](https://hg.openjdk.java.net/jdk/jdk/file/896e80158d35/src/hotspot/share/runtime/synchronizer.cpp)/hpp,JVM 同步相關的各種基礎邏輯。 在 sharedRuntime.cpp 中,下面代碼體現了 synchronized 的主要邏輯。 ~~~ Handle h_obj(THREAD, obj); if (UseBiasedLocking) { // Retry fast entry if bias is revoked to avoid unnecessary inflation ObjectSynchronizer::fast_enter(h_obj, lock, true, CHECK); } else { ObjectSynchronizer::slow_enter(h_obj, lock, CHECK); } ~~~ 其實現可以簡單進行分解: * UseBiasedLocking 是一個檢查,因為,在 JVM 啟動時,我們可以指定是否開啟偏斜鎖。 偏斜鎖并不適合所有應用場景,撤銷操作(revoke)是比較重的行為,只有當存在較多不會真正競爭的 synchronized 塊兒時,才能體現出明顯改善。實踐中對于偏斜鎖的一直是有爭議的,有人甚至認為,當你需要大量使用并發類庫時,往往意味著你不需要偏斜鎖。從具體選擇來看,我還是建議需要在實踐中進行測試,根據結果再決定是否使用。 還有一方面是,偏斜鎖會延緩 JIT 預熱的進程,所以很多性能測試中會顯式地關閉偏斜鎖,命令如下: ~~~ -XX:-UseBiasedLocking ~~~ * fast\_enter 是我們熟悉的完整鎖獲取路徑,slow\_enter 則是繞過偏斜鎖,直接進入輕量級鎖獲取邏輯。 那么 fast\_enter 是如何實現的呢?同樣是通過在代碼庫搜索,我們可以定位到 synchronizer.cpp。 類似 fast\_enter 這種實現,解釋器或者動態編譯器,都是拷貝這段基礎邏輯,所以如果我們修改這部分邏輯,要保證一致性。這部分代碼是非常敏感的,微小的問題都可能導致死鎖或者正確性問題。 ~~~ void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) { if (UseBiasedLocking) { if (!SafepointSynchronize::is_at_safepoint()) { BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD); if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) { return; } } else { assert(!attempt_rebias, "can not rebias toward VM thread"); BiasedLocking::revoke_at_safepoint(obj); } assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now"); } slow_enter(obj, lock, THREAD); } ~~~ 我來分析下這段邏輯實現: * [biasedLocking](http://hg.openjdk.java.net/jdk/jdk/file/6659a8f57d78/src/hotspot/share/runtime/biasedLocking.cpp)定義了偏斜鎖相關操作,revoke\_and\_rebias 是獲取偏斜鎖的入口方法,revoke\_at\_safepoint 則定義了當檢測到安全點時的處理邏輯。 * 如果獲取偏斜鎖失敗,則進入 slow\_enter。 * 這個方法里面同樣檢查是否開啟了偏斜鎖,但是從代碼路徑來看,其實如果關閉了偏斜鎖,是不會進入這個方法的,所以算是個額外的保障性檢查吧。 另外,如果你仔細查看[synchronizer.cpp](https://hg.openjdk.java.net/jdk/jdk/file/896e80158d35/src/hotspot/share/runtime/synchronizer.cpp)里,會發現不僅僅是 synchronized 的邏輯,包括從本地代碼,也就是 JNI,觸發的 Monitor 動作,全都可以在里面找到(jni\_enter/jni\_exit)。 關于[biasedLocking](http://hg.openjdk.java.net/jdk/jdk/file/6659a8f57d78/src/hotspot/share/runtime/biasedLocking.cpp)的更多細節我就不展開了,明白它是通過 CAS 設置 Mark Word 就完全夠用了,對象頭中 Mark Word 的結構,可以參考下圖: ![](https://img.kancloud.cn/b1/22/b1221c308d2aaf13d0d677033ee406fc_1005x314.png) 順著鎖升降級的過程分析下去,偏斜鎖到輕量級鎖的過程是如何實現的呢? 我們來看看 slow\_enter 到底做了什么。 ~~~ void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) { markOop mark = obj->mark(); if (mark->is_neutral()) { // 將目前的 Mark Word 復制到 Displaced Header 上 lock->set_displaced_header(mark); // 利用 CAS 設置對象的 Mark Word if (mark == obj()->cas_set_mark((markOop) lock, mark)) { TEVENT(slow_enter: release stacklock); return; } // 檢查存在競爭 } else if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) { // 清除 lock->set_displaced_header(NULL); return; } // 重置 Displaced Header lock->set_displaced_header(markOopDesc::unused_mark()); ObjectSynchronizer::inflate(THREAD, obj(), inflate_cause_monitor_enter)->enter(THREAD); } ~~~ 請結合我在代碼中添加的注釋,來理解如何從試圖獲取輕量級鎖,逐步進入鎖膨脹的過程。你可以發現這個處理邏輯,和我在這一講最初介紹的過程是十分吻合的。 * 設置 Displaced Header,然后利用 cas\_set\_mark 設置對象 Mark Word,如果成功就成功獲取輕量級鎖。 * 否則 Displaced Header,然后進入鎖膨脹階段,具體實現在 inflate 方法中。 今天就不介紹膨脹的細節了,我這里提供了源代碼分析的思路和樣例,考慮到應用實踐,再進一步增加源代碼解讀意義不大,有興趣的同學可以參考我提供的[synchronizer.cpp](hg.openjdk.java.net/jdk/jdk/file/896e80158d35/src/hotspot/share/runtime/synchronizer.cpp)鏈接,例如: * **deflate\_idle\_monitors**是分析**鎖降級**邏輯的入口,這部分行為還在進行持續改進,因為其邏輯是在安全點內運行,處理不當可能拖長 JVM 停頓(STW,stop-the-world)的時間。 * fast\_exit 或者 slow\_exit 是對應的鎖釋放邏輯。 前面分析了 synchronized 的底層實現,理解起來有一定難度,下面我們來看一些相對輕松的內容。 我在上一講對比了 synchronized 和 ReentrantLock,Java 核心類庫中還有其他一些特別的鎖類型,具體請參考下面的圖。 ![](https://img.kancloud.cn/f5/75/f5753a4695fd771f8178120858086811_751x310.png) 你可能注意到了,這些鎖竟然不都是實現了 Lock 接口,ReadWriteLock 是一個單獨的接口,它通常是代表了一對兒鎖,分別對應只讀和寫操作,標準類庫中提供了再入版本的讀寫鎖實現(ReentrantReadWriteLock),對應的語義和 ReentrantLock 比較相似。 StampedLock 竟然也是個單獨的類型,從類圖結構可以看出它是不支持再入性的語義的,也就是它不是以持有鎖的線程為單位。 為什么我們需要讀寫鎖(ReadWriteLock)等其他鎖呢? 這是因為,雖然 ReentrantLock 和 synchronized 簡單實用,但是行為上有一定局限性,通俗點說就是“太霸道”,要么不占,要么獨占。實際應用場景中,有的時候不需要大量競爭的寫操作,而是以并發讀取為主,如何進一步優化并發操作的粒度呢? Java 并發包提供的讀寫鎖等擴展了鎖的能力,它所基于的原理是多個讀操作是不需要互斥的,因為讀操作并不會更改數據,所以不存在互相干擾。而寫操作則會導致并發一致性的問題,所以寫線程之間、讀寫線程之間,需要精心設計的互斥邏輯。 下面是一個基于讀寫鎖實現的數據結構,當數據量較大,并發讀多、并發寫少的時候,能夠比純同步版本凸顯出優勢。 ~~~ public class RWSample { private final Map<String, String> m = new TreeMap<>(); private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); public String get(String key) { r.lock(); System.out.println(" 讀鎖鎖定!"); try { return m.get(key); } finally { r.unlock(); } } public String put(String key, String entry) { w.lock(); System.out.println(" 寫鎖鎖定!"); try { return m.put(key, entry); } finally { w.unlock(); } } // … } ~~~ 在運行過程中,如果讀鎖試圖鎖定時,寫鎖是被某個線程持有,讀鎖將無法獲得,而只好等待對方操作結束,這樣就可以自動保證不會讀取到有爭議的數據。 讀寫鎖看起來比 synchronized 的粒度似乎細一些,但在實際應用中,其表現也并不盡如人意,主要還是因為相對比較大的開銷。 所以,JDK 在后期引入了 StampedLock,在提供類似讀寫鎖的同時,還支持優化讀模式。優化讀基于假設,大多數情況下讀操作并不會和寫操作沖突,其邏輯是先試著讀,然后通過 validate 方法確認是否進入了寫模式,如果沒有進入,就成功避免了開銷;如果進入,則嘗試獲取讀鎖。請參考我下面的樣例代碼。 ~~~ public class StampedSample { private final StampedLock sl = new StampedLock(); void mutate() { long stamp = sl.writeLock(); try { write(); } finally { sl.unlockWrite(stamp); } } Data access() { long stamp = sl.tryOptimisticRead(); Data data = read(); if (!sl.validate(stamp)) { stamp = sl.readLock(); try { data = read(); } finally { sl.unlockRead(stamp); } } return data; } // … } ~~~ 注意,這里的 writeLock 和 unLockWrite 一定要保證成對調用。 你可能很好奇這些顯式鎖的實現機制,Java 并發包內的各種同步工具,不僅僅是各種 Lock,其他的如[Semaphore](https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/Semaphore.html)、[CountDownLatch](https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/CountDownLatch.html),甚至是早期的[FutureTask](https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/FutureTask.html)等,都是基于一種[AQS](https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/locks/AbstractQueuedSynchronizer.html)框架。 今天,我全面分析了 synchronized 相關實現和內部運行機制,簡單介紹了并發包中提供的其他顯式鎖,并結合樣例代碼介紹了其使用方法,希望對你有所幫助。 ## 一課一練 關于今天我們討論的你做到心中有數了嗎?思考一個問題,你知道“自旋鎖”是做什么的嗎?它的使用場景是什么?
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看