Java多執行緒模式,java多線程
談到Java多線程就涉及到多線程的模型及Java線程與底層作業系統之間的關係。正如我們熟知,現代機器可以分為硬體和軟體兩大塊,2-5-1-1,硬體是基礎,軟體提供實現不同功能的手段。而且軟體可以分為作業系統和應用程式,作業系統專註於對硬體的互動管理並提供一個運行環境給應用程式使用,應用程式則是能實現若干功能的並且運行在作業系統環境中的軟體。同樣,線程按照作業系統和應用程式兩層次可以分為核心線程(Kernel Thread)和使用者線程(User Thread)。
圖2-5-1-1
所謂核心線程就是直接由作業系統核心支援和管理的線程,線程的建立、啟動、同步、銷毀、切換等操作都由核心完成。基本所有的現代作業系統都支援核心線程。使用者線程指完全建立在使用者空間的線程庫上,由核心支援而無需核心管理,核心也無法感知使用者線程的存在,線程的建立、啟動、同步、銷毀、切換完全在使用者態完成,無需切換到核心。可以把使用者線程看成是更高層面的線程,而核心線程則是最底層的支援,那麼他們之間必然存在一定的映射關係,一般有三種常用的關係,下面將逐個列出。
① 一對一模型
一對一模型可以說是最簡單的映射模型,2-5-1-2,KT為核心線程,UT為使用者線程,每個使用者線程都對應一個核心線程,由於每個使用者線程都有各自的核心線程,所以他們互不影響,即使其中一個線程阻塞,也允許另一個線程繼續執行,這無疑是此模型的優點,但也存在一個嚴重的缺陷,由於一對一的關係,有多少個使用者線程就代表有多少個核心線程,而核心線程的開銷較大,一般作業系統都會有核心線程數量的限制,使用者線程的數量也被限制。
圖2-5-1-2
② 多對一模型
第二種是多對一模型,2-5-1-3,可以清晰看到多個使用者線程映射到同一個核心線程上,可以看成由一條核心線程實現若干個使用者線程的並發功能,線程的管理在使用者空間中進行,一般不需要切換到核心態,效率較高,而且比起一對一模型,支援的線程數量更大。但此模型有個致命的弱點是如果一個線程執行了阻塞調用,所有線程都將阻塞,並且任意時刻只能有一個線程訪問核心。另外,對線程的所有操作都將由使用者應用自己處理。所以一般除了在不支援多線程的作業系統被迫使用此模型外,在多線程作業系統中基本不使用。
圖2-5-1-3
③ 多對多模型
多對多模型的提出是為瞭解決前面兩種模型的缺點,2-5-1-4,多個使用者線程與多個核心線程映射形成多工。前面提到的一對一模型存在受核心線程數量限制的問題,多對一模型雖然解決了數量限制問題,但它存在一個線程阻塞導致所有線程阻塞的風險,而且由於一個核心線程只能調度一個線程導致並發性不強。看看多對多模型如何解決這些問題,由於多對一是多對多的子集,所以多對多具備多對一的優點,線程數不受限制。除此之外,多個核心線程可處理多個使用者線程,當某個線程阻塞時,將可以調度另外一個線程執行,這從另一方面看也是增強了並發性。
圖2-5-1-4
三種模型各自有各自的特點,不同的現代作業系統可能使用不同的執行緒模式,例如linux和windows可能使用了一對一模型,而solaris和unix某些版本可能使用多對多模型。對於線程的建立和管理主要由線程庫提供使用者層級和核心層級兩種API進行操作。使用者層級由於不涉及核心操作,所有代碼和資料結構均存放在使用者空間,與此相反,核心層級由核心支援,將直接調用核心系統操作,代碼和資料結構存在與核心空間中。在實際程式中我們一般不直接使用核心線程,使用者線程與核心線程直接需要一種中間資料結構,它由核心支援且是核心線程的進階抽象,這個進階介面被稱為輕量級進程(Light Weight Process),下面簡稱LWP。圖2-5-1-5是三種模型增加了輕量級進程的,從某種層面上看,LWP最多算是廣義的使用者線程,並非狹義定義的使用者進程,LWP線程庫是以核心為基礎,很多操作要進行核心調用,效率不高,如果要快速低消耗的操作則需要一個純粹的使用者線程,線程庫完全建立在使用者空間。於是可以看到一個進程P裡面一般包含若干個使用者進程,使用者進程以某種關係對應輕量級進程,而輕量級進程則是核心線程的進階體現。如此一來,一個核心線程堵塞將導致LWP也阻塞,與LWP相連的使用者線程也將阻塞。
圖2-5-1-5
最後要談談Java線程與底層作業系統的關係,由於Java通過JVM封裝了底層作業系統的差異,所以Java線程也必然是要封裝不同作業系統提供一個統一的並發定義,在JDK發展曆史上,java語言開發人員曾經通過一類叫“綠色線程(Green Threads)”的使用者線程進行實現Java線程,但從jdk1.2開始,java線程使用作業系統原生執行緒模式實現,也就是說Java線程的實現通過不同作業系統提供的線程庫分別實現,JVM根據不同作業系統的執行緒模式對Java線程進行映射,假如Java運行在windows系統上,它通常直接使用Win32 API實現多線程,假如Java運行在linux系統則直接使用Pthread線程庫實現多線程。這樣一來就順利隱藏了底層實現細節,提供給開發人員就是一個具有統一抽象的線程語義。