1. 程式人生 > >06-執行緒的初始化,中斷以及其原始碼講解

06-執行緒的初始化,中斷以及其原始碼講解

從這裡開始,我們來了解執行緒的建立,

繼承Thread類和實現Runnable介面,這兩種方式可以說是中規中矩的,也是我們用的比較多的建立執行緒的方式,後面也是基於繼承Thread類和實現Runnable介面的另外一種,也就是,使用匿名內部類的方式。然後我們會發現無論是使用繼承Thread類還是使用實現Runnable介面的方式還是使用匿名內部類的方式,這三種方式在run()方法中都沒有辦法去丟擲更多的異常,而且沒有返回值。那麼,我們就希望有一個能夠丟擲異常並且帶返回值的這麼一種執行緒,那麼,這就是第四種建立執行緒的方式。第五個是通過定時器的方式,其實定時器也相當於是其中的一個執行緒,那麼,它也是屬於建立執行緒的一種方式,我們會去了解一些關於定時器的內容。第六種方式就是通過執行緒池的方式。第七種就是通過Lambda表示式實現,在JDK8中,新增了一個非常強大的一個Lambda表示式,那麼,就可以通過這個表示式可以實現多執行緒這種對我們集合的操作、對我們的資料流的操作等等。也就是說,這七中都是實現多執行緒的方式,那麼,我們逐一的來看。每一種方式都會通過程式碼來進行演示。當然了,我們不僅僅要看它的使用,我們要深入到它的原理上,從原始碼上能夠來理解執行緒的建立。

第一種方式就是繼承Thread類的方式。我們說萬物皆物件,那麼,執行緒也是一個物件。抽取物件的公共特徵就可以形成一個類,那麼,我們就繼承Thread類。

那麼,當前的這個Demo1就是一個執行緒類的子類,我們只要new出來Demo1的例項,那麼,呼叫例項的執行緒的啟動方法,那麼,就可以啟動一個執行緒了,那麼,執行緒執行什麼活呢?就是線上程裡面定義過的一個run()方法,我們來看一下

這個run()方法的程式碼體非常的簡單,這個方法是需要我們去重寫的,所以,我們需要重寫run()方法來覆蓋掉它,對我們繼承Thread類來講,那麼,原始碼中的這個run()方法中的程式碼體是沒有任何意義的,一會我們會說,原始碼中的這個run()方法中的程式碼的意思。

我們重寫run()方法就可以了。

我們現在是一直在列印,那麼,執行緒它有沒有名字呢?它有沒有其他的一些屬性呢?我們說,物件都是有屬性和行為的,那麼,Thread都有哪些屬性和行為呢?我們可以通過原始碼來簡單的看一下,

 

發現它的名字就是Thread-0和Thread-1,那麼,我們並沒有給它指定名字,它是如何叫做Thread-0和Thread-1的呢?我們來看一下原始碼。我們new Demo1()其實就是new的Thread類的構造方法,我們來看一下Thread類就有哪些構造方法,

 

我們再來執行

我們發現名字已經變成了我們指定的名字了。

我們發現Thread類的構造方法有很多,

我們不管使用哪種構造方法,在建立Thread類的時候,構造方法裡面都有一個進行初始化的過程

我們來看一下init()

這個方法裡面有很多的引數,第一個引數是執行緒組,這個執行緒組是幹什麼的呢?其實就是用於對執行緒進行分組的,非常好理解,我們這裡也不去詳細的去說它,就是說,你可以建立一個執行緒組,然後把執行緒加到這個組裡面去。你可以把執行緒加到執行緒組中來,加到執行緒組中之後,我們來看一下這個執行緒組都有哪些方法呢?

執行緒組它是一種樹狀結構,可以這麼理解,這是一個頂層的執行緒組

那麼,這個執行緒組裡面,下面可以接著放執行緒組,也可以方執行緒,

總之,它就是一層一層的往下走,

這就是所謂的執行緒組,通過上圖我們可以看到,按照面向物件的思維方式,它肯定會有它的名字,還有獲取它的上一級,下一級等功能。

這就是執行緒組,即對執行緒進行分組。

第二個引數是Runnable,也就是可以指定一個執行緒任務。第三個引數就是執行緒的名字。第四個引數是stackSize,那麼,這個引數又是什麼呢?我們看一下init()方法中呼叫的init()方法,

這個init()方法中也有一個stackSize引數,就是棧的大小,那麼,這個引數到底是個何方神聖呢?

