天天看點

Java源碼分析九(Thread)

Java源碼分析九(Thread)

多線程的聲明周期概念:

Java源碼分析九(Thread)

在Java之中線程控制和業務分離,而且線程的實作有兩種方式:

Java源碼分析九(Thread)

守護(daemon)線程:服務其他的線程,比如垃圾回收線程,就是最典型的守護線程。它的最主要特征是:**它守護的線程挂了,它就會停止執行,即使它的實作裡面有finally塊。即daemon線程的finally塊不一定執行**

非守護(daemon)線程:使用者線程,它的特征是:除非主動終止,否則任務會一直執行,直到terminated觸發。

User Thread線程和Daemon Thread守護線程本質上來說去沒啥差別的,唯一的差別之處就在虛拟機的離開:如果User Thread全部撤離,那麼Daemon Thread也就沒啥線程好服務的了,是以虛拟機也就退出了。

**即user thread會在自己的任務執行完之後才會退出,即使父線程退出了。**

**thread.setDaemon(true)必須在thread.start()之前設定,否則會跑出一個IllegalThreadStateException異常**

Java源碼分析九(Thread)

name 線程的名稱。每個線程都可以有name,java允許線程擁有相同的名字,不指定thread name,就會給你generate 一個。

priority 等級。Java線程的等級是1~10,預設是5。需要注意的是:有些系統會忽略優先級,程式的正确性不能依賴線程的優先級。

threadLocals 這些留到ThreadLocal子產品再講。

線程的安全終止推薦使用如下方式:

Thread.interrupt()

不要使用過期的supend,resume,stop。因為這些方法挂起了也是會占有CPU資源的,使用下面的方法替換掉這些過期的方法:

通信使用notify,wait等

停止用打斷 Thread.interrupt()

最重要的邏輯是:interrupt0()

從下面的 registerNatives 函數的講解可以看出:

它對應的是:

調用棧如下:

然後Thread::interrupt(thr); 每個線程的調用都不一樣。

最後看出interrupt還是調用unpark來進行實作的。

1)線程預設的命名,修改線程名稱使用setName

2)父子線程。Thread.currentThread()可以擷取目前線程。

整個Thread最終初始化的調用函數都是下面這個方法:

ThreadGroup

ThreadGroup 是對Java對Thread的一種組織方式。每一個ThreadGroup都是Thread的集合。

ThreadGroup是一個以樹狀結構維護的一個資料結構。再我們虛拟機一開始啟動的時候就初始化一個Root ThreadGroup給我們,所有的線程都在這Root ThreadGroup下面。

ThreadGroup重要的屬性如下:

其中最重要的部分是:

上面的代碼片段也就是我們ThreadGroup的組織方式,如下圖:

Java源碼分析九(Thread)

線程組是一個樹,每個線程組初始化的時候都有一個parent。

一個線程可以通路自己的線程組information,但是不能通路自己線程組的parent thread group或者其它的thread groups。

Thread.currentThread() 是靜态方法,也就是說在哪裡都可以通過Thread.currentThread() 擷取目前線程。

sleep()

yield() : 提醒排程器我願意放棄目前資源,如果CPU不緊張,則會忽略這種提醒

getId(): 擷取線程ID

設定線程上下文類加載器:

Java源碼分析九(Thread)

join()

當我們調用某個線程的這個方法時,這個方法會挂起調用線程,直到被調用線程結束執行,調用線程才會繼續執行。

Java源碼分析九(Thread)

我們看看join的源碼:

從上面可以看出最終執行的方法都是public final synchronized void join(final long millis)。

首先整個join方法用來 synchronized 來修飾,然後使用了Object.wait将線程挂起,然後等待一個notify或者notifyAll。

然後想一下join的注解介紹:阻塞到這個線程退出。我猜測線上程退出的時候,底層會調用一個notifyAll來喚醒所有的join的線程。

然後進入源碼之中一看:

然後我們進入ensure_join之中看看。

果然和我猜測的一樣。

結論是:線程退出的時候,清除狀态,這個時候調用isAlive() 就會傳回false。然後調用lock.notify_all(thread); 這個時候就會喚醒線程上所有的wait,然後Thread.join裡面的while循環就會繼續。但是isAlive傳回false了,這樣就會退出循環整個查詢邏輯結束。

看下面的案例

控制台輸出

關閉線程

Java源碼分析九(Thread)

複制線程

Java源碼分析九(Thread)

registerNatives

static函數是在clinit之中執行的。

這是一個jni調用方法,最後注冊的方法如下:

從上面可以看出interrupt0 對應的函數是:JVM_Interrupt。

