Java并發(fā)編程:什么是線(xiàn)程組?它有什么作用?
線(xiàn)程組簡(jiǎn)介
在 Java 中,ThreadGroup用于表示一個(gè)線(xiàn)程組。我們可以使用ThreadGroup來(lái)批量控制線(xiàn)程,更方便地管理線(xiàn)程。
ThreadGroup和Thread之間的關(guān)系非常簡(jiǎn)單,就像它們的字面意思一樣:每個(gè)Thread必然存在于一個(gè)ThreadGroup中,一個(gè)Thread不能獨(dú)立于ThreadGroup存在。
執(zhí)行main()方法的線(xiàn)程名字是main,如果你在執(zhí)行new Thread()時(shí)沒(méi)有顯式指定一個(gè)ThreadGroup,那么默認(rèn)會(huì)將父線(xiàn)程(當(dāng)前正在執(zhí)行new Thread()的線(xiàn)程)的ThreadGroup設(shè)置為子線(xiàn)程的ThreadGroup。
示例代碼:
public class Demo {
public static void main(String[] args) {
Thread subThread = new Thread(() -> {
System.out.println("子線(xiàn)程所在的線(xiàn)程組名稱(chēng)是:" +
Thread.currentThread().getThreadGroup().getName());
System.out.println("當(dāng)前線(xiàn)程(子線(xiàn)程)的名稱(chēng)是:" +
Thread.currentThread().getName());
});
subThread.start();
System.out.println("執(zhí)行 main() 方法的線(xiàn)程所在的線(xiàn)程組名稱(chēng)是:"
+ Thread.currentThread().getThreadGroup().getName());
System.out.println("當(dāng)前線(xiàn)程的名稱(chēng)是:"
+ Thread.currentThread().getName());
}
}
輸出:
執(zhí)行main()方法的線(xiàn)程所在的線(xiàn)程組名稱(chēng)是: main
當(dāng)前線(xiàn)程的名稱(chēng)是: main
子線(xiàn)程所在的線(xiàn)程組名稱(chēng)是: main
當(dāng)前線(xiàn)程(子線(xiàn)程)的名稱(chēng)是: Thread-0
線(xiàn)程組是父子結(jié)構(gòu)的,一個(gè)線(xiàn)程組可以包含其他線(xiàn)程組,也可以有其他子線(xiàn)程組。從結(jié)構(gòu)上看,線(xiàn)程組是一個(gè)樹(shù)形結(jié)構(gòu),每個(gè)線(xiàn)程屬于一個(gè)線(xiàn)程組,而該線(xiàn)程組又有一個(gè)父線(xiàn)程組,依此類(lèi)推,最終可以追溯到根線(xiàn)程組,即System線(xiàn)程組。
結(jié)構(gòu)如下所示:
圖片
- JVM 創(chuàng)建的system線(xiàn)程組是一組用于處理 JVM 系統(tǒng)任務(wù)的線(xiàn)程,比如對(duì)象銷(xiāo)毀、垃圾回收(GC)等。
- system線(xiàn)程組的直接子線(xiàn)程組是main線(xiàn)程組,它至少包含一個(gè)執(zhí)行main方法的main線(xiàn)程。
- main線(xiàn)程組的子線(xiàn)程組是由應(yīng)用程序創(chuàng)建的線(xiàn)程組。
你可以在main方法中看到 JVM 創(chuàng)建的system線(xiàn)程組和main線(xiàn)程組:
public static void main(String[] args) {
ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();
ThreadGroup systemThreadGroup = mainThreadGroup.getParent();
System.out.println("當(dāng)前線(xiàn)程所在的線(xiàn)程組的父線(xiàn)程組名稱(chēng) = " + systemThreadGroup.getName());
System.out.println("當(dāng)前線(xiàn)程所在的線(xiàn)程組名稱(chēng) = " + mainThreadGroup.getName());
}
輸出:
當(dāng)前線(xiàn)程所在的線(xiàn)程組的父線(xiàn)程組名稱(chēng) = system
當(dāng)前線(xiàn)程所在的線(xiàn)程組名稱(chēng) = main
一個(gè)線(xiàn)程可以訪(fǎng)問(wèn)它所屬線(xiàn)程組的信息,但不能訪(fǎng)問(wèn)它所屬線(xiàn)程組的父線(xiàn)程組或其他線(xiàn)程組的信息。
線(xiàn)程組的結(jié)構(gòu)
首先,我們來(lái)看一下ThreadGroup源碼中的成員變量。
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
private final ThreadGroup parent; // 父線(xiàn)程組
String name;
int maxPriority;
boolean destroyed;
boolean daemon;
boolean vmAllowSuspension;
int nUnstartedThreads = 0;
int nthreads; // 子線(xiàn)程數(shù)量
Thread threads[]; // 子線(xiàn)程數(shù)組
int ngroups; // 子線(xiàn)程組數(shù)量
ThreadGroup groups[]; // 子線(xiàn)程組數(shù)組
}
接下來(lái),我們看一下java.lang.ThreadGroup提供的兩個(gè)構(gòu)造函數(shù),我添加了一些注釋以便理解。
// 當(dāng) JVM 啟動(dòng)時(shí),調(diào)用此構(gòu)造函數(shù)創(chuàng)建根線(xiàn)程組。
private ThreadGroup() {
this.name = "system";
this.maxPriority = Thread.MAX_PRIORITY;
this.parent = null;
}
// 默認(rèn)情況下,傳入當(dāng)前 ThreadGroup 作為父 ThreadGroup。新線(xiàn)程組的父線(xiàn)程組是當(dāng)前運(yùn)行線(xiàn)程的線(xiàn)程組。
public ThreadGroup(String name) {
this(Thread.currentThread().getThreadGroup(), name);
}
// 傳入名稱(chēng)創(chuàng)建線(xiàn)程組,父線(xiàn)程組由客戶(hù)端指定。
public ThreadGroup(ThreadGroup parent, String name) {
this(checkParentAccess(parent), parent, name);
}
// 主要的私有構(gòu)造函數(shù),大多數(shù)參數(shù)從父線(xiàn)程組繼承
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
this.name = name;
this.maxPriority = parent.maxPriority;
this.daemon = parent.daemon;
this.vmAllowSuspension = parent.vmAllowSuspension;
this.parent = parent;
parent.add(this);
}
checkParentAccess()方法用于判斷當(dāng)前運(yùn)行的線(xiàn)程是否有權(quán)限修改線(xiàn)程組。
以下代碼演示了這兩個(gè)構(gòu)造函數(shù)的用法:
public class ConstructDemo {
public static void main(String[] args) {
ThreadGroup subThreadGroup1 = new ThreadGroup("subThreadGroup1");
ThreadGroup subThreadGroup2 = new ThreadGroup(subThreadGroup1, "subThreadGroup2");
System.out.println("subThreadGroup1 的父線(xiàn)程組名稱(chēng)是:" +
subThreadGroup1.getParent().getName());
System.out.println("subThreadGroup2 的父線(xiàn)程組名稱(chēng)是:" +
subThreadGroup2.getParent().getName());
}
}
輸出:
subThreadGroup1的父線(xiàn)程組名稱(chēng)是: main
subThreadGroup2的父線(xiàn)程組名稱(chēng)是: subThreadGroup1
ThreadGroup 包含的方法
ThreadGroup提供了許多有用的方法,下面簡(jiǎn)要介紹其中一些。
方法 | 描述 |
void checkAccess() | 判斷當(dāng)前運(yùn)行的線(xiàn)程是否有權(quán)限修改線(xiàn)程組。 |
int activeCount() | 返回線(xiàn)程組及其子組中活動(dòng)線(xiàn)程的估計(jì)數(shù)量。 |
int activeGroupCount() | 返回線(xiàn)程組及其子組中活動(dòng)線(xiàn)程組的估計(jì)數(shù)量。 |
void destroy() | 銷(xiāo)毀線(xiàn)程組及其所有子組。 |
int enumerate(Thread[] list) | 將線(xiàn)程組及其子組中的所有活動(dòng)線(xiàn)程復(fù)制到指定的數(shù)組中。 |
int getMaxPriority() | 返回線(xiàn)程組的最大優(yōu)先級(jí)。 |
String getName() | 返回線(xiàn)程組的名稱(chēng)。 |
ThreadGroup getParent() | 返回線(xiàn)程組的父線(xiàn)程組。 |
void interrupt() | 中斷線(xiàn)程組中的所有線(xiàn)程。 |
boolean isDaemon() | 判斷線(xiàn)程組是否是守護(hù)線(xiàn)程組。 |
void setDaemon(boolean daemon) | 設(shè)置線(xiàn)程組的守護(hù)狀態(tài)。 |
boolean isDestroyed() | 判斷線(xiàn)程組是否已被銷(xiāo)毀。 |
void list() | 將線(xiàn)程組的信息打印到標(biāo)準(zhǔn)輸出。 |
boolean parentOf(ThreadGroup g) | 判斷線(xiàn)程組是否是參數(shù)線(xiàn)程組或其祖先線(xiàn)程組。 |
void suspend() | 掛起線(xiàn)程組中的所有線(xiàn)程。 |
void resume() | 恢復(fù)線(xiàn)程組中所有被掛起的線(xiàn)程。 |
void setMaxPriority(int prt) | 設(shè)置線(xiàn)程組的最大優(yōu)先級(jí)。 |
void stop() | 停止線(xiàn)程組中的所有線(xiàn)程。 |
String toString() | 返回線(xiàn)程組的字符串表示。 |
我們選擇其中一些方法來(lái)演示用法。
public class ThreadGroupMethodCase {
public static void main(String[] args) throws InterruptedException {
ThreadGroup subgroup1 = new ThreadGroup("subgroup1");
Thread t1 = new Thread(subgroup1, "t1 in subgroup1");
Thread t2 = new Thread(subgroup1, "t2 in subgroup1");
Thread t3 = new Thread(subgroup1, "t3 in subgroup1");
t1.start();
Thread.sleep(50);
t2.start();
int activeThreadCount = subgroup1.activeCount();
System.out.println("線(xiàn)程組 " + subgroup1.getName() + " 中的活動(dòng)線(xiàn)程數(shù)量:" + activeThreadCount);
ThreadGroup subgroup2 = new ThreadGroup("subgroup2");
Thread t4 = new Thread(subgroup2, "t4 in subgroup2");
ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();
int activeGroupCount = currentThreadGroup.activeGroupCount();
System.out.println("線(xiàn)程組 " + currentThreadGroup.getName() + " 中的活動(dòng)線(xiàn)程組數(shù)量:" + activeGroupCount);
System.out.println("將當(dāng)前線(xiàn)程組的信息打印到標(biāo)準(zhǔn)輸出:");
currentThreadGroup.list();
}
}
輸出:
線(xiàn)程組 subgroup1 中的活動(dòng)線(xiàn)程數(shù)量: 2
線(xiàn)程組 main 中的活動(dòng)線(xiàn)程組數(shù)量: 2
將當(dāng)前線(xiàn)程組的信息打印到標(biāo)準(zhǔn)輸出:
java.lang.ThreadGroup[name=main,maxpri=10]
Thread[main,5,main]
java.lang.ThreadGroup[name=subgroup1,maxpri=10]
java.lang.ThreadGroup[name=subgroup2,maxpri=10]
這里有一個(gè)有趣的地方:當(dāng)輸出當(dāng)前線(xiàn)程組中的活動(dòng)線(xiàn)程數(shù)量時(shí),實(shí)際上并沒(méi)有計(jì)算狀態(tài)為NEW和TERMINATED的線(xiàn)程。所以當(dāng)輸出subgroup1.activeCount()時(shí),實(shí)際上只有一個(gè)活動(dòng)線(xiàn)程,即t2,因?yàn)閠1已經(jīng)結(jié)束,而t3還沒(méi)有啟動(dòng)。
總結(jié)
簡(jiǎn)單來(lái)說(shuō),線(xiàn)程組是一個(gè)樹(shù)形結(jié)構(gòu),每個(gè)線(xiàn)程組下可以有多個(gè)線(xiàn)程或多個(gè)線(xiàn)程組。線(xiàn)程組可以用于統(tǒng)一控制線(xiàn)程的優(yōu)先級(jí)、檢查線(xiàn)程的權(quán)限等。