【手撕JAVA多線程】1.從設計初衷去看JAVA的線程操作

目錄

前言

概述

主動阻塞/喚醒

代碼示例

實現

為什么必須在同步塊中使用

計時等待是如何實現的

被動阻塞/喚醒

為什么要有被動阻塞/喚醒

實現(鎖升級)


前言

JAVA多線程相關的內容很多很雜,但工作中用到的頻率不高,用到的面也不全,導致是學了又忘忘了又學,在面對并發的場景下沒辦法很好的去解決問題,我相信不止一個人會存在以上這樣的情況。出現這樣的問題,是因為沒有對多線程建立成體系的認識。接下來博主就將用一個系列去帶大家去建立這個體系,博主有信心,這個系列能讓大家從此對JAVA多線程體系有絲滑且根深蒂固的認識。

JAVA多線程部分的內容,無非分為三部分:

  • 線程的操作

    • 線程狀態模型

    • 線程基礎操作:創建線程、阻塞/喚醒、等待/喚醒

  • 線程安全問題,即JMM

  • 一系列的線程同步工具、線程編排工具

本文先講第一部分:線程的操作。

當然這一部分只會講線程的狀態操作,不會去將怎么創建線程、結束線程這些基礎操作,相信大家都有一定基礎看這篇文章才是有益的。

概述

線程的操作這一部分無非內容就是:

  • JAVA中的線程狀態模型

  • 線程的基本操作(新建、阻塞、等待)。

如果只是按照順序來聊一遍,那和市面上大多數教程也沒什么區別了,我們要建立關于線程操作這一部分的體系認識,才能從思想上徹底吃透,做到內化于心,這樣怎么都不會忘記。JAVA線程操作部分的體系認識:

1.線程狀態的控制是線程的核心操作

線程存在的根本意義本身就在于控制程序的執行,線程上跑的是程序,通過控制線程狀態來實現對程序執行的控制,想暫停執行就阻塞當前線程,想要繼續執行就讓當前線程處于“就緒”狀態去等待CPU的時間片。所以線程最核心的就是對于它的控制操作,即線程的狀態操作。

2.JAVA的線程狀態也是基于操作系統的原生狀態,只是根據自己的需要將阻塞拆分成了多種

JAVA作為一門編程語言,它是運行在操作系統上的,所以他的線程對應的就是操作系統的真實線程,操作系統原生的線程狀態有三種:就緒、運行、阻塞。JAVA自然不能違背這三種基礎狀態,JAVA只是將阻塞狀態拆成了幾種:輕量級阻塞(等待、計時等待)、重量級阻塞(阻塞)。

為什么要拆成輕量級阻塞和重量級阻塞?JAVA作為一門面向應用開發的語言,要提供線程操控能力,就要允許開發者來手動阻塞/喚醒線程,輕量級阻塞就是是給開發者調用Object的wait()和notify()來手動阻塞/喚醒線程的。重量級阻塞是因為手動操作阻塞/喚醒需要一個絕對線程安全的環境,即需要一個同步塊,JDK提供了synchronized 原語,用來保證資源絕對被單一線程持有,從而創造出一個同步塊出來。可以理解為兩者底層都是調用系統調用對線程進行了真正的阻塞,輕量級阻塞是主動阻塞,重量級阻塞是由JVM層面來控制的阻塞。

3.線程的核心操作是阻塞/喚醒

在JAVA的多線程編程中,我們無非就是通過操作線程“阻塞/喚醒”來實現對線程的控制,這里面無非可做的就兩件事:

  • 通過wait()和notify來實現主動的阻塞/喚醒

  • 通過synchronized關鍵字來實現被動的阻塞/喚醒

所以搞清楚JAVA線程的阻塞,其實就搞明白了JAVA多線程的核心操作。

主動阻塞/喚醒

代碼示例

api:

  • wait(),等待notify/notifyAll喚醒

  • wait(long timeout),計時等待,到時自動喚醒,也可被notify/notifyAll喚醒

  • notify,喚醒,隨機選擇一個阻塞的線程喚醒

  • notifyAll,喚醒所有阻塞的線程,讓他們去自由爭搶