就是,開發Thread類的這個人,他也不知道stackSize到底是幹什麼用的,它是為虛擬機器做一些事情,做一些什麼事情呢?做一些虛擬機器想幹的事情,就是說,stackSize指定了之後,虛擬機器可以通過stackSize做一些虛擬機器想做的事情。當然了,很多的虛擬機器是忽略stackSize的。所以,我們也不需要關stackSize這個引數了。

這樣,關於初始化我們就看完了。

這樣

雖然這裡我們只是指定了執行緒的名字,其實我們知道,在初始化的時候,我們可以指定很多的引數來進行初始化我們的Thread。

這裡還有一個問題,就是關於Daemon的問題,

Daemon是用來幹什麼的呢?我們稱它叫做守護執行緒,或者說它是一個支援型執行緒,它是幹什麼的呢?它主要是作用在程式中後臺呼叫,就是說,做一些支援性工作,就比如說垃圾回收執行緒吧,它就扔在後臺,在後臺自己默默的去做一些事,當程式執行完畢的時候,它即使執行不完畢,那麼,它依然會跟著退出。

我們現在把這兩個執行緒的Daemon都設定為True,也就是說,這兩個執行緒都是支援型執行緒,那麼,這兩個執行緒既然都屬於支援型執行緒了,那麼,即使這兩個執行緒的執行緒任務沒有執行完畢,當主執行緒執行完畢之後,那麼,這兩個執行緒也依然會被退出,

這裡加while(true)是為了讓它一直執行。

執行程式,我們發現,還沒執行程式就終止了。也就是說,執行緒還沒有啟動就已經退出去了。我們為了能夠讓它執行一點,我們讓當前的這個執行緒休息一會,休息兩秒鐘,

我們再來執行,

我們發現,兩秒鐘過了之後,雖然執行緒d1和執行緒d2沒有執行完畢,但是執行緒d1和執行緒d2依然隨著主執行緒的退出而退出。

這就是所謂的Daemon。

關於執行緒的建立,我們就說完了。

接著我們看執行緒的中斷以及銷燬。

比如說,我們想讓一個執行緒執行一段時間之後,我們不想讓它再執行了,我們讓它中斷掉,怎麼辦呢?

首先,我們看到這裡有三個方法,interrupt()、interrupted()、isInterrupted()。

interrupt()就是中斷這個執行緒,

通過interrupted()可以看當前這個執行緒是否是中斷了的,

isInterrupted()就是判斷當前的這個執行緒是否中斷。

 

其實它就是用於恢復中斷的標誌的。

 

我們發現執行出錯了,但是,後面依然在執行。那麼,這是怎麼回事呢?

其實我們發現interrupt()它是JDK6.0才出現的,那麼,在之前是用什麼呢?

我們發現這裡面有stop()讓這個執行緒終止。那麼,我們呼叫stop()看一下。

stop()方法過期了。

但是我們發現,這個時候就只有thread2在執行了。

為什麼不推薦我們去使用stop()了呢?其實就是因為,使用stop()去進行停止的時候,那麼,這個執行緒所獲取的鎖、獲取的其他資源都沒有被釋放掉,因此,這個stop()只是讓這個執行緒無限期的等待下去,所以這種方式是非常不好的,因此就被停用掉了。那麼,推薦我們所使用的方法就是interrupt(),但是剛才我們也看到了,通過interrupt並沒有讓我們的執行緒終止掉,這是怎麼回事呢?這就需要我們自己去處理,我們寫程式碼的時候就不要這麼去寫了

 

那麼,我們應該怎麼去寫呢?在這裡我們就不再寫while(true)了,我們想讓它正常的去中斷,如何讓它正常中斷呢?其實就是為了讓它儘可能的把執行緒執行完畢,那麼,線上程執行完畢的這個階段,那麼,它肯定是可以把所有的資源都釋放掉,把該做的事情都做好,那麼,我們就可以通過

我們通過這個interrupted(),如果不中斷的話,那麼,我們就去執行,當我們呼叫了interrupt()之後,

它就會把中斷標誌修改為“是中斷”,即interrupted()方法返回值為true,所以,此時

這裡就不再執行了,那麼while這段程式碼就直接退出了,也就是說執行緒也就終止了。

這樣也就是說我們的執行緒同樣的也就正常的結束了。

這種中斷方式是我們比較推薦的一種方式,

我們這裡已經瞭解了關於中斷,那麼,我們以後在進行中斷的時候,我們就不要再使用像stop()等,我們就使用interrupt()就可以了。

這就是安全的終止執行緒。那麼,執行緒的關於啟動和中斷我們就說完了。