您現在的位置是:首頁 > 綜藝首頁綜藝

java多執行緒之Thread建構函式(原始碼分析)

由 愚公要移山1 發表于 綜藝2023-01-07
簡介官方文件指出,當java虛擬機器中沒有非守護執行緒了,預設執行緒也會退出

一個類的建構函式最多有幾個

在上一篇文章中對執行緒狀態生命週期和常見的執行緒api進行了一個講解。這篇文章開始著重對其構造方法進行一個說明,也將揭曉為什麼我們呼叫了start方法就能啟動一個執行緒。

一、守護執行緒和非守護執行緒

我們獲取執行緒的id的時候會發現每次都不是0,這是因為在java虛擬機器執行一個執行緒的時候會預設啟動一些其他的執行緒,來為我們的執行緒服務。預設建立的和我們自己建立的執行緒是有區分的。這就要區分守護執行緒和非守護執行緒了。

1、什麼是守護執行緒和非守護執行緒?

預設啟動的這些執行緒就是守護執行緒,他專門處理一些後臺的工作。比如說垃圾回收等。非守護執行緒就是我們自己建立的這些執行緒。官方文件指出,當java虛擬機器中沒有非守護執行緒了,預設執行緒也會退出。舉個例子就能明白:

守護執行緒就像飯店裡面的服務員,非守護執行緒就像是顧客,顧客沒有了,那麼服務員也沒有存在的必要了。

2、程式碼演示

我們透過程式碼來演示一下他們的作用,

java多執行緒之Thread建構函式(原始碼分析)

在這裡主要有兩個執行緒一個是main執行緒,第二個就是自己建立的thread。執行之後很明顯程式會無線的執行下去,因為thread是非守護執行緒。即使是main執行緒執行結束了thread也會執行。現在我們把thread設定為守護執行緒就不一樣了。

java多執行緒之Thread建構函式(原始碼分析)

在執行一遍,我們會發現程式正常的退出了,這是因為我們把thread設定成了守護執行緒,你想想看main執行緒和thread都變成了服務員,現在沒有顧客了,於是這些守護執行緒到店裡轉悠一圈就走了。

3、守護執行緒應用場景?

你瞭解了守護執行緒的特點之後,就可以運用這個原理做一些意想不到的事,比如說在退出jvm的時候也想讓一些執行緒跟著退出,就可以把他設定為守護執行緒。

對這個基本的概念瞭解了之後我們再來看看執行緒的建構函式。

二、執行緒的建構函式

1、建構函式

執行緒Thread得建構函式一共有8個,

java多執行緒之Thread建構函式(原始碼分析)

在這裡我們接觸到了一個新的類ThreadGroup。它代表的含義就是一個執行緒所屬的執行緒組。在上面我們可以看到在例項化一個執行緒時候,既可以指定執行緒所屬的執行緒組,也可以宣告其runnable介面。下面我們分析一下這個執行緒組ThreadGroup。

他們倆的關係可以這樣表示:

java多執行緒之Thread建構函式(原始碼分析)

在上面說我們能夠指定執行緒所在的執行緒組,下面我們就程式碼演示一下。

java多執行緒之Thread建構函式(原始碼分析)

這就是其基本用法,但是如果我們沒有指定執行緒所屬的執行緒組輸出會是什麼結果呢?測試一下:

java多執行緒之Thread建構函式(原始碼分析)

上面的程式碼的意思是這樣的,執行緒A指定了我們建立的執行緒組,執行緒B預設的執行緒組,maingroup是主執行緒組。根據輸出結果我們會發現,如果一個執行緒沒有指定執行緒組,那麼他就和父親的執行緒組是一樣的。

2、例項化一個執行緒

上面給出了執行緒的八個構造方法,我們可以使用這八個構造方法去例項化一個執行緒,但是底層是如何做的呢?會不會是像普通類那樣例項化的呢?對此我們就需要深入執行緒的原始碼去看看:

java多執行緒之Thread建構函式(原始碼分析)

我們選用了一個最複雜的構造方法,因為其他構造方法都是其子集,我們可以看到,這裡其實呼叫的是init方法,也就是說真正實現初始化的是在init方法中進行的。我們不妨跟進去看看:

java多執行緒之Thread建構函式(原始碼分析)

這個init方法裡面還有一層,而且還多出了兩個引數。想要搞清楚我們就需要再跟進去看看:

java多執行緒之Thread建構函式(原始碼分析)

java多執行緒之Thread建構函式(原始碼分析)

終於找到了初始化執行緒的方法。我們劃分了五個部分:

(1)第一部分:確保執行緒名字不能為空,

在這裡就不得不提一句執行緒名了,java官方要求我們開發者如果沒有顯示的為執行緒指定一個名字,那麼執行緒將以“Thread-”為字首以數字為字尾,組成執行緒的名字。但是無論如何執行緒都需要有一個名字。

(2)第二部分:指定當前執行緒的執行緒組

裡面的程式碼很明白,也就是說如果g不為空,我們就是用這個g作為當前執行緒的執行緒組,否則的話就使用父類的執行緒組。當然了,中間還要檢查一下許可權問題等等。

(3)第三部分:其他屬性配置

在這裡配置了是否設定為守護執行緒、優先順序、類載入器等。

(4)第四部分:runnable介面配置

指定實現了runnable介面類。

(5)第五部分:設定棧大小

執行緒的棧大小 根據引數傳遞過程可以看出預設大小為零,即使用預設的執行緒棧大小

(6)第六部分:設定執行緒id

執行緒的ID是在nextThreadID方法中指定的。我們可以看看如何指定執行緒的ID的。

java多執行緒之Thread建構函式(原始碼分析)

可以看到,其實就是threadSeqNumber。

OK,以上就是如何初始化一個執行緒,相信我們都比較清楚了,其他的建構函式只是對這個init方法的引數進行了一些改變而已。但是原理都是一樣的。上篇文章中提到的一個問題還沒有解決,接著往下看。

三、為什麼呼叫start方法就能啟動一個執行緒

為了解決這個問題我們還必須要深入原始碼看一下(jdk1。8):

java多執行緒之Thread建構函式(原始碼分析)

這些程式碼的意思是什麼呢?首先會判斷執行緒狀態是否異常,然後把當前執行緒在啟動之前加入到執行緒組中,最後呼叫start0方法正式的啟動執行緒。現在關鍵來了,真正啟動執行緒的是這個start0方法,我們不妨再追進去看看:

java多執行緒之Thread建構函式(原始碼分析)

也就是說真正啟動時native方法啟動的,好像也沒有呼叫run方法,為什麼run方法裡面的內容就被執行了呢。官方文件是這麼解釋的:JNI方法start0內部呼叫了run方法。就是這麼一句話就解釋了上面的這個原因。

上面已經解決了兩個問題,第一個就是建構函式,第二個也理解了為什麼我們呼叫start方法就能啟動一個執行緒而不是run。我們分析原始碼就能知道,執行緒提供的api方法基本上全部是native的。

公眾號:java的架構師技術棧。專注於從小白到架構師系列的文章,回覆關鍵字可獲取各種教程資源和學習路線