面試官:Java執行緒可以無限建立嗎?

程序员世杰發表於2024-07-06

Java執行緒無限建立

哈嘍,大家好🎉,我是世傑。

⏩本次給大家介紹一下作業系統執行緒和Java的執行緒以及二者的關聯

1. 面試連環call

  1. Java執行緒可以無限建立嗎?
  2. Java執行緒和作業系統執行緒有什麼關聯?
  3. 作業系統為什麼要區分核心態和使用者態?

⏩要想解答這些問題,我們要先從作業系統執行緒開始說起,讓我們開始吧🎉🎉🎉


2. 作業系統執行緒

2.1 核心態和使用者態

根據程序訪問資源的特點,我們可以把程序在系統上的執行分為兩個級別:

  • 使用者態(User Mode) : 使用者態執行的程序可以直接讀取使用者程式的資料,擁有較低的許可權

  • 核心態(Kernel Mode):核心態執行的程序幾乎可以訪問計算機的任何資源包括系統的記憶體空間、裝置、驅動程式等,不受限制,擁有非常高的許可權。當作業系統接收到程序的系統呼叫請求時,就會從使用者態切換到核心態,執行相應的系統呼叫,並將結果返回給程序,最後再從核心態切換回使用者態。

usermode-and-kernelmode

那為什麼要區分使用者態和核心態呢?

  • 在 CPU 的所有指令中,有一些指令是比較危險的比如記憶體分配設定時鐘IO 處理等,如果所有的程式都能使用這些指令的話,會對系統的正常執行造成災難性地影響。因此,我們需要限制這些危險指令只能核心態執行。這些只能由作業系統核心態執行的指令也被叫做 特權指令

  • 如果計算機系統中只有一個核心態,那麼所有程式或程序都必須共享系統資源,例如記憶體、CPU、硬碟等,這將導致系統資源的競爭和衝突,從而影響系統效能和效率。並且,這樣也會讓系統的安全性降低,畢竟所有程式或程序都具有相同的特權級別和訪問許可權。

2.2 使用者態執行緒

早期的作業系統中,所有的執行緒都是在使用者態下實現,作業系統只能排程執行緒所屬的程序,而無法排程執行緒

在這種模型下,使用者需要自己定義執行緒的資料結構、建立、銷燬、排程和維護等,這些執行緒執行在某個程序內,作業系統直接對程序進行排程

img

『優點』

  • 即使作業系統原生不支援執行緒,我們也可以透過庫函式來支援執行緒
  • 執行緒的排程只發生在使用者態,避免了作業系統從核心態到使用者態的轉換開銷。

『缺點』

  • 由於作業系統無法排程執行緒,CPU 的時間片切換是以程序為維度的,如果程序中某個執行緒進行了耗時比較長的操作,那麼由於使用者態中沒有時鐘中斷機制,就會導致此程序中的其它執行緒因為得不到 CPU 資源而長時間的持續等待;
  • 如果某個執行緒進行系統呼叫時比如缺頁中斷而導致了執行緒阻塞,此時作業系統也會阻塞整個程序,即使這個程序中其它執行緒還在工作。

2.3 核心態執行緒

現代作業系統,包括 Windows、Linux、Mac OS X 和 Solaris 等,都支援核心執行緒。執行緒執行在核心空間,直接由核心負責,由核心來完成排程。

此時我們可以直接使用作業系統中已經內建好的執行緒,執行緒的建立、銷燬、排程和維護等,都直接由作業系統的核心來實現,我們只需要使用系統呼叫就好了,不需要像使用者級執行緒那樣自己設計執行緒排程。

img

核心執行緒和使用者執行緒的對應關係並不完全是1對1,其關聯模式有三種

2.4 執行緒模型

多對一執行緒模型

多個使用者執行緒對應到同一個核心執行緒上,執行緒的建立、排程、同步的所有細節全部由程序的使用者空間執行緒庫來處理。這樣,極大地減少了建立核心態執行緒的成本,但是執行緒不可以並行。因此,這種模型現在基本上用的很少。

img

『優點』

  • 使用者執行緒的很多操作對核心來說都是透明的,不需要使用者態和核心態的頻繁切換。使執行緒的建立、排程、同步等非常快。

『缺點』

  • 由於多個使用者執行緒對應到同一個核心執行緒,如果其中一個使用者執行緒阻塞,那麼該其他使用者執行緒也無法執行
  • 核心並不知道使用者態有哪些執行緒,無法像核心執行緒一樣實現較完整的優先順序排程等操作

一對一執行緒模型

該模型為每個使用者態的執行緒分配一個單獨的核心態執行緒,在這種情況下,每個使用者態都需要透過系統呼叫建立一個繫結的核心執行緒。 這種模型允許所有執行緒並行執行,能夠充分利用多核優勢。目前 Linux 中的執行緒OpenJDK Java 執行緒等採用的都是一對一執行緒模型。每一個JVM執行緒,都有一個對應的核心執行緒。

img

『優點』

  • 解決了多對一模型的阻塞排程問題
  • 實現起來較為簡單

『缺點』

  • 每建立一個使用者執行緒,相應地就需要建立一個核心執行緒,開銷較大,因此需要限制整個系統的執行緒數量
  • 對使用者執行緒的大部分操作都會對映到核心執行緒上,引起使用者態和核心態的頻繁切換

多對多執行緒模型

這種模式下會為 n 個使用者態執行緒分配 m 個核心態執行緒。m 通常小於 n。一種可行的策略是將 m 設定為核數。這種多對多的關係,減少了核心執行緒,同時也保證了多核心並行。多對多模型中執行緒的排程需要由核心態和使用者態一起來實現,例如執行緒間同步需要使用者態和核心態共同實現。使用者態和核心態的分工合作導致實現該模型非常複雜。

PS: Linux多執行緒模型曾經也想使用該模型,但它太複雜,要對核心進行大範圍改動,所以還是採用了一對一的模型

img

『優點』

  • 多對多模型將任意數量的使用者執行緒複用到相同或更少數量的核心執行緒上,結合了一對一和多對一模型的最佳特性
  • 使用者對建立的執行緒數沒有限制

『缺點』

  • 實現起來非常複雜

3. Java 執行緒

3.1 執行緒庫

在進入 Java 執行緒主題之前,有必要講解一下執行緒庫 Thread library 的概念。

執行緒庫就是為開發人員提供建立和管理執行緒的一套 API。執行緒庫不僅可以在使用者空間中實現,還可以在核心空間中實現。前者涉及僅在使用者空間內實現的 API 函式,沒有核心支援。後者涉及系統呼叫,也就是說呼叫庫中的一個 API 函式將會導致對核心的系統呼叫,並且需要具有執行緒庫支援的核心。

下面簡單介紹下三個主要的執行緒庫:

  • POSIX執行緒:是[POSIX]的[執行緒]標準,定義了建立和操縱執行緒的一套[API]。實現POSIX執行緒標準的庫常被稱作pthreads,一般用於[Unix-like] POSIX系統,如[Linux]、 [Solaris]。

  • Win32 執行緒:用於 Window 作業系統的核心級執行緒庫

  • Java 執行緒:Java 執行緒 API 通常採用宿主系統的執行緒庫來實現,也就是說在 Win 系統上,Java 執行緒 API 通常採用 Win API 來實現,在 UNIX 類系統上,採用 Pthread 來實現。

3.1 Java執行緒模型

  • 在 JDK 1.2 之前,Java 執行緒是基於稱為 "綠色執行緒"(Green Threads)的使用者級執行緒實現的,JVM 開發了自己的一套執行緒庫或者說執行緒管理機制。

  • 在 JDK 1.2 及以後,JVM 選擇了更加穩定且方便使用的作業系統原生的核心級執行緒,透過系統呼叫,將執行緒的排程交給了作業系統核心。而對於不同的作業系統來說,它們本身的設計思路基本上是完全不一樣的,因此它們各自對於執行緒的設計也存在種種差異,所以 JVM 中明確宣告瞭:虛擬機器中的執行緒狀態,不反應任何作業系統中的執行緒狀態

因此,現今 Java 中執行緒的本質,其實就是作業系統中的執行緒,其執行緒庫和執行緒模型很大程度上依賴於作業系統(宿主系統)的具體實現,比如在 Windows 中 Java 就是基於 Wind32 執行緒庫來管理執行緒,且 Windows 採用的是一對一的執行緒模型

3.2 Java執行緒建立數量

每個執行緒都有一個執行緒棧空間透過-Xss設定,可以透過JVM配置,JVM的預設棧大小

img

不考慮系統限制,可以透過如下公式計算,得出最大執行緒數量

執行緒數量=(機器本身可用記憶體-JVM分配的堆記憶體)/Xss的值

根據計算公式,得出如下結論:

  • 結論1:JVM堆越大,系統建立的執行緒數量越小。

  • 結論2:當-Xss的值越小,可生成執行緒數量越多。

假如我們的容器記憶體大小是8G,堆大小是4096M,走-Xss預設值,可以得出 最大執行緒數量:4096個。

我們知道作業系統分配給每個程序的記憶體大小是有限制的,比如32位的Windows是2G。因此作業系統對一個程序下的執行緒數量是有限制的,不能無限的增多。

如果考慮系統限制,主要跟以下幾個引數有關係

  • /proc/sys/kernel/pid_max 增大,執行緒數量增大,pid_max有最高值,超過之後不再改變,而且32,64位也不一樣

  • /proc/sys/kernel/thread-max 系統可以生成最大執行緒數量

執行緒是非常寶貴的資源,我們要嚴格控制執行緒的數量


『引用』:

Threads

Java 執行緒和作業系統的執行緒有啥區別?

一臺 Java 伺服器可以跑多少個執行緒?

作業系統常見面試題總結(上)

使用者態執行緒和核心態執行緒的區別

相關文章