Java - JVM - 監視器鎖 與 等待隊列


  1. 概述

    1. jvm 監視器鎖 與 等待隊列
    2. 初版, 目前來看, 還是一個 生硬的總結
      1. 后續會做調整
  2. 背景

    1. 之前講了 synchronized
    2. 但是其中的原理, 並沒有講
    3. 這些是定義在 java 內存模型 里的

1. 回顧: synchronized

  1. 概述

    1. 回顧之前的內容
  2. 格式

    1. 方法

      # 后面簡稱 同步方法
      public static synchronized void method() {}
      public synchronized void method() {}
      
    2. 代碼塊

      # 后面簡稱 同步代碼塊
      synchronized(obj) {}
      
  3. 作用

    1. 通過一個對象, 來限定 synchronized 范圍內的代碼塊
      1. 獲取對象鎖的線程, 可以執行代碼塊內容
      2. 其他線程, 需要等到 對象鎖 被釋放, 才有機會執行
  4. 所以

    1. 這個鎖, 到底是個 什么情況
      1. 監視器鎖

2. 監視器鎖

  1. 概述

    1. 監視器鎖
  2. 監視器鎖

    1. 概述

      1. 一種 同步機制
    2. 機制

      1. 對象

        1. 每個對象, 都有一個 監視器鎖
      2. 線程

        1. 持有

          1. 線程獲取 監視器鎖 成功, 則稱 線程持有鎖
            1. 同時, 相關的 同步方法/代碼塊, 進入 加鎖狀態, 只有 持有鎖 的線程, 才可以執行
          2. 同一時間, 同一個監視器鎖, 只能被一個線程持有
            1. 同一個線程, 可以持有同一個 監視器鎖 多次 - 遞歸
            2. 同一個線程, 可以持有不同的 監視器鎖 多次 - 同步方法/同步代碼塊 的連環調用
          3. 持有 監視器鎖
            1. 可以執行執行 依賴該鎖 的 同步方法/同步代碼塊
        2. 觸發

          1. 線程執行 同步代碼塊/同步方法 時, 會 觸發 對 相應監視器鎖 的請求
        3. 請求

          1. 未加鎖
            1. 單線程請求
              1. 線程請求成功, 持有鎖
              2. 相關 同步方法/同步代碼塊 加鎖
            2. 多線程請求
              1. 只有一個線程 可以請求成功, 並持有鎖
              2. 相關 同步方法/同步代碼塊 加鎖
              3. 其他線程, 進入 阻塞狀態
                1. 直到 之前的線程, 不再持有鎖, 展開下一次 競爭
                2. 老實說, 這個狀態, 我也不太清楚, 先這么說, 不影響理解
          2. 已加鎖
            1. 持有鎖線程
              1. 再額外持有一次鎖
            2. 其他線程
              1. 進入 阻塞狀態
          3. 鎖對象
            1. 實例方法 和 普通代碼塊
              1. 申請 對象 的監視器鎖
            2. 靜態方法
              1. 申請 類對象 的監視器鎖
        4. 釋放

          1. 正常
            1. 情況
              1. 方法正常執行完
            2. 結果
              1. 持有鎖 的線程, 歸還 一層鎖
                1. 釋放的順序, 類似 棧 - 先持有, 后釋放
              2. 如果 線程持有 0 層鎖, 則 同步方法/同步代碼塊 不再加鎖
          2. 異常
            1. 情況
              1. 出現 未處理異常 時, 會拋出異常 並 歸還 所有鎖
                1. 這個在 ref 里有提到, 感興趣的朋友, 可以看下
  3. 問題

    1. 現狀
      1. 已經形成一個相對完整的循環

        # 正常情況
        觸發 > 請求 > 執行 > 釋放 > 再次請求
        
    2. 問題
      1. 線程的切換

        1. 每個線程, 必須執行完一段 同步方法/同步代碼塊, 才能切換
        2. 這樣的切換方式, 感覺有些 機械, 無法應對一些場景
      2. 場景

        1. 線程A 在 同步代碼/同步代碼塊 中, 需要等待另一個資源就緒
          1. 按現在的機制來設計代碼 - 暫時不考慮 異步...
            1. 檢測資源是否就緒
            2. 如果就緒了, 就正常執行
            3. 如果沒有就緒, 則直接退出
            4. 退出之后, 由外面一層方法負責 輪詢
      3. 問題

        1. 需要一個 循環檢測
          1. 增加了代碼的 復雜度
        2. 如果資源沒有就緒, 則 需要重新執行方法
          1. 方法中 部分代碼, 可能會執行多次,
          2. 需要考慮 冪等性
          3. 增加了代碼的 復雜度
        3. 鎖的影響
          1. 要么一直 持有鎖, 不釋放
            1. 如果別的線程也需要這樣的鎖, 有概率在成 長時間阻塞
          2. 要么多次 申請同一個鎖
            1. 如果這個鎖競爭激烈, 可能會導致處理效率降低
            2. 競爭本身, 線程切換, 都會有資源的消耗
          3. 增加了 系統的額外消耗
      4. 解決

        1. 讓線程之間, 相互協調
  4. 注意

    1. 同一個監視器鎖, 可以被持有多次
      1. 解鎖也需要多次
    2. 同一個線程, 可以持有多個監視器鎖
    3. 持有多個鎖的時候, 如果出現異常, 會一次把所有鎖 都歸還

