什么是Java線程模型
因為Java字節碼運行在JVM中,而JVM運行在各個操作系統上,所以當JVM想要進行線程創建和回收的這種操作時,是必須要調用操作系統的相關接口,也就是說JVM線程與操作系統線程之間存在着某種映射關系。
這兩種不同維度的線程之間的規范和協議呢,就是線程模型。有人可能要問了那為什么需要這種線程模型?我們在開發時如果直接調用操作系統的接口來創建和回收線程不是更加直接嗎,這個問題的答案呢其實很容易理解就像我們現在為什么不常用匯編語言來進行開發而是使用更加簡單更加容易上手的高級語言一樣,這是一種自下而上的抽象方式。JVM線程對不同操作系統的原生線程進行了高級抽象,可以使開發者一般情況下可以不用關注下層的細節,而只要專注上層的開發就行了。
但是對於我們個人來說還是需要秉持知其然並知其所以然的態度,就要去理解這種抽象方式,這也有助於我們在將來自己進行一些設計的時候,能夠調用前人的思想。
線程模型種類
在說線程模型之前,我們先看看操作系統內核線程是怎樣的。
我們以Linux系統為例,在Linux系統中Linux線程KLT(Kernel Level Thread)又被稱為輕量級進程LWP(Light Weight Process),那么到底這是線程還是進程呢?我們可以這么去理解,線程是抽象概念,因為Linux內核沒有專門為線程定義數據結構和調度算法,所以Linux去實現線程的方式是輕量級進程,其實本質還是進程,只不過加了一個輕量級的修飾詞。那么輕量級進程與進程之間的區別在哪呢?一個Linux進程擁有自己獨立的地址空間,而一個輕量級進程沒有自己獨立的地址空間,只能共享同一個輕量級進程組下的地址空間。
Linux進程和線程都是使用了clone系統調用,區別僅僅在於向clone函數傳遞的參數不同,不同的參數代表是否指定共享地址空間等資源。好了下面我們來看線程模型的種類。
線程模型主要有三類分別如下:
1. 一對一:1:1
    
即一個用戶線程對應一個內核線程,內核負責每個線程的調度,可以調度到其他處理器上面。
一對一這種線程模型就是用戶線程與內核線程建立了一對一的關系,即一個用戶線程對應一個內核線程,內核負責每個線程的調度。
優點
1)這種關系模型比較簡單好用,可以解決大部分場景下的問題。
缺點:
1)用戶線程的阻塞和喚醒會直接映射到內核線程,容易引起內核態和用戶態的切換,這種頻繁切換會降低性能。但是一些語言引入了CAS機制來避免一部分情況下的切換,比如Java語言就使用了AQS這種函數級別的鎖來減少內核級別的鎖,就能有效的提升性能。
2)Linux系統內核能夠創建的線程數量還是比較有限的,所以這在一定程度上會限制並發量。目前大部分主流的JVM上都是采用這種線程模型。
2. 多對一:N:1
    
多對一線程模型,又叫作用戶級線程模型,即多個用戶線程映射到同一個內核線程上,用戶線程的創建、調度、同步的所有操作全部都是由用戶空間的線程來完成的。
優點:
1)能夠提高並發量
2)大部分調度操作都是在用戶空間完成,可以有效提升性能。
缺點:
1)當一個用戶線程進行了內核調用並且阻塞的時候,其實線程在阻塞的這段時間內都無法進行內核調用,這非常致命。Java語言早起版本就是使用了這種線程模型。
3. 多對多:M:N
     
多對多模型,又叫作兩級線程模型,它充分吸收前面兩種線程模型的優點且盡量規避它們的缺點。在此模型下用戶線程與內核線程是多對多(M : N,通常M >= N)的映射模型。
優點:
1) 兼具多對一模型的輕量。
2)由於對應了多個內核線程,則一個用戶線程阻塞時,其他用戶線程仍然可以執行;
3)由於對應了多個內核線程,則可以實現較完整的調度、優先級等;
缺點:
1)實現比較復雜,比如Go語言采用的GMP線程模型就是這種多對多的方式實現的,這也是為什么使用goroutine可以實現高並發的原因之一。目前Java的Loom項目也在這方面進行探索。
結語
了解線程模型其實在對我們理解基於Java對象的悲觀鎖和基於AQS的樂觀所都是有幫助的。