public class WaitNotifyExample {private final Object lock = new Object();private boolean condition = false;
?public void producer() throws InterruptedException {synchronized (lock) {System.out.println("生產者線程開始...");// 模擬生產耗時Thread.sleep(2000);condition = true;System.out.println("生產者完成,通知消費者...");lock.notify(); // 或者使用 notifyAll() 喚醒所有等待線程}}
?public void consumer() throws InterruptedException {synchronized (lock) {System.out.println("消費者線程開始,等待通知...");while (!condition) { // 使用 while 防止虛假喚醒lock.wait(); // 釋放鎖并等待}System.out.println("消費者被喚醒,繼續執行...");condition = false;}}
?public static void main(String[] args) {WaitNotifyExample example = new WaitNotifyExample();new Thread(() -> {try {example.consumer();} catch (InterruptedException e) {e.printStackTrace();}}).start();new Thread(() -> {try {example.producer();} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}

實現

主動的阻塞/喚醒,是為了提供靈活的線程編排能力,能靈活的控制線程的執行進度,既然要足夠靈活,那么就要求線程能被主動阻塞在任何地方,于是JVM選擇能讓線程被阻塞在任何類上。所以JVM會為每個對象維護一個監視器(Monitor),Java的wait()和notify()機制是通過JVM底層的監視器(Monitor)實現的。

class ObjectMonitor {private Thread owner; ? ? ? ? ? ? ?// 當前持有鎖的線程private int recursion; ? ? ? ? ? ? // 重入次數private Queue<Thread> entryList; ? // 等待獲取鎖的線程隊列private Queue<Thread> waitSet; ? ? // 等待條件的線程集合private Object object; ? ? ? ? ? ? // 關聯的Java對象private PlatformEvent platformEvent; // 平臺相關的同步原語
}

【question】為什么要有兩個隊列?

waitSet里面存的是blocked(阻塞狀態)的線程,entryList里面存的是可執行的線程,等待操作系統分配時間片。被阻塞的線程由waitSet喚醒,進入entryList,等待操作系統時間片來執行。

wait()時的隊列操作:

  • 保存狀態:保存當前線程的鎖重入信息

  • 釋放鎖:將監視器的owner設為null,重入計數歸零

  • 加入waitSet:將線程加入等待集合隊列,狀態變為WAITING

  • 系統調用:調用操作系統原語阻塞線程

  • 移出調度:線程從操作系統的運行隊列中移除

notify()時的隊列操作:

  • 檢查waitSet:查看是否有線程在等待

  • 選擇線程:從waitSet隊列中選擇一個等待線程

  • 移動隊列:將選中線程從waitSet移到entryList

  • 改變狀態:線程狀態從WAITING變為BLOCKED

  • 系統調用:調用操作系統原語喚醒線程

為什么必須在同步塊中使用

 private final Object lock = new Object();private boolean flag = false;//錯誤的并發代碼(偽代碼,實際會拋異常)public void problematicScenario() {// 線程A執行:if (!flag) { // 檢查條件// 此時發生線程切換// 線程B執行并設置flag=true,調用notify()// 線程A恢復執行,調用wait() -> 永遠等待!lock.wait(); // 錯過了通知,永遠阻塞}}//正確的同步版本public void correctScenario() throws InterruptedException {synchronized (lock) {// 檢查和等待是原子操作while (!flag) {lock.wait(); // 不會錯過通知}}}

計時等待是如何實現的

核心流程:

  • 驗證監視器所有權 - 檢查當前線程是否持有鎖

  • 釋放監視器鎖 - 原子性地釋放對象鎖

  • 加入等待隊列 - 線程加入對象的wait_set隊列

  • 系統級阻塞 - 調用操作系統原語(futex/park/wait)阻塞線程

  • 超時管理 - 啟動定時器監控超時

  • 等待喚醒 - 等待notify/notifyAll調用或超時到期

  • 重新競爭鎖 - 被喚醒后重新獲取監視器鎖

  • 恢復執行 - 繼續執行wait()后面的代碼

如何實現超時管理:

  • 操作系統負責超時 (主流實現)

  • JVM調用帶超時參數的系統調用

  • 操作系統內核維護定時器

  • 內核自動喚醒超時的線程

被動阻塞/喚醒

為什么要有被動阻塞/喚醒

被動阻塞可以理解為JDK提供的原語級別的能力,用來保證資源絕對被單一線程持有。

為什么要提供這種原語級別的能力:

因為我們仔細想想就能想到純靠編碼去進行主動阻塞是無法保證線程安全的。不管什么寫法都不能保證wait()一定是線程安全的。所以JDK要自己提供一個原語來保證創造一個線程安全的環境,也就是創造一個同步區域、同步塊。這就是JDK提供的(被動阻塞/喚醒)同步原語:synchronized關鍵字。

實現(鎖升級)

synchronized 關鍵字用于實現線程同步,它可以保證同一時刻只有一個線程執行被synchronized 修飾的代碼塊或方法。

底層實現原理

  1. 對象頭(Mark Word) 每個 Java 對象在內存中都有一個對象頭,對象頭中包含鎖信息,用于實現 synchronized。

  2. 鎖的四種狀態 無鎖狀態:對象未被任何線程鎖定 偏向鎖:偏向第一個訪問對象的線程,減少同一線程獲取鎖的開銷 輕量級鎖:當存在多個線程競爭但競爭不激烈時使用 重量級鎖:當線程競爭激烈時,會阻塞未獲取到鎖的線程

  3. 鎖升級過程 無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖(單向升級)

無鎖:

當同步代碼首次被一個線程訪問,那么就會在Mark Word記錄該線程的ID,從無鎖狀態(001)變成偏向鎖(101)。

img

偏向鎖:

當下一次同步代碼被訪問時,那么就會檢測該線程ID與鎖的Mark Word 中的線程ID是否是相同。

相同:則直接進入同步代碼,因為之前沒有釋放鎖

不同:表示發生了競爭,會嘗試使用CAS來替換Mark Word里面的線程ID。競爭成功則會替換Mark Word 里面的線程ID,競爭失敗可能會變成輕量級鎖。

img

輕量級鎖:

當有第二個線程嘗試獲取鎖時,偏向鎖升級為輕量級鎖,兩個線程自由爭搶,沒搶到的就CAS自旋等待。

img

重量級鎖:

當自旋超過一定次數(默認10次)或等待線程數超過CPU核心數的一半,鎖升級為重量級鎖,這時候會指向一個監視器對象,這個監視器對象用集合的形式,來登記和管理排隊的線程。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/93602.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/93602.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/93602.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

UE5多人MOBA+GAS 46、制作龍卷風技能

文章目錄創建龍卷風GA創建蒙太奇創捷一系列GE添加數據表添加到角色中創建龍卷風GA GA_Tornado 添加標簽 // 龍卷風冷卻CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Tornado_Cooldown)// 通用技能傷害CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Generic_Dama…

如何在ubuntu下安裝libgpiod庫

以下是關于如何在ubuntu下安裝libgpiod庫的兩種安裝方式以及遇到ubuntu存在多個工具鏈導致編譯失敗的解決方法。如果想要自由選擇使用不同版本的libgpiod&#xff0c;可以選擇手動編譯安裝方式&#xff0c;系統安裝默認1.6.3版本(ubuntu22.04)。手動編譯安裝1、在github上下載要…

qt vs2019編譯QXlsx

1、安裝ActivePerl2、打開pro文件&#xff0c;直接編譯即可第一個簡單實例&#xff1a;#include "xlsxcellrange.h" #include "xlsxchart.h" #include "xlsxchartsheet.h" #include "xlsxdocument.h" #include "xlsxrichstring.h…

計算機存儲器分類和層次結構詳解

存儲器是計算機系統的核心部件之一&#xff0c;其核心功能是存儲程序&#xff08;指令&#xff09;和數據&#xff0c;是馮諾依曼體系結構“存儲程序”概念的物質基礎。它直接關系到計算機系統的性能、容量和成本。 存儲器核心內容總覽表分類維度存儲器層級技術實現速度容量成本…

通過rss訂閱小紅書,程序員將小紅書同步到自己的github主頁

title: 通過rss訂閱小紅書&#xff0c;程序員將小紅書同步到自己的github主頁 tags: 個人成長 categories:雜談最近在做一些新的嘗試&#xff0c;把文本的內容轉化為漫畫和圖片&#xff0c;方便大眾閱讀&#xff0c;恰好小紅書很適合分發這些內容&#xff0c;于是我開通了小紅書…

麒麟KylinOS V10-SP3 安裝FastGPT

1. 操作系統環境CPU&#xff1a;20核 Xeon(R) Platinum 8457C 內存&#xff1a;64GB GPU&#xff1a;4090 操作系統&#xff1a;KylinOS-V10-SP32. 安裝docker、docker-compose、fastgpt下載安裝docker、docker-compose1. 下載docker docker 下載地址&#xff1a; https://do…

前端/在vscode中創建Vue3項目

Contenthtml input元素添加css樣式使用js添加交互按鈕點擊提示輸入框字符計數使用 npm 來管理項目包安裝 Node.js初始化項目安裝依賴包創建一個基于 Vite 的 Vue 項目創建項目進入項目目錄安裝依賴調用代碼格式化工具啟動開發服務器在瀏覽器中訪問html input元素 <input ty…

HiSmartPerf使用WIFI方式連接Android機顯示當前設備0.0.0.0無法ping通!設備和電腦連接同一網絡,將設備保持亮屏重新嘗試

在使用HiSmartPerf使用WIFI方式連接Android機時&#xff0c;如果出現無法ping通0.0.0.0的情況&#xff0c;可以嘗試以下步驟解決問題&#xff1a;問了一下AI&#xff0c;給出的解答如下&#xff1a; 檢查網絡連接 &#xff1a;確保設備和電腦連接到同一局域網的Wi-Fi。可以在手…

SpringWeb是什么東西?

SpringWeb是個什么東西&#xff1f;SpringWeb是一個Java開發Web項目時的Web層框架。所謂Web層&#xff0c;就是直接和用戶打交道的框架&#xff0c;用戶(User)也就是顧客&#xff0c;顧客就是上帝&#xff0c;我們說是Web項目&#xff0c;通常也就是說B/S架構的項目&#xff0c…

docker+nginx+keepalived+openappsec+web ui+crowdsec部署安全代理

docker+nginx+keepalived+openappsec+web ui+crowdsec部署安全代理 一、環境介紹 二、基礎環境安裝 1、優化系統參數 2、安裝docker 3、創建容器網絡 4、安裝測試容器(可選) 三、安裝nginx 1、拉取鏡像 2、創建映射目錄 3、準備默認配置文件 4、證書文件準備 5、啟動nginx容器…

自動駕駛中安全相關機器學習功能的可靠性定義方法

摘要當前標準無法涵蓋高自動化駕駛中基于機器學習功能的安全需求。由于神經網絡的不透明性&#xff0c;一些自動駕駛功能無法按照 V 模型進行開發。這些功能需要對標準進行擴展。本文聚焦這一空白&#xff0c;為這類功能定義了功能可靠性&#xff0c;以幫助未來的標準控制基于機…

css實現圓角+邊框漸變+背景半透明

ui小姐姐經常搞一些花里胡哨的東西&#xff0c;圓角邊框漸變背景半透明&#xff0c;雖然每個都可以弄&#xff0c;但是合在一起真的不好弄&#xff0c;主要是因為通過border–image設置的邊框漸變&#xff0c;無法使用圓角&#xff0c;下面是自己搜索整理的一些可以的方案。 方…

tree組件(幾種不同分叉樹Vue3)

效果圖&#xff1a; 基礎樹組件&#xff08;本人博客里面有&#xff09; https://blog.csdn.net/xfy991127/article/details/140346861?spm1001.2014.3001.5501 下面是工作需求改造后 父組件 <template><div class"go-JJTree" id"tree-scroll&quo…

百度智能云x中科大腦:「城市智能體」如何讓城市更會思考

近日&#xff0c;2025中關村論壇系列活動——中關村人工智能與未來城市論壇在中關村國家自主創新示范區展示中心舉辦。論壇上&#xff0c;發布了應用范式創新升級成果、智能體產品、可信數據空間成果等。 中科大腦聯合百度智能云等伙伴共同打造并發布21個智能體產品&#xff0c…

在職老D滲透日記day16:sqli-labs靶場通關(第24關)二次注入 sqlmap自動注入沒跑出來。。。

5.24.2.sqlmap自動注入第一個&#xff1a;登錄頁面&#xff08;1&#xff09;pb抓取http頭POST /sqli-labs/Less-24/login.php HTTP/1.1 Host: 192.168.10.106 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0 Accept: text/ht…

Fanuc機器人EtherCAT通訊配置詳解

1、EtherCAT簡介EtherCAT&#xff0c;這一基于以太網的現場總線系統&#xff0c;以其開放架構和高速性能著稱。CAT代表的是控制自動化技術&#xff08;Control Automation Technology&#xff09;的縮寫&#xff0c;彰顯了其在工業自動化領域的核心地位。作為確定性的工業以太網…

超酷炫的Three.js示例

今天寫一個超級酷炫的Three.js示例&#xff0c;以下是文件源代碼&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-sca…

從零開始大模型之實現GPT模型

從零開始大模型之從頭實現GPT模型1.大語言模型整體架構2 大語言的Transformer模塊2.1 層歸一化2.2 GELU激活函數2.3 前饋神經網絡2.4 快捷連接3 附錄3.1 anacondapython環境搭建1.數據預處理&#xff1a;原始數據進行詞元化&#xff0c;以及通過&#xff0c;依據詞匯表生成ID編…

[1Prompt1Story] 滑動窗口機制 | 圖像生成管線 | VAE變分自編碼器 | UNet去噪神經網絡

鏈接&#xff1a;https://github.com/byliutao/1Prompt1Story 這個項目是一個基于單個提示生成一致文本到圖像的模型。它在ICLR 2025會議上獲得了聚焦論文的地位。該項目提供了生成一致圖像的代碼、Gradio演示代碼以及基準測試代碼。 主要功能點: 使用單個提示生成一致的文本…

【GitHub開源AI精選】Sitcom-Crafter:北航聯合港中文等高校打造的劇情驅動3D動作生成系統

系列篇章&#x1f4a5; No.文章1【GitHub開源AI精選】LLM 驅動的影視解說工具&#xff1a;Narrato AI 一站式高效創作實踐2【GitHub開源AI精選】德國比勒費爾德大學TryOffDiff——高保真服裝重建的虛擬試穿技術新突破3【GitHub開源AI精選】哈工大&#xff08;深圳&#xff09;…