深入理解JVM(③)線程與Java的線程

前言

我們都知道,線程是比進程更輕量級的調度執行單位,線程的引入,可以把一個進程的資源分配和執行調度分開,各個線程既可以共享進程資源調度(內存地址、文件I/O等),又可以獨立調度。

線程的實現

主流的操作系統都提供了線程實現,Jav語言則是提供了在不同硬件和操作系統平臺下對線程操作的統一處理,每個已經調用過start()方法且還未結束的java.lang.Thread類的實例就代表這一個線程。
其實Thread類與大部分的Java類庫API有着顯著差別,它的所有關鍵方法都被聲明爲Native。在Java中,一個Native方法往往就意味着這個方法沒有使用或無法使用平臺無關的手段來實現(通常最高效率的手段就是平臺相關的手段)。
那麼線程的實現其實是有三種方式的:

  • 使用內核線程實現(1:1實現)
  • 使用用戶線程實現(1:N)實現
  • 使用用戶線程加輕量級進程混合實現

內核線程實現

使用內核線程實現的方式被稱爲1:1實現。內核線程(Kernel Levvel Thread,KLT)就是直接由操作系統內核(Kernel,下稱內核)支持的線程,內核通過操縱調度器(Scheduler)對線程進行調度,並負責將線程的任務映射到各個處理器上。
其實程序一般不會直接使用內核線程,而是使用內核線程的一種高級接口——輕量級進程(Light Weight Process,LWP),輕量級進程就是我們通常所講的線程。這種輕量級進程與內存線程之間1:1的關係稱爲一對一的線程模型
輕量級進程與內核線程之間1:1關係
輕量級進程也具有它的侷限性:首先,由於是基於內核線程實現的,所以各種線程操作(創建、析構及同步),都需要進行系統調用。系統調用就要在用戶態和內核態中來回切換。其次,每個輕量級進程都需要一個內核線程的支持,因此需要消耗一定的內核資源,所以一個系統支持輕量級進程的數量是有限的。

用戶線程實現

使用用戶線程實現的方式被稱爲1:N實現。廣義上來講,一個線程只要不是內核線程,都可以任務是用戶線程(User Threa,UT)的一種。從定義上來看輕量級進程不是內核線程也就是屬於用戶線程,但是它始終是建立在內核之上的,所以效率會受到限制,並不具備用戶線程的優點。

用戶線程的建立、同步、銷燬和調度完全咋用戶態中完成,不需要內核幫助。如果程序實現得當,不需要切換內核態,因此操作可以是非常快且低消耗的,也能夠支持規模更大的線程數量,部分高性能數據庫中的多線程就是由用戶線程實現的。
這種進程與用戶線程之間1:N的關係稱爲一對多的線程模型
進程與用戶線程1:N的關係
用戶線程的速度快低消耗等優勢在於不需要系統內核支援,但是劣勢也在於沒有內核的支援,所有的線程操作都需要由用戶程序自己去處理。這樣就會導致線程的一些問題處理起來就很困難,甚至有些是不可能實現的。
Java、Ruby等予以都曾經使用過用戶線程,最終又都放棄了使用它。

混合實現

線程除了依賴內核線程實現和完全由用戶程序自己實現之外,還有一種將內核線程與用戶線程一起使用的實現方式,被稱爲N:M實現
用戶線程還是完全建立在用戶空間中,因此用戶線程的創建、切換、析構等操作依然廉價,並且可以支持大規模的用戶線程併發。而操作系統支持的輕量級進程則作爲用戶線程和內核線程之間的橋樑,這樣可以使用內核提供的線程調度功能及處理器映射,並且用戶線程的系統調用要通過輕量級進程來完成,大大降低了整個進程被完全阻塞的風險。
在這裏插入圖片描述

Java線程的實現

