徹底理解線程

来源:https://www.cnblogs.com/xiaoyangjia/archive/2022/08/27/16631830.html
-Advertisement-
Play Games

操作系統支持多個應用程式同時執行,每個應用至少對應一個進程,彼此之間的操作和數據不受干擾。當一個進程需要磁碟IO的時候,CPU就切換到另外的進程,提高了CPU利用率。有了進程,為什麼還要線程?因為進程的成本太高了。啟動新的進程必須分配獨立的記憶體空間,建立數據表維護它的代碼段、堆棧段和數據段,這是昂貴... ...


1 線程的意義

操作系統支持多個應用程式同時執行,每個應用至少對應一個進程,彼此之間的操作和數據不受干擾。當一個進程需要磁碟IO的時候,CPU就切換到另外的進程,提高了CPU利用率。

有了進程,為什麼還要線程?因為進程的成本太高了。

啟動新的進程必須分配獨立的記憶體空間,建立數據表維護它的代碼段、堆棧段和數據段,這是昂貴的多任務工作方式。如果兩個進程之間需要通信,要採用管道通信、消息隊列、共用記憶體等等方式。線程可以看作輕量化的進程,或者粒度更小的進程。線程之間使用相同的地址空間,切換線程的時間遠遠小於切換進程的時間。一個進程的開銷大約是線程開銷的30倍左右。

隨著操作系統的發展,進程已經演變成了線程容器的角色。進程是資源分配的最小單位,線程是CPU調度的最小單位。每一個進程中至少有一個線程,同一進程的所有線程共用該進程的所有資源。

2 詳解Java線程

我們以Java語言和JVM為例,瞭解一下線程的實現原理。

2.1 線程的底層實現

啟動一個Java程式會創建一個JVM進程,JVM創建、管理線程本質都是調用操作系統介面。

public class TestThreadStart {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("start thread now ");
        }, "TestThreadStart");
        thread.run();
        System.out.println("the state of thread is " + thread.getState().name());
        thread.start();
        System.out.println("the state of thread is " + thread.getState().name());
    }
}

以上代碼演示了使用start方法啟動線程,run方法只是執行同步方法,輸出結果如下:

start thread now 
the state of thread is NEW
the state of thread is RUNNABLE
start thread now 

JVM源碼文件 https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Thread.java 中,可以看到線程啟動的start方法調用本地方法start0。

    public void start() {
        synchronized (this) {
            // zero status corresponds to state "NEW".
            if (holder.threadStatus != 0)
                throw new IllegalThreadStateException();
            start0();
        }
    }
    
    private native void start0();

源碼文件 https://github.com/openjdk/jdk/blob/master/src/java.base/share/native/libjava/Thread.c 中,實現了start0方法映射到JVM本地方法。

static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread},
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive0",         "()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
    {"resume0",          "()V",        (void *)&JVM_ResumeThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield0",           "()V",        (void *)&JVM_Yield},
    {"sleep0",           "(J)V",       (void *)&JVM_Sleep},
    {"currentCarrierThread", "()" THD, (void *)&JVM_CurrentCarrierThread},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"setCurrentThread", "(" THD ")V", (void *)&JVM_SetCurrentThread},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",       "()[" THD,    (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    {"getStackTrace0",   "()" OBJ,     (void *)&JVM_GetStackTrace},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
    {"extentLocalCache",  "()[" OBJ,    (void *)&JVM_ExtentLocalCache},
    {"setExtentLocalCache", "([" OBJ ")V",(void *)&JVM_SetExtentLocalCache},
    {"getNextThreadIdOffset", "()J",     (void *)&JVM_GetNextThreadIdOffset}
};

在源碼文件 https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/thread.cpp 可以看到啟動線程依賴系統級方法os::start_thread(thread)

void Thread::start(Thread* thread) {
  // Start is different from resume in that its safety is guaranteed by context or
  // being called from a Java method synchronized on the Thread object.
  if (thread->is_Java_thread()) {
    // Initialize the thread state to RUNNABLE before starting this thread.
    // Can not set it after the thread started because we do not know the
    // exact thread state at that time. It could be in MONITOR_WAIT or
    // in SLEEPING or some other state.
    java_lang_Thread::set_thread_status(JavaThread::cast(thread)->threadObj(),
                                        JavaThreadStatus::RUNNABLE);
  }
  os::start_thread(thread);
}

在源碼文件 https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/os.cpp 找到os::start_thread方法,可以看到系統創建了線程,並且狀態設置為RUNNABLE。

void os::start_thread(Thread* thread) {
  OSThread* osthread = thread->osthread();
  osthread->set_state(RUNNABLE);
  pd_start_thread(thread);
}

Linux系統並沒有把線程和進程區別對待,無論線程還是進程都是一個數據結構,用task_struct結構體表示,唯一的區別是共用的數據區域不同。

struct task_struct {
    // 進程狀態
    long              state;
    // 虛擬記憶體結構體
    struct mm_struct  *mm;
    // 唯一進程號
    pid_t             pid;
    // 指向父進程的指針
    struct task_struct   *parent;
    // 子進程列表
    struct list_head      children;
    // 存放文件系統信息的指針
    struct fs_struct      *fs;
    // 進程/線程打開的文件指針
    struct files_struct   *files;
};

以上代碼是 task_struct 的極少部分欄位。mm_struct是進程的虛擬記憶體空間,files_struct是進程將要讀寫的文件。Linux系統將一切外設和磁碟文件都當做文件處理,files_struct代表所有的IO操作。


