-
概述
- jvm 監視器鎖 與 等待隊列
- 初版, 目前來看, 還是一個 生硬的總結
- 后續會做調整
-
背景
- 之前講了 synchronized
- 但是其中的原理, 並沒有講
- 這些是定義在 java 內存模型 里的
1. 回顧: synchronized
-
概述
- 回顧之前的內容
-
格式
-
方法
# 后面簡稱 同步方法 public static synchronized void method() {} public synchronized void method() {}
-
代碼塊
# 后面簡稱 同步代碼塊 synchronized(obj) {}
-
-
作用
- 通過一個對象, 來限定 synchronized 范圍內的代碼塊
- 獲取對象鎖的線程, 可以執行代碼塊內容
- 其他線程, 需要等到 對象鎖 被釋放, 才有機會執行
- 通過一個對象, 來限定 synchronized 范圍內的代碼塊
-
所以
- 這個鎖, 到底是個 什么情況
- 監視器鎖
- 這個鎖, 到底是個 什么情況
2. 監視器鎖
-
概述
- 監視器鎖
-
監視器鎖
-
概述
- 一種 同步機制
-
機制
-
對象
- 每個對象, 都有一個 監視器鎖
-
線程
-
持有
- 線程獲取 監視器鎖 成功, 則稱 線程持有鎖
- 同時, 相關的 同步方法/代碼塊, 進入 加鎖狀態, 只有 持有鎖 的線程, 才可以執行
- 同一時間, 同一個監視器鎖, 只能被一個線程持有
- 同一個線程, 可以持有同一個 監視器鎖 多次 - 遞歸
- 同一個線程, 可以持有不同的 監視器鎖 多次 - 同步方法/同步代碼塊 的連環調用
- 持有 監視器鎖
- 可以執行執行 依賴該鎖 的 同步方法/同步代碼塊
- 線程獲取 監視器鎖 成功, 則稱 線程持有鎖
-
觸發
- 線程執行 同步代碼塊/同步方法 時, 會 觸發 對 相應監視器鎖 的請求
-
請求
- 未加鎖
- 單線程請求
- 線程請求成功, 持有鎖
- 相關 同步方法/同步代碼塊 加鎖
- 多線程請求
- 只有一個線程 可以請求成功, 並持有鎖
- 相關 同步方法/同步代碼塊 加鎖
- 其他線程, 進入 阻塞狀態
- 直到 之前的線程, 不再持有鎖, 展開下一次 競爭
- 老實說, 這個狀態, 我也不太清楚, 先這么說, 不影響理解
- 單線程請求
- 已加鎖
- 持有鎖線程
- 再額外持有一次鎖
- 其他線程
- 進入 阻塞狀態
- 持有鎖線程
- 鎖對象
- 實例方法 和 普通代碼塊
- 申請 對象 的監視器鎖
- 靜態方法
- 申請 類對象 的監視器鎖
- 實例方法 和 普通代碼塊
- 未加鎖
-
釋放
- 正常
- 情況
- 方法正常執行完
- 結果
- 持有鎖 的線程, 歸還 一層鎖
- 釋放的順序, 類似 棧 - 先持有, 后釋放
- 如果 線程持有 0 層鎖, 則 同步方法/同步代碼塊 不再加鎖
- 持有鎖 的線程, 歸還 一層鎖
- 情況
- 異常
- 情況
- 出現 未處理異常 時, 會拋出異常 並 歸還 所有鎖
- 這個在 ref 里有提到, 感興趣的朋友, 可以看下
- 出現 未處理異常 時, 會拋出異常 並 歸還 所有鎖
- 情況
- 正常
-
-
-
-
問題
- 現狀
-
已經形成一個相對完整的循環
# 正常情況 觸發 > 請求 > 執行 > 釋放 > 再次請求
-
- 問題
-
線程的切換
- 每個線程, 必須執行完一段 同步方法/同步代碼塊, 才能切換
- 這樣的切換方式, 感覺有些 機械, 無法應對一些場景
-
場景
- 線程A 在 同步代碼/同步代碼塊 中, 需要等待另一個資源就緒
- 按現在的機制來設計代碼 - 暫時不考慮 異步...
- 檢測資源是否就緒
- 如果就緒了, 就正常執行
- 如果沒有就緒, 則直接退出
- 退出之后, 由外面一層方法負責 輪詢
- 按現在的機制來設計代碼 - 暫時不考慮 異步...
- 線程A 在 同步代碼/同步代碼塊 中, 需要等待另一個資源就緒
-
問題
- 需要一個 循環檢測
- 增加了代碼的 復雜度
- 如果資源沒有就緒, 則 需要重新執行方法
- 方法中 部分代碼, 可能會執行多次,
- 需要考慮 冪等性
- 增加了代碼的 復雜度
- 鎖的影響
- 要么一直 持有鎖, 不釋放
- 如果別的線程也需要這樣的鎖, 有概率在成 長時間阻塞
- 要么多次 申請同一個鎖
- 如果這個鎖競爭激烈, 可能會導致處理效率降低
- 競爭本身, 線程切換, 都會有資源的消耗
- 增加了 系統的額外消耗
- 要么一直 持有鎖, 不釋放
- 需要一個 循環檢測
-
解決
- 讓線程之間, 相互協調
-
- 現狀
-
注意
- 同一個監視器鎖, 可以被持有多次
- 解鎖也需要多次
- 同一個線程, 可以持有多個監視器鎖
- 持有多個鎖的時候, 如果出現異常, 會一次把所有鎖 都歸還
- 同一個監視器鎖, 可以被持有多次
3. 等待隊列
-
概述
- 等待隊列
-
准備
- 場景
- 線程
- 多個線程
- 同步方法/同步代碼塊
- 一段
- 線程
- 場景
-
機制
-
等待
- 前提
- 線程 持有鎖
- 操作
- 線程執行 等待 操作
- LockObj.wait()
- LockObj.wait(time)
- 線程執行 等待 操作
- 結果
- 線程 放棄鎖
- 線程進入 等待隊列
- 前提
-
等待隊列
- 監視器鎖
- 每個 監視器鎖 都有一個 等待隊列
- 線程
- 進入等待隊列的線程, 都是曾經 持有過鎖, 並且主動放棄的
- 隊列里的線程, 會一直呆在隊列里, 不會再對鎖去做 申請
- 除非被喚醒
- 監視器鎖
-
喚醒
- 前提
- 線程 持有鎖
- 操作
- 線程執行 喚醒 操作
- LockObj.notify()
- LockObj.notifyAll()
- 線程執行 喚醒 操作
- 結果
- notify
- 隨機一個線程被喚醒
- notifyAll
- 喚醒所有線程
- 被喚醒的線程
- 離開等待隊列
- 重新參與 鎖的競爭
- 搶到鎖之后, 從 wait() 后面開始繼續執行
- notify
- 前提
-
-
疑問
-
等待隊列里 喚醒的線程, 和 被阻塞 的線程, 優先級有區別嗎
- 我目前沒發現區別
- 可以理解為, 從 等待隊列 里出來, 就重新去外面排隊了
- 等待隊列的作用, 就是讓你 既沒有鎖, 也不去排隊
- 我目前沒發現區別
-
為什么 wait 和 notify 都需要 持有鎖呢
- 目的
- 防止 notify 被忽略
- 場景
-
線程 t1
-
執行 wait()
-
但是 wait 通常不單獨存在, 需要配合 條件判定 來使用
-
所以, 通常是
if (condition) { wait(); }
-
-
線程 t2
- 執行 notifyAll()
-
假設, 都沒有同步
-
執行
- t1: 判定通過, 還沒來得及執行 wait(), 被切換了
- t2: 執行 notifyAll(), 切回去
- t1: 執行 wait(), 完美錯過 notify
-
- 目的
-
-
異常
-
如果 wait 中發生中斷, 會拋出異常
- InterruptedException
-
處理
- 機制
- try...catch
- 機制
- 等待隊列中的線程t, 接收到 中斷請求
- 線程t 響應中斷請求, 退出 等待隊列, 並修改 自身狀態
- 線程重新獲得鎖時, try...catch...內的處理, 會執行
- Java 語言規范
- 機制
-
4. 后續
-
概述
- 感覺后續還有些問題
-
后續
- InterruptedException 的一些機制
- 觸發時機
- 標記位
- 線程停止的其他方法
- sleep
- yeild
- join
- 線程狀態
- 目前只有 運行, 阻塞, 等待
- InterruptedException 的一些機制
-
疑問: 鎖的特性
- 忽然回想起, 面試時候, 面試官會問一些我完全摸不着頭腦的問題
- 你本來就很菜
- 大概是 什么 樂觀鎖, 悲觀鎖, 偏向鎖, 自旋鎖
- 這些到底是什么玩意, 也沒見 Java 里有這些鎖
- 后來才知道, 這些是 鎖的性質
- 但是 好多博文上亂七八糟, 甚至和 鎖的實現 混為一談, 讓人懵逼
- 但是看一堆博客, 總覺得這玩意學起來, 心理不踏實, 想找本書, 看看什么書 有講這些玩意
- ref
- Java多線程中鎖的理解與使用
- 原文是 並發編程網 的, 不知道為啥這幾天我上不去了
- Java多線程中鎖的理解與使用
- 忽然回想起, 面試時候, 面試官會問一些我完全摸不着頭腦的問題
ps
-
ref
- Java 語言規范(Se 8)
- chpt17
- 圖解 Java 多線程設計模式
- 序章1
- 一個線程執行synchronized同步代碼時,再次重入該鎖過程中,如果拋出異常,會釋放鎖嗎?
- Java SE 8 源碼
- Object.java
- 關於 wait() 方法的同步執行
- 關於 InterruptedException
- Java並發--InterruptedException機制
- 雖然標榜原創, 但是和 下面 2006 年的文章, 十分相似
- 幾篇 ref 都值得一看
- 處理 InterruptedException
- Java並發--InterruptedException機制
- Java 語言規范(Se 8)
-
感覺
- 並發的內容, 需要先明確這么些東西
- 哪些線程, 會共用一把鎖
- 這些線程, 會有什么樣的 公共資源
- 線程之間, 需要怎么樣的依賴關系
- 並發的內容, 需要先明確這么些東西