Java線程如何實現並不受Java虛擬機規範約束,這是一個與具體虛擬機相關的畫圖。Java線程在早期的Classic虛擬機上(JDK1.2以前),是基於一種被稱爲“綠色線程”(Green Threads)的用戶線程實現的,但從JDK1.3起,“主流”平臺上的“主流”商用Java虛擬機的線程模型普遍都被替換爲基於操作系統原生線程模型來實現,即採用1:1的線程模型。
操作系統支持怎樣的線程模型,在很大程度想會影響上面的Java虛擬機的線程是怎麼樣映射的,這一點咋不同的平臺上很難達成一致,因此《Java虛擬機規範》中才不去限定Java線程需要使用哪種線程模型來實現。

Java線程調度

線程調度是指系統爲線程分配處理使用權的過程,調度主要方式有兩種,分別是協同式(Cooperative Threads-Scheduling)線程調度搶佔式(Preemptive Threads-Scheduling)線程調度

  • 協同式線程調度:線程的執行時間由線程本身來控制,線程把自己的工作執行完了之後,要主動通知系統切換到另外一個線程上去。
    優點:實現簡單,切換操作對線程自己是可知的,所以一般沒有什麼線程同步問題。
    缺點:線程執行時間不可控制,甚至如果一個線程的代碼編寫有問題,一直不告知系統進行線程切換,那麼程序就會一直阻塞在那裏。
  • 搶佔式線程調度:每個線程將由系統來分配執行時間,線程的切換不由線程本身來決定。
    優點:可以主動讓出執行時間(例如Java的Thread::yield()方法),並且線程的執行時間是系統可控的,也不會有一個線程導致整個系統阻塞的問題。
    缺點:無法主動獲取執行時間。

Java使用的就是搶佔式線程調度,雖然這種方式的線程調度是系統自己的完成的,但是我們可以給操作系統一些建議,就是通過設置線程優先級來實現。Java語言一共設置了10個級別的線程優先級。在兩個線程同時處於Ready狀態時,優先級越高的線程越容易被系統選擇執行。

不過由於各個系統的提供的優先級數量不一致,所以導致Java提供的10個級別的線程優先級並不見得能與各系統的優先級都一一對應

Java線程狀態轉換

Java語言定義了6種線程狀態,在任意一個時間點鐘,一個線程只能有且只有其中的一種狀態,並且可以通過特定的方法在不同狀態之間切換。

  • 新建(New):創建後尚未啓動的線程處於這種狀態。
  • 運行(Runnable):包括操作系統線程狀態中的Running和Ready,也就是處理此狀態的線程有可能正在執行,也有可能正在等待着操作系統爲它分配執行時間。
  • 無限期等待(Waiting):處於這種狀態的線程不會被分配處理器執行時間,它們要等待被其他線程顯示喚醒。
    以下方法會讓線程陷入無限期等待狀態:
    1、沒有設置Timeout參數的Object::wait()方法;
    2、沒有設置Timeout參數的Thread::join()方法;
    3LockSupport::park()方法。
  • 限期等待(Timed Waiting):處於這種狀態的線程也不會被分配處理器執行時間,不過無須等待被其他線程顯式喚醒,在一定時間之後它們會由系統自動喚醒。
    以下方法會讓線程進入限期等待狀態:
    1Thread::sleep()方法;
    2、設置了Timeout參數的Object::wait()方法;
    3、設置了Timeout參數的Thread::join()方法;
    4LockSupport::parkNanos()方法;
    5LockSupport::parkUntil()方法;
  • 阻塞(Blocked):線程被阻塞了,“阻塞狀態”與“等待狀態”的區別是“阻塞狀態”在等待着獲取到一個排他鎖,這個事件將在另外一個線程放棄這個鎖的時候發生;而“等待狀態”則是在等待一段時間 ,或者喚醒動作發生。在程序進入同步區域的時候,線程將進入這種狀態。
  • 結束(Terminated):已終止線程的線程狀態,線程已經結束執行。

這6種狀態在遇到特定事件發生的時候將會互相轉換,他們的轉換關係如下圖:
線程狀態轉換關係

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章