從上圖可以看到,Linux創建進程和子進程會申請不同的記憶體空間,讀寫不同的文件;創建進程和進程下的線程,共用了記憶體空間,讀寫一樣的文件。因此多線程應用程式要利用鎖機制,避免在同一區域寫入錯亂數據的問題。

2.2 線程的生命周期

操作系統的線程生命周期可以分為五種狀態。分別是:初始狀態、可運行狀態、運行狀態、休眠狀態和終止狀態。JVM將線程等待狀態細分成兩種,一共六種狀態。

  • NEW:創建。
  • RUNNABLE:運行中。
  • BLOCKED:受阻塞並等待某個監視器鎖。
  • WAITING:無限期地等待。
  • TIMED_WAITING:等待指定時間。
  • TERMINATED:終止。
2.3 線程的優先順序

操作系統調度線程有兩種方式:

  • 協作式調度:當前線程完全占用CPU時間,執行時間由線程本身控制,直到運行結束,系統才執行下一個線程。可能出現一個線程一直占有CPU,而其他線程等待,導致整個系統崩潰。

  • 搶占式調度:操作系統決定下一個占用CPU時間的是哪一個線程,定期的中斷當前正在執行的線程,任何一個線程都不能獨占。不會因為一個線程而影響整個進程的執行,但是頻繁阻塞和調度會造成系統資源的浪費。

JVM的線程調度預設是搶占式調度,線程調度器按照優先順序決定調度哪個線程來執行。線程優先順序的範圍是1~10,預設的優先順序是5,10極最高。線程優先順序高的不一定先執行,優先順序低只是獲得調度的概率低,並不是一定最後被調度。通過setPriority()可以改變線程優先順序。

2.4 JVM守護線程

守護線程是一種JVM中特殊的線程,在後臺完成一些系統性的服務,比如垃圾回收。應用程式創建的線程叫做用戶線程,完成具體的業務操作。程式中所有的用戶線程執行完畢之後,不管守護線程是否結束,JVM都會自動結束。任何線程都可以通過setDaemon()設置為守護線程和用戶線程,如下代碼所示:

public class DaemonThreadDemo {

    public static void main(String[] args) {
        System.out.println("--主線程開始--");
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println("執行守護線程");
            }
        });
        thread.setDaemon(true);
        thread.start();
        System.out.println("--主線程結束--");
    }
}

程式運行結果:

--主線程開始--
--主線程結束--
執行守護線程
執行守護線程
執行守護線程
執行守護線程
執行守護線程
Process finished with exit code 0

當一個應用程式需要在後臺持續做某件事情,就是守護線程的典型應用場景。比如開發一款社交軟體,開啟守護線程持續監聽聊天消息。當應用程式退出時,守護線程一定會終止。

參考文章:https://www.codingbrick.com/archives/937.html

博客作者:編碼磚家
公 眾 號:編碼磚家
獨立博客:codingbrick.com
文章出處:https://www.cnblogs.com/xiaoyangjia/p/16631830.html
本文版權歸作者所有,任何人或團體、機構全部轉載或者部分轉載、摘錄,請在文章明顯位置註明作者和原文鏈接。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.創建Prism Prism是一個用於WPF、Xamarin Form、Uno平臺和 WinUI 中構建鬆散耦合、可維護和可測試的XAML應用程式框架 通過以下方式訪問、使用、學習它: https://github.com/PrismLibrary/Prism https://github.com ...
  • 概述 本文講述下拉框和枚舉類型進行綁定的一些操作。 下拉框的基本操作 設計部分: <ComboBox ItemsSource="{Binding Fruits}" SelectedItem="{Binding SelectedFruit}" SelectedIndex="{Binding Selec ...
  • 概述 本文描述WPF的自定義控制項和用戶控制項。 自定義控制項 前面文章介紹了WPF的ControlTemplate,當我們對系統控制項自帶的樣式不太滿意時,我們可以通過控制項模板自定義用戶的樣式,以Button為例,我們可以設計一個圓形的按鈕,並通過觸發器控制一些動態效果。在使用控制項模板時,我們通過Temp ...
  • 概述 本文描述WPF的附加屬性。對於使用MVVM框架的項目,附加屬性是非常重要的一個特性。 在MVVM框架下,ViewModel的代碼通過控制項的依賴屬性來控制控制項的,例如: //ViewModel public Visibility GridVisibility {get;set} public v ...
  • 一:背景 在 記憶體泄漏 的系列問題中,有一類問題是 記憶體碎片化 導致的,而且這種更容易發生在 LOH 上,因為它預設不開啟 對象壓縮,一般遇到這種情況,優先讓朋友執行下麵的代碼應急。 GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHe ...
  • 概述 本文描述幾個WPF的常用特性,包括:樣式、觸發器和控制項模板。 樣式/Style Style就是控制項的外觀,在XAML中,我們通過修改控制項的屬性值來設置它的樣式,如: <!--直接定義style--> <Border Grid.Row="0" Grid.Column="0" Background ...
  • 概述 本文描述幾款WPF中常用的佈局控制項。 Grid Grid是WPF最常用的佈局控制項。 它把面板分割為固定長和寬的網格,子控制項就放置在網格內。 <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefini ...
  • WPF是一個生不逢時的技術,剛推出的時候由於機器性能的原因會感覺很卡,等機器性能提高了,WEB時代又來了,做桌面應該的本來就不多了,加上WinForm又比較簡單易用,誰還用WPF呢! 在種情況下寫一個WPF快速開發入門的教程的意義是什麼呢?本教程是針對具備WinForm經驗的.NET開發人員,我希望... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...