1. 程式人生 > >JVM高階特性與實踐(十三):執行緒實現 與 Java執行緒排程

JVM高階特性與實踐(十三):執行緒實現 與 Java執行緒排程

一. 執行緒的實現

執行緒其實是比程序更輕量級的排程執行單位。執行緒的引入,可以把一個檢查的資源分配和執行排程分開,各個執行緒既可以共享資源(記憶體地址、檔案I/O等),又可以獨立排程(執行緒是CPU排程的基本單位)。

執行緒實現 還是 Java執行緒實現?

主流的作業系統都有提供執行緒實現,Java語言則提供了不同硬體和作業系統平臺下對執行緒操作的統一處理,每個已經執行start() 且還未結束的 java.lang.Thread 類的例項就代表了一個執行緒。

Thread類與大部分Java API有顯著的差別,因為它所有的關鍵方法都宣告 Native的。在Java API 中,一個Native

方法往往意味著這個方法沒有使用或無法使用平臺無關的手段。正因如此,此小節標題作為“執行緒實現”而不是“Java執行緒實現”。

接下來介紹實現執行緒的3種主要方式:

  • 使用核心執行緒實現
  • 使用使用者執行緒實現
  • 使用使用者執行緒加輕量級程序混合實現

1. 使用核心執行緒(Kernel-Level Thread)實現

(1)定義

核心執行緒(Kernel-Level Thread): 就是直接由作業系統核心(下稱核心)支援的執行緒,這種執行緒由核心來完成執行緒切換,核心通過操縱排程器對執行緒進行排程,並負責將執行緒的任務對映到各個處理器上。

多執行緒核心(Multi-Threads Kernel):

每個核心執行緒可以視為核心的一個分身,這樣作業系統就有能力同時處理多個事情,支援多執行緒的核心就叫多執行緒核心

(2)輕量級程序(Light Weight Process)

程式一般不會直接去使用核心執行緒,而是去使用核心執行緒的一種高階介面——輕量級程序輕量級程序就是我們通常意義上講的執行緒。

(3)輕量級程序與核心執行緒之間關係

由於每個輕量級執行緒都由一個核心執行緒支援,因此只有先支援核心執行緒,才能有輕量級程序。這種輕量級程序與核心執行緒間1:1 的關係稱為一對一的執行緒模型,如下圖所示

這裡寫圖片描述

(4)輕量級程序的侷限性

由於核心執行緒的支援,每個輕量級程序都成為一個獨立的排程單元,即使有一個輕量級程序在系統呼叫中阻塞了,也不會影響整個程序繼續工作,但是輕量級程序有它的侷限性:

  • 由於是基於核心執行緒實現的,所以各種執行緒操作,如建立,析構及同步,都需要進行系統呼叫, 而系統呼叫的代價相對較高,需要在使用者態和核心態中來回切換。
  • 每個輕量級程序都需要有一個記憶體執行緒的支援,因此輕量級程序要消耗一定的核心資源(如核心執行緒的棧空間),因此一個系統支援輕量級程序的數量是有限的。

2. 使用使用者執行緒(User Thread)實現

(1)定義

  • 從廣義而言,一個執行緒只要不是核心執行緒,就可認為是使用者執行緒因此,輕量級程序也屬於使用者執行緒,但輕量級程序實現始終建立在核心執行緒上,許多操作都要系統呼叫,效率受到限制。

  • 從狹義而言, 使用者執行緒指的是完全建立在使用者空間的執行緒庫上,系統核心不能感知執行緒存在的實現。使用者執行緒的建立、同步、銷燬和排程完全在使用者態中完成,不需要核心幫助。

(2)程序與使用者程序關係

如果處理得當,此執行緒不需要切換到核心態,因此操作時非常快速和低耗能的,也可支援大規模執行緒數量,部分高效能資料庫中的多執行緒就是由使用者執行緒實現的。

這種程序使用者執行緒之間的關係如下:

這裡寫圖片描述

(3)使用者執行緒的優劣勢

  • 優勢:不需要系統核心支援。
  • 劣勢:沒有系統核心支援,所有的執行緒操作都需要使用者程式自行處理。

執行緒的建立、切換和排程都需要考慮,由於作業系統只把處理器資源分配到程序,那“阻塞如何處理”諸如此類問題解決會很困難。

(4)使用者執行緒已被放棄

因而使用使用者執行緒實現的程式一般比較複雜,除了以前在不支援多執行緒的作業系統中有使用,現在已被拋棄。

3. 使用使用者執行緒加輕量級程序混合實現

(1)介紹

執行緒除了依賴核心執行緒實現和完全由使用者程式自行實現外,還有一種混合實現,在該實現方式下,既存在使用者執行緒,也存在輕量級程序。

(2)優勢

  • 使用者執行緒還是完全建立在使用者空間中:因此使用者執行緒的建立,切換,析構等操作依然廉價,並且可以支援大規模的使用者執行緒併發。

  • 作業系統提供支援的輕量級程序則作為使用者執行緒和核心執行緒之間的橋樑:這樣可以使用核心提供的執行緒排程功能及處理器對映,並且使用者執行緒的系統呼叫要通過輕量級執行緒來完成,大大降低了整個程序被完全阻塞 的風險.

(3)使用者執行緒與輕量級程序關係

在這種混合模式中,使用者執行緒輕量級程序的數量比是不定的,即為 N:M 的關係,如下圖所示, 這種就是多對多的執行緒模型

這裡寫圖片描述

二. Java 執行緒排程 和 狀態轉換

1. Java執行緒排程

(1)定義