3. 等待隊列

  1. 概述

    1. 等待隊列
  2. 准備

    1. 場景
      1. 線程
        1. 多個線程
      2. 同步方法/同步代碼塊
        1. 一段
  3. 機制

    1. 等待

      1. 前提
        1. 線程 持有鎖
      2. 操作
        1. 線程執行 等待 操作
          1. LockObj.wait()
          2. LockObj.wait(time)
      3. 結果
        1. 線程 放棄鎖
        2. 線程進入 等待隊列
    2. 等待隊列

      1. 監視器鎖
        1. 每個 監視器鎖 都有一個 等待隊列
      2. 線程
        1. 進入等待隊列的線程, 都是曾經 持有過鎖, 並且主動放棄的
        2. 隊列里的線程, 會一直呆在隊列里, 不會再對鎖去做 申請
          1. 除非被喚醒
    3. 喚醒

      1. 前提
        1. 線程 持有鎖
      2. 操作
        1. 線程執行 喚醒 操作
          1. LockObj.notify()
          2. LockObj.notifyAll()
      3. 結果
        1. notify
          1. 隨機一個線程被喚醒
        2. notifyAll
          1. 喚醒所有線程
        3. 被喚醒的線程
          1. 離開等待隊列
          2. 重新參與 鎖的競爭
          3. 搶到鎖之后, 從 wait() 后面開始繼續執行
  4. 疑問

    1. 等待隊列里 喚醒的線程, 和 被阻塞 的線程, 優先級有區別嗎

      1. 我目前沒發現區別
        1. 可以理解為, 從 等待隊列 里出來, 就重新去外面排隊了
        2. 等待隊列的作用, 就是讓你 既沒有鎖, 也不去排隊
    2. 為什么 wait 和 notify 都需要 持有鎖呢

      1. 目的
        1. 防止 notify 被忽略
      2. 場景
        1. 線程 t1

          1. 執行 wait()

          2. 但是 wait 通常不單獨存在, 需要配合 條件判定 來使用

          3. 所以, 通常是

            if (condition) {
                wait();
            }
            
        2. 線程 t2

          1. 執行 notifyAll()
        3. 假設, 都沒有同步

        4. 執行

          1. t1: 判定通過, 還沒來得及執行 wait(), 被切換了
          2. t2: 執行 notifyAll(), 切回去
          3. t1: 執行 wait(), 完美錯過 notify
  5. 異常

    1. 如果 wait 中發生中斷, 會拋出異常

      1. InterruptedException
    2. 處理

      1. 機制
        1. try...catch
      2. 機制
        1. 等待隊列中的線程t, 接收到 中斷請求
        2. 線程t 響應中斷請求, 退出 等待隊列, 並修改 自身狀態
        3. 線程重新獲得鎖時, try...catch...內的處理, 會執行
          1. Java 語言規范

4. 后續

  1. 概述

    1. 感覺后續還有些問題
  2. 后續

    1. InterruptedException 的一些機制
      1. 觸發時機
      2. 標記位
    2. 線程停止的其他方法
      1. sleep
      2. yeild
      3. join
    3. 線程狀態
      1. 目前只有 運行, 阻塞, 等待
  3. 疑問: 鎖的特性

    1. 忽然回想起, 面試時候, 面試官會問一些我完全摸不着頭腦的問題
      1. 你本來就很菜
      2. 大概是 什么 樂觀鎖, 悲觀鎖, 偏向鎖, 自旋鎖
    2. 這些到底是什么玩意, 也沒見 Java 里有這些鎖
    3. 后來才知道, 這些是 鎖的性質
      1. 但是 好多博文上亂七八糟, 甚至和 鎖的實現 混為一談, 讓人懵逼
      2. 但是看一堆博客, 總覺得這玩意學起來, 心理不踏實, 想找本書, 看看什么書 有講這些玩意
    4. ref
      1. Java多線程中鎖的理解與使用
        1. 原文是 並發編程網 的, 不知道為啥這幾天我上不去了

ps

  1. ref

    1. Java 語言規范(Se 8)
      1. chpt17
    2. 圖解 Java 多線程設計模式
      1. 序章1
    3. 一個線程執行synchronized同步代碼時,再次重入該鎖過程中,如果拋出異常,會釋放鎖嗎?
    4. Java SE 8 源碼
      1. Object.java
    5. 關於 wait() 方法的同步執行
      1. 為什么WAIT必須在同步塊中
      2. Why must wait() always be in synchronized block
    6. 關於 InterruptedException
      1. Java並發--InterruptedException機制
        1. 雖然標榜原創, 但是和 下面 2006 年的文章, 十分相似
        2. 幾篇 ref 都值得一看
      2. 處理 InterruptedException
  2. 感覺

    1. 並發的內容, 需要先明確這么些東西
      1. 哪些線程, 會共用一把鎖
      2. 這些線程, 會有什么樣的 公共資源
      3. 線程之間, 需要怎么樣的依賴關系


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM