Java 線(xiàn)程和操作系統的線(xiàn)程有啥區別?
想看解釋的小伙伴可直接翻到文末尋找答案。
1. 用戶(hù)空間和內核空間
關(guān)于內核態(tài)和用戶(hù)態(tài)我們在 了解操作系統的那些事兒,從這篇文章開(kāi)始 這篇文章中已經(jīng)詳細介紹過(guò),這里不再過(guò)多贅述。
至于什么是系統空間和用戶(hù)空間也非常好理解:在操作系統中,內存通常會(huì )被分成用戶(hù)空間(User space)與內核空間(Kernel space)這兩個(gè)部分。當進(jìn)程/線(xiàn)程運行在用戶(hù)空間時(shí)就處于用戶(hù)態(tài),運行在內核空間時(shí)就處于內核態(tài):
- 運行在內核態(tài)的程序可以訪(fǎng)問(wèn)用戶(hù)空間和內核空間,或者說(shuō)它可以訪(fǎng)問(wèn)計算機的任何資源,不受限制,為所欲為,例如協(xié)調 CPU 資源,分配內存資源,提供穩定的環(huán)境供應用程序運行等
- 而應用程序基本都是運行在用戶(hù)態(tài)的,或者說(shuō)用戶(hù)態(tài)就是提供應用程序運行的空間。運行在用戶(hù)態(tài)的程序只能訪(fǎng)問(wèn)用戶(hù)空間
那為什么要區分用戶(hù)態(tài)和內核態(tài)呢?
其實(shí)早期操作系統是不區分用戶(hù)態(tài)和內核態(tài)的,也就是說(shuō)應用程序可以訪(fǎng)問(wèn)任意內存空間,如果程序不穩定常常會(huì )讓系統崩潰,比如清除了操作系統的內存數據。為此大佬們設計出了一套規則:對于那些比較危險的操作需要切到內核態(tài)才能運行,比如 CPU、內存、設備等資源管理器程序就應該在內核態(tài)運行,否則安全性沒(méi)有保證。
舉個(gè)例子,對于文件系統和數據來(lái)說(shuō),文件系統數據和管理就必須放在內核態(tài),但是用戶(hù)的數據和管理可以放在用戶(hù)態(tài)。
用戶(hù)態(tài)的程序不能隨意操作內核地址空間,這樣有效地防止了操作系統程序受到應用程序的侵害。
那如果處于用戶(hù)態(tài)的程序想要訪(fǎng)問(wèn)內核空間的話(huà)怎么辦呢?就需要進(jìn)行系統調用從用戶(hù)態(tài)切換到內核態(tài)。
2. 操作系統線(xiàn)程① 在用戶(hù)空間中實(shí)現線(xiàn)程在早期的操作系統中,所有的線(xiàn)程都是在用戶(hù)空間下實(shí)現的,操作系統只能看到線(xiàn)程所屬的進(jìn)程,而不能看到線(xiàn)程。
從我們開(kāi)發(fā)者的角度來(lái)理解用戶(hù)級線(xiàn)程就是說(shuō):在這種模型下,我們需要自己定義線(xiàn)程的數據結構、創(chuàng )建、銷(xiāo)毀、調度和維護等,這些線(xiàn)程運行在操作系統的某個(gè)進(jìn)程內,然后操作系統直接對進(jìn)程進(jìn)行調度。
這種方式的好處一目了然,首先第一點(diǎn),就是即使操作系統原生不支持線(xiàn)程,我們也可以通過(guò)庫函數來(lái)支持線(xiàn)程;第二點(diǎn),線(xiàn)程的調度只發(fā)生在用戶(hù)態(tài),避免了操作系統從內核態(tài)到用戶(hù)態(tài)的轉換開(kāi)銷(xiāo)。
當然缺點(diǎn)也很明顯:由于操作系統看不見(jiàn)線(xiàn)程,不知道線(xiàn)程的存在,而 CPU 的時(shí)間片切換是以進(jìn)程為維度的,所以如果進(jìn)程中某個(gè)線(xiàn)程進(jìn)行了耗時(shí)比較長(cháng)的操作,那么由于用戶(hù)空間中沒(méi)有時(shí)鐘中斷機制,就會(huì )導致此進(jìn)程中的其它線(xiàn)程因為得不到 CPU 資源而長(cháng)時(shí)間的持續等待;另外,如果某個(gè)線(xiàn)程進(jìn)行系統調用時(shí)比如缺頁(yè)中斷而導致了線(xiàn)程阻塞,此時(shí)操作系統也會(huì )阻塞住整個(gè)進(jìn)程,即使這個(gè)進(jìn)程中其它線(xiàn)程還在工作。
② 在內核空間中實(shí)現線(xiàn)程所謂內核級線(xiàn)程就是運行在內核空間的線(xiàn)程, 直接由內核負責,只能由內核來(lái)完成線(xiàn)程的調度。
幾乎所有的現代操作系統,包括 Windows、Linux、Mac OS X 和 Solaris 等,都支持內核線(xiàn)程。
每個(gè)內核線(xiàn)程可以視為內核的一個(gè)分身,這樣操作系統就有能力同時(shí)處理多件事情,支持多線(xiàn)程的內核就叫做多線(xiàn)程內核(Multi-Threads Kernel)。
從我們開(kāi)發(fā)者的角度來(lái)理解內核級線(xiàn)程就是說(shuō):我們可以直接使用操作系統中已經(jīng)內置好的線(xiàn)程,線(xiàn)程的創(chuàng )建、銷(xiāo)毀、調度和維護等,都是直接由操作系統的內核來(lái)實(shí)現,我們只需要使用系統調用就好了,不需要像用戶(hù)級線(xiàn)程那樣自己設計線(xiàn)程調度等。
上圖畫(huà)的是 1:1 的線(xiàn)程模型,所謂線(xiàn)程模型,也就是用戶(hù)線(xiàn)程和內核線(xiàn)程之間的關(guān)聯(lián)方式,線(xiàn)程模型當然不止 1:1 這一種,下面我們來(lái)詳細解釋以下這三種多線(xiàn)程模型:
下文翻譯自 https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/4_Threads.html
1)多對一線(xiàn)程模型:
- 在多對一模型中,多個(gè)用戶(hù)級線(xiàn)程映射到某一個(gè)內核線(xiàn)程上
- 線(xiàn)程管理由用戶(hù)空間中的線(xiàn)程庫處理,這非常有效
- 但是,如果進(jìn)行了阻塞系統調用,那么即使其他用戶(hù)線(xiàn)程能夠繼續,整個(gè)進(jìn)程也會(huì )阻塞
- 由于單個(gè)內核線(xiàn)程只能在單個(gè) CPU 上運行,因此多對一模型不允許在多個(gè) CPU 之間拆分單個(gè)進(jìn)程
從并發(fā)性角度來(lái)總結下,雖然多對一模型允許開(kāi)發(fā)人員創(chuàng )建任意多的用戶(hù)線(xiàn)程,但是由于內核只能一次調度一個(gè)線(xiàn)程,所以并未增加并發(fā)性?,F在已經(jīng)幾乎沒(méi)有操作系統來(lái)使用這個(gè)模型了,因為它無(wú)法利用多個(gè)處理核。
2)一對一線(xiàn)程模型:
- 一對一模型克服了多對一模型的問(wèn)題
- 一對一模型創(chuàng )建一個(gè)單獨的內核線(xiàn)程來(lái)處理每個(gè)用戶(hù)線(xiàn)程
- 但是,管理一對一模型的開(kāi)銷(xiāo)更大,涉及更多開(kāi)銷(xiāo)和減慢系統速度
- 此模型的大多數實(shí)現都限制了可以創(chuàng )建的線(xiàn)程數
從并發(fā)性角度來(lái)總結下,雖然一對一模型提供了更大的并發(fā)性,但是開(kāi)發(fā)人員應注意不要在應用程序內創(chuàng )建太多線(xiàn)程(有時(shí)系統可能會(huì )限制創(chuàng )建線(xiàn)程的數量),因為管理一對一模型的開(kāi)銷(xiāo)更大。Windows (從 Win95 開(kāi)始) 和 Linux 都實(shí)現了線(xiàn)程的一對一模型。
3)多對多線(xiàn)程模型:
- 多對多模型將任意數量的用戶(hù)線(xiàn)程復用到相同或更少數量的內核線(xiàn)程上,結合了一對一和多對一模型的最佳特性
- 用戶(hù)對創(chuàng )建的線(xiàn)程數沒(méi)有限制
- 阻止內核系統調用不會(huì )阻止整個(gè)進(jìn)程
- 進(jìn)程可以分布在多個(gè)處理器上
- 可以為各個(gè)進(jìn)程分配可變數量的內核線(xiàn)程,具體取決于存在的 CPU 數量和其他因素
在進(jìn)入 Java 線(xiàn)程主題之前,有必要講解一下線(xiàn)程庫 Thread library 的概念。
在上面的模型介紹中,我們提到了通過(guò)線(xiàn)程庫來(lái)創(chuàng )建、管理線(xiàn)程,那么什么是線(xiàn)程庫呢?
線(xiàn)程庫就是為開(kāi)發(fā)人員提供創(chuàng )建和管理線(xiàn)程的一套 API。
當然,線(xiàn)程庫不僅可以在用戶(hù)空間中實(shí)現,還可以在內核空間中實(shí)現。前者涉及僅在用戶(hù)空間內實(shí)現的 API 函數,沒(méi)有內核支持。后者涉及系統調用,也就是說(shuō)調用庫中的一個(gè) API 函數將會(huì )導致對內核的系統調用,并且需要具有線(xiàn)程庫支持的內核。
下面簡(jiǎn)單介紹下三個(gè)主要的線(xiàn)程庫:
1)POSIX Pthreads:可以作為用戶(hù)或內核庫提供,作為 POSIX 標準的擴展
2)Win32 線(xiàn)程:用于 Window 操作系統的內核級線(xiàn)程庫
3)Java 線(xiàn)程:Java 線(xiàn)程 API 通常采用宿主系統的線(xiàn)程庫來(lái)實(shí)現,也就是說(shuō)在 Win 系統上,Java 線(xiàn)程 API 通常采用 Win API 來(lái)實(shí)現,在 UNIX 類(lèi)系統上,采用 Pthread 來(lái)實(shí)現。
下面我們來(lái)詳細講解 Java 線(xiàn)程:
事實(shí)上,在 JDK 1.2 之前,Java 線(xiàn)程是基于稱(chēng)為 "綠色線(xiàn)程"(Green Threads)的用戶(hù)級線(xiàn)程實(shí)現的,也就是說(shuō)程序員大佬們?yōu)?JVM 開(kāi)發(fā)了自己的一套線(xiàn)程庫或者說(shuō)線(xiàn)程管理機制。
而在 JDK 1.2 及以后,JVM 選擇了更加穩定且方便使用的操作系統原生的內核級線(xiàn)程,通過(guò)系統調用,將線(xiàn)程的調度交給了操作系統內核。而對于不同的操作系統來(lái)說(shuō),它們本身的設計思路基本上是完全不一樣的,因此它們各自對于線(xiàn)程的設計也存在種種差異,所以 JVM 中明確聲明了:虛擬機中的線(xiàn)程狀態(tài),不反應任何操作系統中的線(xiàn)程狀態(tài)。
也就是說(shuō),在 JDK 1.2 及之后的版本中,Java 的線(xiàn)程很大程度上依賴(lài)于操作系統采用什么樣的線(xiàn)程模型,這點(diǎn)在不同的平臺上沒(méi)有辦法達成一致,JVM 規范中也并未限定 Java 線(xiàn)程需要使用哪種線(xiàn)程模型來(lái)實(shí)現,可能是一對一,也可能是多對多或多對一。
總結來(lái)說(shuō),回答下文題,現今 Java 中線(xiàn)程的本質(zhì),其實(shí)就是操作系統中的線(xiàn)程,其線(xiàn)程庫和線(xiàn)程模型很大程度上依賴(lài)于操作系統(宿主系統)的具體實(shí)現,比如在 Windows 中 Java 就是基于 Wind32 線(xiàn)程庫來(lái)管理線(xiàn)程,且 Windows 采用的是一對一的線(xiàn)程模型。
*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。