執行緒排程是指系統為執行緒分配處理器使用權的過程。主要排程方式有:協同式執行緒排程 和 搶佔式執行緒排程

(2)協同式執行緒排程(Cooperative Threads-Scheduling)

定義

在此多執行緒系統中,執行緒的執行時間由執行緒本身控制,執行緒把自己的工作執行完之後,要主動通知系統切換到另外一個執行緒上。

優劣勢

  • 好處:實現簡單,由於執行緒要把自己事情幹完才會進行執行緒切換,切換操作對執行緒自己是可知的,所以沒什麼執行緒同步問題。
  • 壞處:執行緒執行時間不可控,如果一個執行緒編寫有問題,一直不告知系統進行執行緒切換,那麼程式就會一直阻塞在那裡。

(3)搶佔式執行緒排程(Preemptive Threads-Scheduling)

定義

在此多執行緒系統中,每個執行緒將由系統來分配時間,執行緒的切換不由執行緒本身決定(Java中,Thread.yield() 可以讓出執行時間,但是執行緒無法獲取執行時間)。

優勢

執行緒執行時間是系統可控的,也不會出現一個執行緒導致整個程序阻塞的問題。Java使用的執行緒排程方法就是這個。

(4)Java執行緒優先順序

雖然Java 執行緒排程是系統自動完成的, 但我們還是可以建議系統給某些執行緒多分配一點執行時間,另外一些執行緒則可以少分配一點——這項操作可以通過設定執行緒優先順序來完成。Java語言一共設定了10個級別的優先順序,在兩個執行緒同時處於 Ready狀態,優先順序越高的執行緒越容易被系統選擇執行。

不過執行緒優先順序並不是太靠譜,原因是因為Java的執行緒是通過對映到系統的原生執行緒上來實現的,所以執行緒排程最終還是取決於 作業系統,雖然現在很多os 都提供了執行緒優先順序,但不見得和 能與 java執行緒的優先順序一一對應。如 Solaris中有 2^32 種優先順序,而windows只有7種 。

(5)Java 執行緒優先順序與Windows執行緒優先順序對應關係

下表顯示了 Java執行緒優先順序 與 Windows 執行緒優先順序之間的對應關係:

這裡寫圖片描述

(6)優先順序可能會被系統自行改變

上文說到的“Java執行緒優先順序並不是太靠譜”,不僅僅是在說一些平臺上不同的優先順序實際會變得相同這一點,還有其他情況讓我們不能太依賴優先順序:優先順序可能會被系統自行改變。

例如:在Windows 中存在一個稱為 “優先順序推進器”的功能,作用是 當系統發現一個執行緒執行得特別勤奮的話,可能會越過執行緒優先順序去為它分配執行時間。因此,我們不能在程式中通過優先順序來完全準備判斷一組狀態都為Ready的執行緒將會先執行哪個。

2. 狀態轉換

(1)Java語言中執行緒的6種狀態

Java語言定義了6種狀態,在任意一個時間點,一個執行緒只能有且其中一種狀態,分別如下:

  • 新建(New):建立後尚未啟動的執行緒處於這個狀態。

  • 執行(Runnable): Runable包括了os 執行緒狀態中的 RunningReady,也就是處於 此狀態的執行緒有可能正在執行,也有可能正在等待著CPU 為它分配執行時間。

  • 無限期等待(Waiting):處於這種狀態的執行緒不會被分配CPU執行時間,它們要等待被其他執行緒顯式的喚醒。以下方法會讓執行緒陷入無限期的等待狀態:

    • 沒有設定Timeout引數的Object.wait()方法;
    • 沒有設定Timeout引數的 Thread.join() 方法;
    • LockSupport.park() 方法;
  • 限期等待(Timed Waiting):處於這種狀態的執行緒也不會被分配CPU 執行時間,不過無需等待被其他執行緒顯式喚醒,在一定時間之後,它們會由系統自動喚醒。以下方法會讓執行緒進入限期等待狀態:

    • Thread.sleep() 方法;
    • 設定了Timeout引數的Object.wait()方法;
    • 設定了Timeout引數的 Thread.join() 方法;
    • LockSupport.parkNanos() 方法;
    • LockSupport.parkUntil() 方法;
  • 阻塞(Blocked):執行緒被阻塞了, “阻塞狀態”與“等待狀態”的區別是:“阻塞狀態”在等待著獲取到一個排他鎖,這個事件將在另外一個執行緒放棄這個鎖的時候發生;而“等待狀態”則是在等待一段時間,或者喚醒動作的發生。在程式等待進入同步區域的時候,執行緒將進入這種狀態。

  • 結束(Terminated):已經終止執行緒的執行緒狀態,執行緒已經結束執行。

(2)狀態轉換關係圖

上述5種狀態在遇到特定事件發生時會互相轉換,它們的轉換關係如下:

這裡寫圖片描述

三. 總結

上一篇博文結合此篇博文學習瞭解了虛擬機器Java記憶體模型的結構與操作,還有原子性、可見性、有序性在Java記憶體模型的體現,先行發生原則的規則及使用。而此篇文章主要是從JVM的角度來探討實現執行緒的3種主要方式、Java執行緒排程與優先順序、狀態轉換。整體而言,第二大點的內容在學習Java語言過程中多少有涉及,並不陌生,主要是第一大點——執行緒底層實現的主要方式,值得了解學習。

關於“高效併發”這個話題,在這兩篇博文中主要介紹了虛擬機器如何實現“併發”,在接下來一章學習,將主要關注虛擬機器如何實現“高效”,以及虛擬機器對開發人員編寫併發程式碼提供了什麼優化手段。

若有錯誤,虛心指教~