Java 併發編程解析 | 關於線程機制的那些事,你究竟瞭解多少?

来源:https://www.cnblogs.com/mazhilin/archive/2022/07/30/16534198.html
-Advertisement-
Play Games

蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》 寫在開頭 眾所周知,在電腦操作系統中,進程(Process)是一個很關鍵的概念,最本質的理解就是操作系統執行的一個應用程式(Application Program)。與每個進程相關的是地址空間(Address ...


蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》

寫在開頭

眾所周知,在電腦操作系統中,進程(Process)是一個很關鍵的概念,最本質的理解就是操作系統執行的一個應用程式(Application Program)。與每個進程相關的是地址空間(Address Space)。其中,描述的是從某個最小值的存儲位置(通常是0)到最大值的存儲位置的列表。在這個地址空間中,進程可以進行讀寫操作。地址空間中可以存放可執行程式,以及程式需要的數據和棧針。與每個進程相關的資源集合。通常包括寄存器(Registers),打開的文件清單,突發的系統報警,有關的進程清單和其他執行程式的信息。其中,寄存器主要包括程式計數器(Program Counter)和堆棧指針(Stack Pointer)。從一定程度上,我們可以把進程當作容納運行一個程式所有信息的一個容器(Container)。

操作系統中可以使用進程來描述一個程式的執行過程,進程擁有該程式的所有數據(包括一些I/O分配情況、記憶體分配情況等),也就是該程式的一個載體,所以進程有一個特點就是資源分配的單位,這一點十分重要。進程還有一個特點就是調度執行,交替執行以提高資源利用率。

操作系統管理進程(創建、切換進程、分配與回收等)開銷是很大的,比如進程創建時還需要創建PCB,分配記憶體獨立的內空間,建立映射表,創建資源,進程切換時還需要切換資源,如切換對應的記憶體映射表,進程退出時還需要釋放資源。

由此不難得出一個結論,每一個進程都有一個地址空間(Address Space)和一個控制線程(Control Thead)。但是,操作系統有了進程為何要出現線程呢?主要是因為,雖然進程任然是資源分配的單位,但是調度執行卻交給了線程,因為線程是在進程的內部,線程間的切換不用切換資源,不用切換映射表,只需要簡單的在進程內部切換一下PC指針和保存一些寄存器即可,這也就更輕量了(避免不了不同進程間的線程切換)。

基本概述

線程(Thread)既保留了併發執行的優點,也避免了進程切換的代價。

假設現在有一個網路伺服器,此時沒有線程的概念,該伺服器程式用到多個進程,如用一個進程監聽客戶端的請求,當客戶端連接上後就分派出(複製出一個子進程)一個進程給該用戶(每個進程都有獨立的資源),用於監聽該用戶發送的數據並處理(即多進程程式),此時設想一下,這多個進程切來切去,每次切換的時候都需要切換資源,是不是很耗費資源。

此時,引入了線程之後,網路伺服器這個程式是一個進程,進程用於承載該程式的資源,首先用進程中的一個線程監聽請求,每次連接客戶端都分配一個線程給用戶(多線程程式),此時處理器只需要在這幾個線程中切換即可,線程的切換不需要切換資源(進程時資源級的切換,線程是指令級的切換),那麼多個線程只需要共用進程中的資源即可,其運行速度和執行效率也得到了提升。

由此可見,操作系統引入線程後,調度和分派是線上程上完成的,但是某些活動會影響進程中的所有線程,因此這些活動必須在進程級對他們進行管理。如掛起操作會掛起所有線程,因為所有線程共用進程的用戶地址空間。引入了線程最關鍵的體現在以下兩個方面:

  • 線程的創建、終止和調度更輕量
  • 線程間的通信不進過內核,不需要用戶態->內核態的轉換

但是,同時也增加了程式的開發難度,如果開發者對於線程機制的掌握和認識不夠準確,也會陷入技術困惑。

線程模型

所有線程共用進程的狀態和資源,所以線程都駐留在同一塊地址空間中,並可訪問相同的數據。

對於有線程和無線程的區別,其中主要是體現在用戶棧和內核棧兩個關鍵:

  • 用戶棧用於保存用戶進程的子程式間相互調用的參數、返回值以及局部變數等信息(保存普通方法的棧)
  • 內核棧是程式發生系統調用時內核態調用方法時的棧;用戶地址空間則是進程的程式和數據存放的空間,線程是沒有自己的用戶地址空間的

一般來說,用戶棧和內核棧已經線上程中獨有,也證明瞭線程成為了任務調度的基本單位,這些線程都共用進程所持有的資源,線程式控制制塊中存放了寄存器的值、優先順序、線程狀態等信息。

在操作系統層面,線程也有“生老病死”,專業的說法叫有生命周期。雖然不同的開發語言對於操作系統線程進行了不同的封裝,但是對於線程的生命周期這部分,基本上是相通的。每一個線程基本都有如下特征:

  1. 類似進程,線程也有執行狀態(生命周期),因為線程也是一個執行過程
  2. 線程的上下文,線程切換時也需要進行保護現場
  3. 執行棧,保存系統調用時的一些參數和中間結果
  4. 少量的,線程私有的局部變數的存儲空間,不再擁有大量的存儲空間
  5. 與進程內其他線程共用的記憶體和資源的訪問
  6. 線程式控制制塊TCB,存放上下文切換的信息,同PCB

可以看出,對於有生命周期的事物,要學好和掌握它,思路非常簡單,只要能搞懂生命周期中各個節點的狀態轉換機制即可。

線程分類

線程分為用戶級線程(User-Level Thread,ULT)和內核級線程(Kernel-Level Thread,KLT),內核級線程又叫做輕量級進程(Light-Weight Process,LWP)。

用戶級線程(User-Level Thread,ULT)

在純ULT軟體中,管理線程的所有工作都是應用程式完成,內核意識不到線程的存在,線程完全是由線程庫提供的,創建、銷毀、調度線程、線程間傳遞消息等,還包括保存上下文都是由它管控的,如果可以的話我們自己也可以實現自己的線程庫,只要合理的組織線程即可。

但是用戶級線程所有的活動都發生在用戶空間和一個進程中,系統感知不到用戶級線程的存在,所以系統依舊是以進程的方式調度。

當線程1發生系統調用等阻塞了,此時系統就會認為該進程阻塞了,操作系統會把CPU時間片分配給其他進程,在此期間,根據線程庫維護的數據結構來看,線程1任然處於運行狀態,但在處理器執行的角度,線程2是不處於運行狀態的,也分不到時間片。

這也導致了用戶級線程一旦阻塞,就會阻塞進程中的所有線程,使得其他線程也得不到運行。使用用戶級線程(ULT)如下特點:

優點:

  • 所有線程的管理都在一個進程的用戶空間中,線程的切換不需要內核模式特權,不需要系統調用,從而節省了用戶態到內核態轉換的開銷
  • 線程的調度更靈活,可以為每個不同的應用程式量身定製更合適的調度演算法,因為這些調度演算法都可以自己實現,不需要更改操作系統底層的調度程式
  • ULT可以在任何操作系統下運行,即便是不支持線程的操作系統也能實現,線程庫是供所有應用程式共用的一組應用級函數

缺點:

  • 在執行一個系統調用時不僅僅是阻塞當前線程,還會阻塞進程中的所有線程
  • ULT不能利用多處理技術,操作系統看不到線程,所以內核一次只能把一個進程分配給一個處理器,因此一個進程中的所有線程不能夠並行執行,只能夠併發執行,相當於一個進程內實現了多道程式設計

解決這兩個問題的方法有:

  • 把應用程式寫出多進程程式,但是該方法消除了線程的主要優點
  • 套管技術:把產生阻塞的系統調用轉化為一個非阻塞的系統調用

綜上所述,用戶級線程(User-Level Thread,ULT)適合計算密集型的,因為不需要IO操作 ,不會阻塞整個進程。

內核級線程(Kernel-Level Thread,KLT)

在KLT軟體中,管理線程的所有工作均由內核完成,應用級沒有線程管理代碼,只有一個到內核線程的API。

內核為進程級進程內的所有線程維護上下文信息,調度由內核基於線程完成。

該方法剋服了ULT的兩個缺點。首先,內核可以把一個進程中的線程分配個多個處理器中;其次,進程中的某個線程阻塞了,內核還可以調度同一個進程中的其他線程。

缺點是:在把控制權從一個線程傳送到另一個進程的線程時,需要切換到內核模式,開銷較大。

綜上所述,KLT併發性更好,適合I/O操作較多的程式。

混合線程(Hybrid-Approach Thread,HAT)

有些操作系統提供了ULT和KLT的混合體:線程創建完全在用戶空間中完成,線程的調度和同步也在應用程式中進行,一個應用程式中的多個用戶級線程會被映射到一些(小於等於用戶級線數)內核級線程上,進程和線程的比為 N:M,N<=M,ULT中比值為1:N,KLT為1:1

同一個應用程式中的多個線程可在多個處理器上並行的運行,某個引起阻塞的系統調用不會阻塞整個進程。

綜上所述,內核級線程(KLT)和用戶級線程(User-Level Thread,ULT)對比分析如下:

線程生命周期

一個線程的生命周期基本上可以這個“五態模型”來描述,主要分別是:初始狀態、可運行狀態、運行狀態、休眠狀態和終止狀態。其中:

  1. 初始狀態,指的是線程已經被創建,但是還不允許分配 CPU 執行。這個狀態屬於編程語言特有的,不過這裡所謂的被創建,僅僅是在編程語言層面被創建,而在操作系統層面,真正的線程還沒有創建。
  2. 可運行狀態,指的是線程可以分配 CPU 執行。在這種狀態下,真正的操作系統線程已經被成功創建了,所以可以分配 CPU 執行。
  3. 運行狀態:當有空閑的 CPU 時,操作系統會將其分配給一個處於可運行狀態的線程,被分配到 CPU 的線程的狀態就轉換成了運行狀態。
  4. 休眠狀態:運行狀態的線程如果調用一個阻塞的 API(例如以阻塞方式讀文件)或者等待某個事件(例如條件變數),那麼線程的狀態就會轉換到休眠狀態,同時釋放 CPU 使用權,休眠狀態的線程永遠沒有機會獲得 CPU 使用權。當等待的事件出現了,線程就會從休眠狀態轉換到可運行狀態。
  5. 終止狀態:線程執行完或者出現異常就會進入終止狀態,終止狀態的線程不會切換到其他任何狀態,進入終止狀態也就意味著線程的生命周期結束。

版權聲明:本文為博主原創文章,遵循相關版權協議,如若轉載或者分享請附上原文出處鏈接和鏈接來源。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 配置 Webpack Dev Server 可以解決本地開發前端應用時,手動執行 webpack 命令或 yarn build 命令,再去瀏覽器中訪問 dist/index.html 的麻煩耗時操作,可有效簡化流程。本文實戰演練配置 Webpack Dev Server 的方法與步驟。 ...
  • FrameDataTrans教程 博客園 乳鴿菌 20220729 核心原理是使用postMessage發送數據,window.addEventListener("message",fun)監聽。 插件地址 index.htm // <iframe id="fr" src="child.html"> ...
  • 記錄一個清除計時器的小bug,使用計時器的時候迴圈一直停不下來,把計時器賦值給了變數,仍舊無法停止。後來發現是清除器放置的位置有問題。 最初的位置: 這樣放置就導致if語句值運行了一次!我潛意識預設了整個getprogress方法為迴圈體,但其實只有那個計時器部分是迴圈體,導致我百思不得其解為什麼循 ...
  • 是不是見到google,facebook等大型專業網站的擁有不同的語言站群,可以不同語言間切換很給力 今天要介紹的就是如何識別不同國家語言,只需要簡單幾步,使你的web應用更有國際範。 安裝vue-i18n npm i vue-i18n --save 新建多語言json文件 在src目錄下新建 co ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 最近鼓搗了一下 Vue3 + Vite2,遇到了不少問題,整理了5個可以提高開發效率的小知識,讓你在 Vue3 的項目開發中更加絲滑、順暢。 一、setup name 增強 Vue3的setup語法糖是個好東西,但使用setup語法 ...
  • 最近寫二維碼的時候,突然想起之前項目遇到過的一個問題,網上也沒有這方面解答,想到大家今後可能也會遇到這類問題,在此記錄下來,希望對你們有所幫助,大佬們不喜勿噴,qrcode配合畫布canvas本地生成二維碼的時候第一次能夠正常顯示,最下方會貼出代碼 跨頁面後顯示異常 剛開始是有些頭疼的,因為控制台一 ...
  • 自定義事件 tips 推薦始終使用 kebab-case 的事件名。(v-on會將事件名自動轉換為小寫,避免匹配不到) changeData × change-data √ 自定義組件的v-model 用法: 父組件定義數據源(不需要定義修改數據的方法),在子組件標簽上通過v-model="data ...
  • 因為涉及到有些監控是沒有音頻的,所以使用flv.js插件,通過<web-view>標簽跳轉到h5頁面。 在真機上調試發現是沒有發現flv.js文件,出現跨域現象,但是我只是導入了js文件,不知道什麼情況; 然後,就只是移動端不支持flv.js。經過不懈努力,我找到了另外一個插件 DPlayer.js ...
一周排行
    -Advertisement-
    Play Games
  • 記錄一下Winform程式打包過程 參考文章:VS2017 WinFrom打包設置與教程 下載 Visual Studio Installer 拓展插件 從VS2017開始VS已預設不再集成Installer拓展,所以需要手動下載安裝,在 工具 - 插件和更新 裡面的插件商店裡面搜索"install ...
  • 先下載一個gif圖片,這裡提供一個gif圖片示例 線上GIF圖片幀拆分工具 - UU線上工具 (uutool.cn) 使用這個網站將gif切成單張圖片 創建一個Avalonia MVVM的項目,將圖片copy進去 在項目文件中添加一下代碼: <ItemGroup> <None Update="gif ...
  • 1 簡介 Dapr是一個可移植的、事件驅動的運行時,它使任何開發人員能夠輕鬆構建出彈性的、無狀態和有狀態的應用程式,並可運行在雲平臺或邊緣計算中,它同時也支持多種編程語言和開發框架。Dapr支持的語言很多,包括C++/Go/Java/JavaScript/Python/Rust/.NET/PHP等。 ...
  • 作者:大眼睛圖圖 鏈接:https://juejin.cn/post/7156483709338648613 前言 還有誰?剛上三天班就被公司公司的工作不適合我,叫我先提升一下。 後面我也向公司那邊討要了一個說法,我只能說他們那邊的說辭讓我有些不服氣。 現在之所以把這件事在掘金上記錄一下,一是記錄一 ...
  • 雲原生下的流水線是通過啟動容器來運行具體的功能步驟,每次運行流水線可能會被調度到不同的計算節點上。這會導致一個問題:容器運行完是不會保存數據的,每當流水線重新運行時,又會重新拉取代碼、編譯代碼、下載依賴包等等。在雲原生場景下,不存在本地宿主機編譯代碼、構建鏡像時緩存的作用,大大延長了流水線運行時間,... ...
  • 本文介紹基於Python語言中TensorFlow的tf.estimator介面,實現深度學習神經網路回歸的具體方法。 () 1 寫在前面 1. 本文介紹的是基於TensorFlow tf.estimator介面的深度學習網路,而非TensorFlow 2.0中常用的Keras介面;關於Keras接 ...
  • 基礎 我是在ruoyi-vue已經安裝了mybatis-plus的基礎上進行的修改 關於SQLite SQLite 是一個軟體庫,實現了自給自足的、無伺服器的、零配置的、事務性的 SQL 資料庫引擎。 SQLite主頁:https://www.sqlite.org/index.html 1. pom ...
  • 1. 服務監控三要素^1 服務監控需要滿足的三要素分別如下: 日誌監控 指標監控 請求鏈路追蹤 服務監控只要能滿足這三個要素,基本就能實現我們想要的監控效果。 1.1.主流APM系統^1 APM 系統(Application Performance Management,即應用性能管理)是對企業的應 ...
  • 雙重檢查鎖定與延遲初始化 在 java 程式中,有時候可能需要推遲一些高開銷的對象初始化操作,並且只有在使用這些對象時才進行初始化。此時程式員可能會採用延遲初始化。但要正確實現線程安全的延遲初始化需要一些技巧,否則很容易出現問題。比如,下麵是非線程安全的延遲初始化對象的示例代碼: COPYpubli ...
  • 顧名思義單調棧就是具有單調性的棧 ==常見模型:找出每個數左邊離它最近的比它大/小的數== 【演算法】 int stk[N],tt = 0; // 棧中存數據 for (int i = 1; i <= n; i ++){ int x; cin >> x; while (tt && stk[tt] >= ...