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
  • 使用原因: 在我們服務端調用第三方介面時,如:支付寶,微信支付,我們服務端需要模擬http請求並加上一些自己的邏輯響應給前端最終達到我們想要的效果 1.使用WebClient 引用命名空間 using System.Net; using System.Collections.Specialized; ...
  • WPF 實現帶蒙版的 MessageBox 消息提示框 WPF 實現帶蒙版的 MessageBox 消息提示框 作者:WPFDevelopersOrg 原文鏈接: https://github.com/WPFDevelopersOrg/WPFDevelopers.Minimal 框架使用大於等於.N ...
  • 一、JSON(JavaScript Object Notation)的簡介: ① JSON和XML類似,主要用於存儲和傳輸文本信息,但是和XML相比,JSON更小、更快、更易解析、更易編寫與閱讀。 ② C、Python、C++、Java、PHP、Go等編程語言都支持JSON。 二、JSON語法規則: ...
  • 1.避免Scoped模式註冊的服務變成Singleton模式 當提供一個生命周期模式為Singleton的服務實例時,如果發現該服務中還依賴生命周期模式為Scoped的服務實例(Scoped服務實例將被一個Singleton服務實例所引用),那麼這個被依賴的Scoped服務實例最終會成為一個Sing ...
  • 索引時資料庫提高數據查詢處理性能的一個非常關鍵的技術,索引的使用可以對性能產生上百倍甚至上千倍的影響。接下來,會介紹索引的基本原理、概念,並深入學習資料庫中所使用的索引結構和存儲方式,以及如何管理、維護索引等。 1.索引的基本概念 索引時用來快速查詢表記錄的一種存儲結構,一般使用索引有一下兩個方面: ...
  • django2 路由控制器 Route路由,是一種映射關係。路由是把客戶端請求的url路徑和用戶請求的應用程式,這裡意指django裡面的視圖進行綁定映射的一種關係。 請求路徑和視圖函數不是一一對應的關係 在django中所有的路由最終都被保存到一個叫urlpatterns的文件里,並且該文件必須在 ...
  • 1、我們的目標是獲取微博某博主的全部圖片、視頻 2、拿到網址後 我們先觀察 打開F12 隨著下滑我們發現載入出來了一個叫mymblog的東西,展開響應發現需要的東西就在裡面 3、重點來了!!! 通過觀察發現第二頁比第一頁多了參數since_id 而第二頁的since_id參數剛好在上一頁中能獲取到, ...
  • 一、實現原理 在Servlet3協議規範中,包含在JAR文件/META-INFO/resources/路徑下的資源可以直接訪問。 二、舉例說明 如下圖所示,是我新建的一個Spring Boot Starter項目:zimug-minitor-threadpool,用於實現可配置、可觀測的線程池。其中 ...
  • 精華筆記: static final常量:應用率高 必須聲明同時初始化 由類名打點來訪問,不能被改變 建議:常量所有字母都大寫,多個單詞用_分隔 編譯器在編譯時會將常量直接替換為具體的數,效率高 何時用:數據永遠不變,並且經常使用 抽象方法: 由abstract修飾 只有方法的定義,沒有具體的實現( ...
  • Python有一個for...else語法,它的寫法如下 for i in range(0,100): if i == 3: break else: print("Not found") 該語句表示:若for迴圈遍歷完畢,則執行else部分的語句。也就是說上述代碼不會有任何輸出,而下述代碼會輸出“N ...