Thread的老管家ThreadGroup(二)
大家好我是kconn,我是一個不愛看原始碼,不喜歡分析原始碼,更不喜歡寫文章的程式員。自從面試被人虐後,我知道我的不足,所以我打算學習Android原始碼。
最近有感而發想給大家分享下我的雞湯:“人總是有夢想的,如果不為此拼命努力,怎麼會知道夢想是那麼遙不可及。”

我麥鎮樓!
本篇文章是上一篇文章的補充,上篇文章寫的有點過於隨意,很多細節比較鬆散,讓人看的雲裡霧裡,所以特地抽時間寫了這篇文章,讓更多的人瞭解老管家ThreadGroup。
因為是文章的補充,所以原始碼比較少,沒有看過上篇文章的同學可以先去看看了解下。好了,廢話不多說,直接進入主題。
ThreadGroup俗稱執行緒組,位於java.lang包下的一個類,是用於統一的執行緒管理,聽名字就知道,相當於是執行緒的集合。除了系統執行緒組外,其他的執行緒組都一定會有父執行緒組,他們之間是樹的關係。
口說無憑,我先上程式碼給你們肛。
ThreadGroup tomThreadGroup = new ThreadGroup("Tom");
我建立了一個名字為"湯姆"的執行緒組。那麼看下他和他的父執行緒組。
Log.e("logTag", "tomThreadGroup名:" + leeThreadGroup.getName()); // 輸出Tom Log.e("logTag", "tom的父執行緒組大名:" + leeThreadGroup.getParent().getName()); // 輸出main
接著看下父執行緒組的父執行緒組,也就是爺爺執行緒組。
Log.e("logTag", "Tom的爺爺執行緒組大名:" + leeThreadGroup.getParent().getParent().getName()); // 輸出system
最後看這個“system”的執行緒組的父執行緒組,輸出的時候崩掉了,錯誤日誌顯示空指標。
看過我上篇文章的同學就知道,這個“system”執行緒組是最強王者,上面沒人了,所以父執行緒組為空。
資料裡面說執行緒組之間名字可重複,那麼我們試試看。
ThreadGroup tomThreadGroup = new ThreadGroup("Tom"); ThreadGroup tomThreadGroup2 = new ThreadGroup("Tom"); Log.e("logTag", "tomThreadGroup名:" + leeThreadGroup.getName()); // 輸出Tom Log.e("logTag", "tomThreadGroup2名:" + leeThreadGroup2.getName()); // 輸出Tom
寫到這裡,我很好奇tomThreadGroup和tomThreadGroup2是mainThreadGroup的子執行緒組,那麼mainThreadGroup的子執行緒組中是否只有他們呢?為了防止混淆,我把tomThreadGroup2的名字改為Jerry。
ThreadGroup tomThreadGroup = new ThreadGroup("Tom"); ThreadGroup jerryThreadGroup = new ThreadGroup("Jerry"); ThreadGroup mainThreadGroup = tomThreadGroup.getParent(); // 定義一個執行緒組陣列,大小為mainThreadGroup活動的執行緒組數量(包括子執行緒組合孫子執行緒組) ThreadGroup[] threadGroups = new ThreadGroup[mainThreadGroup.activeGroupCount()]; // 複製mainThreadGroup的所有活動子執行緒組和孫子執行緒組 mainThreadGroup.enumerate(threadGroups); // 輸出執行緒組資訊 for (ThreadGroup threadGroup : threadGroups) { Log.e("logTag", threadGroup.getName()); // 兩個結果Jerry、Tom }
既然知道了main那麼也可以知道system執行緒組裡面的子執行緒組。
ThreadGroup systemThreadGroup = mainThreadGroup.getParent(); ThreadGroup[] threadGroups = new ThreadGroup[systemThreadGroup.activeGroupCount()]; systemThreadGroup.enumerate(threadGroups); for (ThreadGroup threadGroup : threadGroups) { Log.e("logTag", threadGroup.getName()); // 三個結果main、Jerry、Tom }
ok,執行緒組之間的關係瞭解完了,現在接著看執行緒組和執行緒的關係。
Thread tuffyThread = new Thread(jerryThreadGroup, "Tuffy"); Log.e("logTag", "Tuffy所屬的執行緒組:" + tuffyThread.getThreadGroup().getName());// 輸出結果是Jerry
然後試下輸出Jerry的執行緒
Thread[] threads = new Thread[jerryThreadGroup.activeCount()]; jerryThreadGroup.enumerate(threads); for (Thread thread : threads) { Log.e("logTag", thread.getName()); // 輸出結果為空白 }
我矇蔽了一會,然後醒悟了,我們獲取的是Jerry的活動執行緒,因為Tuffy還沒啟動,所以他名下沒有活動的執行緒。這情況就像是你去買房,人家還沒過戶給你,你就先把錢給了人家。

後面start執行緒之後輸出是Tuffy。
這裡插個嘴:看過原始碼的同學也知道,ThreadGroup裡面有個list方法,是用來列印執行緒組的資訊,感興趣的同學可以自己試下。
同理我們可以得出main和system執行緒組的活動執行緒。
mian執行緒組的執行緒:Binder_2、Binder_1、main(主執行緒)、Tuffy system執行緒組的執行緒:HeapTaskDaemon、FinalizerWatchdogDaemon、FinalizerDaemon、 ReferenceQueueDaemon、JDWP、Signal Catcher、Binder_2、Binder_1、main、Tuffy
前面說了,執行緒組之間是樹的關係,那麼執行緒組和執行緒之間就是集合關係,用以下圖來表示。

這裡插個嘴:資料上面說,如果執行緒歸入某執行緒組,那麼他就不能改投另一個執行緒組。就像貓和老鼠,Tom貓是一個執行緒組,Jerry鼠是另一個執行緒組。現在有一個執行緒Tuffy,它加入了Jerry組,那麼它就無法改投Tom組。另外再說句,如果執行緒沒有表示加入哪個執行緒組,那麼它預設是屬於main執行緒組。

好,接著看ThreadGroup的interrupt方法,資料上面顯示這個是中斷該執行緒組中所有執行緒的方法。實際上它並沒有中斷。
Thread tuffyThread = new Thread(jerryThreadGroup, new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (i == 3) { jerryThreadGroup.interrupt(); } Log.e("logTag", "爆炸虛出:" + i); // 依然我行我素輸出到9 } } }, "Tuffy");
這裡就不太明白,說好的中斷呢?在原始碼中它最後是遍歷了它名下的執行緒,然後呼叫Thread的interrupt方法(最後實現是native方法)。
百思不得其姐的時候,突然發現這個Thread.sleep拋的異常是InterruptedException,尋思著兩者之間會不會有什麼騷操作。
Thread tuffyThread = new Thread(jerryThreadGroup, new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); Log.e("logTag", "異常虛出:" + e.toString()); } if (i == 3) { jerryThreadGroup.interrupt(); } Log.e("logTag", "爆炸虛出:" + i); // 依然我行我素輸出到9 } } }, "Tuffy");

結果可以看到當虛出到3的時候拋了異常。這裡我說下細節,虛出3、java.lang.InterruptedException、4這三個動作是一起完成的,也就是說明此刻的Thread.sleep(1000)是沒效的,也就是說interrupt方法中斷的是Thread.sleep方法。值得一提的是它只中斷一次就是拋異常的那次,後面的5-9依然sleep了1000毫秒。至於為什麼,這就留到後面寫到Thread文章再詳細解釋。

好了,最後在結束的之前介紹下Thread和ThreadGroup異常的處理。ThreadGroup實現了Thread.UncaughtExceptionHandler介面,用意在於當執行緒組中的執行緒發生異常,那麼就會呼叫Thread.UncaughtExceptionHandler介面的uncaughtException方法。異常的處理我們可以呼叫執行緒的setUncaughtExceptionHandler方法進行處理,也可以重寫執行緒組的uncaughtException方法處理。
ThreadGroup jerryThreadGroup = new ThreadGroup("Jerry") { @Override public void uncaughtException(Thread t, Throwable e) { Log.e("logTag", t.getName() + ":" + e.toString()); // Tuffy:java.lang.RuntimeException: 對方不想理你並向你丟擲個異常 } }; Thread tuffyThread = new Thread(jerryThreadGroup, new Runnable() { @Override public void run() { throw new RuntimeException("對方不想理你並向你丟擲個異常"); } },"Tuffy");
肛原始碼不容易,我也不是大牛,文章也寫的不是很好。大家看著有什麼想法都可以說,我不一定會看,看了也不一定會回,因為我胃疼!