什麼是上下文切換呢?

我們的CPU因為運作速度比記憶體等快了好幾個數量級,應對這種情況為了充分使用CPU,我們将CPU的處理時間切片,哪個時間機關歸哪個線程執行任務,都是靠排隊。每個線程執行任務是需要執行任務的環境的,就像我們工作的時候的工作環境準備。那麼準備這些環境也是需要時間的,這樣就會浪費了很多CPU執行的時間。是以多線程雖然能提升我們執行任務的效率,但是如果設定的線程數不合理也是有很大的性能消耗。

怎麼設定線程數比較合理呢?看下圖:

Java源碼分析九(Thread)

死鎖示範的代碼

我們使用jstack 工具 可以看到 死鎖裡面具體内容

0x000000070f537a48 這些表示的是鎖頭的位址。

Java源碼分析九(Thread)

枚舉類 State解析

Java源碼分析九(Thread)

初始态(NEW):

建立一個Thread對象,但還未調用start()啟動線程時,線程處于初始态

運作态(RUNNABLE):

運作态在Java中包括就緒态和運作态

1.就緒态:

該狀态下的線程已經獲得執行所需的所有資源,隻要CPU配置設定執行權就能運作

所有就緒态的線程存放在就緒隊列中

2.運作态:

獲得CPU執行權,正在執行的線程

由于一個CPU同一時刻隻能執行一條線程,是以每個CPU每個時刻隻有一個運作态的線程

阻塞态(BLOCKED)

當一條正在執行的線程請求某一資源失敗時,就會進入阻塞态

而在Java中,阻塞态專指請求鎖失敗時進入的狀态

由一個阻塞隊列存放所有阻塞态的線程。處于阻塞态的線程會不斷請求資源,一旦請求成功,就會進入就緒隊列,等待執行

等待态(WAITING)

目前線程中調用wait、join、park函數時,目前線程就會進入等待态

也有一個等待隊列存放所有等待态的線程

線程處于等待态表示它需要等待其他線程的訓示才能繼續運作

進入等待态的線程會釋放CPU執行權,并釋放資源(如:鎖)

逾時等待态(TIMED_WAITING)

當運作中的線程調用sleep(time)、wait、join、parkNanos、parkUntil時,就會進入該狀态

它和等待态一樣,并不是因為請求不到資源,而是主動進入,并且進入後被其他線程喚醒或逾時自動喚醒

進入該狀态後釋放CPU執行權和占有的資源,其中wait()方法會釋放CPU執行權和占有的鎖,sleep(long)方法僅釋放CPU使用權,鎖仍然占用

與等待态的差別:到了逾時時間後自動進入阻塞隊列,開始競争鎖

終止态(TERMINATED)

線程執行結束後的狀态

其中有幾點需要注意的:

yield方法僅釋放CPU執行權,鎖仍然占用,線程會被放入就緒隊列,會在短時間内再次執行

wait和notify必須配套使用,即必須使用同一把鎖調用

wait和notify必須放在一個同步塊中調用,wait和notify的對象必須是他們所處同步塊的鎖對象

Caches/WeakClassKey

我們可以看到WeakClassKey這個内部類繼承了WeakReference,而WeakClassKey被Caches所使用,從名字我們也能明白其部分含義,本地緩存,WeakClassKey是弱引用相關類,至于弱引用的使用大家可以自行Google,這裡不多說,如果你看過《深入了解Java虛拟機》,應該多少有點了解

subclassAudits提供了一個哈希表緩存,該緩存的鍵類型為java.lang.Thread.WeakClassKey,注意看它的值類型是一個java.lang.Boolean類型的,從其代碼注釋可以知道這個哈希表緩存中儲存的是所有子類的代碼執行安全性檢測結果

  

subclassAuditsQueue定義了一個Queue隊列,儲存已經稽核過的子類弱引用

繼承的類和實作的接口

出現最頻繁的ThreadGroup類分析

線程組是一個父子結構,一個線程組可以屬于其他線程組,也可以擁有自己的子線程組,如果你一直向上追溯的話,會發現所有的線程組都在一個根線程組裡面— System 線程組

線程組的出現可不是為耍酷用的,它是為了更友善的管理線程而存在的.比如設定線程最大優先級,銷毀線程等等,添加線程,移除線程組等

還繼承了Thread.UncaughtExceptionHandler,用來處理預設的線程異常捕獲處理,如果線程沒有設定處理器,預設走的是group的處理方法

其中大部分方法都是遞歸操作方法

出現的屬性和靜态代碼塊解析

構造器