這個系列的出現可以說就是因爲ThreadGroup,相對於Thread、ThreadLocal可能部分Java程序員,對於ThreadGroup比較陌生,或者說知道是什麼,但是不怎麼用,接下來會從ThreadGroup的概念以及具體應用進行介紹,希望大家從中可以學到什麼。
2. ThreadGroup
不要問爲什麼從2開始,因爲上一章是1。
2.1 認識ThreadGroup
引用官方SDK的介紹如下:
線程組表示一個線程的集合。此外,線程組也可以包含其他線程組。線程組構成一棵樹,在樹中,除了初始線程組外,每個線程組都有一個父線程組。
允許線程訪問有關自己的線程組的信息,但是不允許它訪問有關其線程組的父線程組或其他任何線程組的信息
怎麼形象地理解這個線程組?在上一章不知道有沒有留意一個線程的創建其中一個步驟就是將線程添加到某個線程組,那麼主線程可能也是有屬於某個線程組的,因此我們還是從主線程入手。
循環輸出一下父級線程組:
public static void main(String[] args) throws Exception {
//獲取當前線程
Thread currentThread = Thread.currentThread();
//獲取當前線程所在的線程組
ThreadGroup currentThreadGroup = currentThread.getThreadGroup();
System.out.println("當前線程名稱:"+currentThread.getName() );
while (currentThreadGroup!=null){
System.out.println("父線程組名稱:"+currentThreadGroup.getName() );
currentThreadGroup = currentThreadGroup.getParent();
}
}
執行後輸出結果:
根據這個結果我們畫個圖表示一下關係,
大概是這樣子
從上圖可以看到線程與線程組的所屬關係以及線程組與線程組之間的所屬關係,而最爸爸的線程組就是system線程組,爲什麼這樣說呢,我們進行實驗如下:
//創建一個線程組threadgroup 1
ThreadGroup tg1 = new ThreadGroup ("threadgroup 1");
//創建threadgroup 1下的一個線程組
Thread t1 = new Thread (tg1, "thread 1");
currentThreadGroup = t1.getThreadGroup();
while (currentThreadGroup!=null){
System.out.println("父線程組名稱:"+currentThreadGroup.getName() );
currentThreadGroup = currentThreadGroup.getParent();
}
控制檯輸出結果如下:
我們再重新畫一下圖:
對於爲什麼出現這個結果,我們直接看ThreadGroup對象創建過程,他實際上就是在默認情況下拿當前線程所在線程組創建,剛剛我們在main線程中創建這個線程組threadgroup 1,因此它是屬於main線程組的子線程組。
集合第一章對線程得學習,因此我們可以總結得:
1.JVM創建的system線程組是用來處理JVM的系統任務的線程組,例如對象的銷燬等。
2.system線程組的直接子線程組是main線程組,這個線程組至少包含一個main線程,用於執行main方法。
3.main線程組的子線程組就是應用程序創建的線程組。
2.2 ThreadGroup的作用
在Java的程序定義裏面,把每個線程都歸屬一個線程組,又在最大線程組system下不斷創建子線程組,這樣的樹形結構有什麼意義?
接下來我們從ThreadGroup本身的屬性進行入手
可以看到它包含的屬性主要是父線程組、名稱、最大優先級、是否被銷燬、是否啓動、 是否供VM使用、未開始的線程數量、線程數量、線程數組、線程組數量、線程組數組。
查看它的構造方法,如何初始化這些屬性
除了自定義的name和父線程組,線程組的屬性都是follow它的父線程組。
ThreadGroup的方法有:
具體介紹可以去sdk進行查詢,這裏不作詳細介紹。
在上面列出的方法裏面,經過了解,線程組具備能力就是控制整個線程組下的所有線程的狀態,而由於ThreadGroup本身並非線程安全,線程組ThreadGroup對象中的stop,resume,suspend會導致安全問題,主要是死鎖問題,已經被官方廢棄,剩餘與線程生命週期相關的就是
destroy() 銷燬此線程組。只有在當前線程組線程數量=0的時候有效
interrupt() 中斷此線程組中的所有線程。
2.2.1 添加線程
我們可以從Thread創建的源碼裏面,直接跳到ThreadGroup.add(),線程的創建在進入jvm創建系統子線程前,先將線程放進線程組
void add(Thread t) {
synchronized (this) {
if (destroyed) {
throw new IllegalThreadStateException();
}
if (threads == null) {
threads = new Thread[4];
} else if (nthreads == threads.length) {
threads = Arrays.copyOf(threads, nthreads * 2);
}
threads[nthreads] = t;
// This is done last so it doesn't matter in case the
// thread is killed
nthreads++;
// The thread is now a fully fledged member of the group, even
// though it may, or may not, have been started yet. It will prevent
// the group from being destroyed so the unstarted Threads count is
// decremented.
nUnstartedThreads--;
}
}
這段add方法的實現,簡單來說就是,首先判斷當前線程組是否被銷燬,假如否,拋異常,假如是就將創建的新線程放進線程組對象裏面,線程組初始大小爲4,線程組的增長是按2倍增長,進行線程組線程數量計算。
2.2.2 中斷線程
ThreadGroup具有中斷該組下的線程的作用,具體實現如下:
// 創建2個MyThread屬於tg1
MyThread mt = new MyThread(tg1,"A");
mt.start();
mt = new MyThread(tg1,"B");
mt.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中斷線程組
System.out.println("中斷tg1");
tg1.interrupt();
//啓動以後進入等待,直到被interrupt的線程
static class MyThread extends Thread {
public MyThread(ThreadGroup tg,String name){
super(tg,name);
}
public void run() {
synchronized ("A") {
System.out.println(getName() + " 等待中");
try {
"A".wait();
} catch (InterruptedException e) {
System.out.println(getName() + " 中斷");
}
System.out.println(getName() + " 停止");
}
}
}
控制檯輸出結果:
ThreadGroup的源碼實現如下:
public final void interrupt() {
int ngroupsSnapshot;
ThreadGroup[] groupsSnapshot;
synchronized (this) {
checkAccess();
for (int i = 0 ; i < nthreads ; i++) {
threads[i].interrupt();
}
ngroupsSnapshot = ngroups;
if (groups != null) {
groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
} else {
groupsSnapshot = null;
}
}
for (int i = 0 ; i < ngroupsSnapshot ; i++) {
groupsSnapshot[i].interrupt();
}
}
核心中斷線程的是, threads[i].interrupt();
這一句,實際最後是通過調用native interrput0(),調用jvm中斷系統線程。然後他把線程組對象複製到新的的線程組快照對象裏面。
2.2.3 作用總結
從這裏來看,對於線程來說,線程組並沒有比較能用上的作用,一般非必要的情況下,線程的生命週期管理還是交給線程自己主導,並不使用線程組去控制。
2.3 線程組與線程池的區別
線程組是線程的爸爸,每一個線程一定是屬於某個線程組的,所有線程組都是來自於system線程組。
線程池是沒有血緣關係的,線程池是爲了最大化利用計算機能力而出現,由於我們cpu調度能力有限,不能無限創建線程,無限並行任務,因此出現線程池, 線程池主要用來解決線程生命週期開銷問題和資源不足問題。通過對多個任務重複使用線程,線程創建的開銷就被分攤到了多個任務上了,而且由於在請求到達時線程已經存在,所以消除了線程創建所帶來的延遲。這樣,就可以立即爲請求服務,使用應用程序響應更快。另外,通過適當的調整線程中的線程數目可以防止出現資源不足的情況。