優(yōu)秀的網(wǎng)站首頁布局360競價推廣
什么是線程模型:
Java字節(jié)碼運行在JVM中,JVM運行在各個操作系統(tǒng)上。所以當JVM想要進行線程創(chuàng)建回收這種操作時,勢必需要調(diào)用操作系統(tǒng)的相關(guān)接口。也就是說,JVM線程與操作系統(tǒng)線程之間存在著某種映射關(guān)系,這兩種不同維度的線程之間的規(guī)范和協(xié)議,就是線程模型。
可能有讀者會存在疑惑:為什么需要這樣的中間層?我們在開發(fā)時,直接調(diào)用操作系統(tǒng)的接口來創(chuàng)建回收線程不是更直接嗎?這個問題的答案顯而易見,正如我們現(xiàn)在不常用匯編語言進行開發(fā),而是使用更加簡單容易上手的高級語言一樣,這是一種自下而上的抽象。
JVM線程對不同操作系統(tǒng)上的原生線程進行了高級抽象,使開發(fā)者大多數(shù)情況下可以不用關(guān)注下層細節(jié),而只要專注上層開發(fā)。不過在學習過程中,我們秉持知其然并知其所以然的態(tài)度,就需要去理解這種抽象方式,這也有助于將來我們自己進行一些設(shè)計的時候,能夠復(fù)用前人的思想。
理解了什么是線程模型,為什么要有線程模型。接下來介紹一下JVM線程模型的三種類型: 一對一,多對一,多對多。
內(nèi)核線程:
在具體介紹這三種類型之前,有必要先來介紹一下操作系統(tǒng)的內(nèi)核線程本身是什么樣的面貌。這里我們就以最主流的Linux內(nèi)核為例。
有一道面試題非常普遍:“說說線程和進程的區(qū)別” 。網(wǎng)上流傳的答案之一是“線程屬于進程”,這個說法是不準確的。Linux線程又被稱為“輕量級進程”,這就使很多同學摸不著頭腦,那到底是線程還是進程?我們可以這么去理解,“線程” 是抽象概念(KLT, 內(nèi)核線程),因為Linux內(nèi)部沒有專門為線程定義的數(shù)據(jù)結(jié)構(gòu)和調(diào)度算法,所以Linux去實現(xiàn)“線程”的方式是“輕量級進程”(LWP, 輕量級進程),本質(zhì)還是進程。只不過加了一個“輕量級”的修飾詞。
?
“輕量級進程”與“進程”的區(qū)別在哪? 一個Linux進程擁有自己獨立的地址空間,而一個輕量級進程沒有自己獨立的地址空間,只能共享同一個輕量級進程組下的地址空間。進程和輕量級進程的創(chuàng)建都使用clone系統(tǒng)調(diào)用,區(qū)別僅僅在于向clone函數(shù)傳遞的參數(shù)不同,來指定是否共享地址空間等資源。
明白了Linux內(nèi)核線程的真面目,我們就來講三種Java線程模型的區(qū)別。
一對一
可以看下面這張圖,一目了然,這種線程模型就是在Java線程(用戶線程)與操作系統(tǒng)線程(KLT)之間建立一對一的關(guān)系,這種關(guān)系看上去簡單粗暴,但就是好用。
?
優(yōu)點:
每個線程都是獨立的調(diào)度單元,直接利用操作系統(tǒng)內(nèi)核提供的調(diào)度功能。
缺點:
用戶線程的阻塞喚醒,會直接映射到內(nèi)核線程上,容易引起頻繁切換,降低性能。但是一些語言引入了CAS來避免一部分的內(nèi)核調(diào)用,比如Java引入了AQS這種函數(shù)級別的鎖,減少使用內(nèi)核級別的鎖,就能提升性能。
Linux內(nèi)核能夠創(chuàng)建的資源畢竟是有限的,所以這在一定程度上會限制并發(fā)量。
目前大部分主流JVM.上都是采用的這種線程模型。
UT=用戶線程; LWP=輕量級進程; KLT=內(nèi)核線程
多對一
可以看下面這張圖,圖上多個用戶線程映射到一個內(nèi)核線進程上,用戶線程的調(diào)度需要由用戶空間來完成。
?
優(yōu)點:
提升并發(fā)量上限,大部分調(diào)度和同步操作都在用戶空間內(nèi)完成,減少狀態(tài)切換,能夠提升性能。
缺點:
當一個用戶線程進行了內(nèi)核調(diào)用并阻塞了,那么其他線程在這段時間里都無法進行內(nèi)核調(diào)用。
Java早期版本就是采用的這種線程模型,不過后來被拋棄了。
多對多
來看下面這張圖?;旧夏芸吹贸鰜?#xff0c;這種方式的優(yōu)點能夠解決一對一和多對一模型的缺點,綜合它們的優(yōu)點。不過缺點就是,要實現(xiàn)這種線程模型難度比較高。
Go語言采用的GMP線程模型就是基于多對多的方式來實現(xiàn)的,這也是為什么能夠利用goroutine實現(xiàn)更高并發(fā)的原因。值得一提的是,Java的Loom項目也在進行這方面的探索。