Java多線程拾遺(六)——ThreadGroup初探

前言

關於ThreadGroup的話,其實使用的並不多,在沒有線程池的時代,還是用的很多,但是有些老的源碼閱讀起來還是會有線程組的影子,這裏與《Java高併發詳解 》一書保持一致,進行一個簡單的總結

線程組與線程之間的關係其實可以類比數據結構中的樹形結構。《Java高併發詳解 》一書中有這樣一張圖,ThreadGroup可以看成樹的非葉子節點,Thread可以看成是樹的葉子節點。

在這裏插入圖片描述

線程組的創建

通過查看ThreadGroup的源碼發現,其實ThreadGroup對外提供的公共的構造方法只有如下兩個

public ThreadGroup(String name);//只指定線程組的名稱,父線程組默認爲當前線程組
public ThreadGroup(ThreadGroup parent, String name);//指定父線程組,同時指定線程組的名稱

直接看如下實例吧:

/**
 * autor:liman
 * createtime:2020/6/21
 * comment:線程組的創建
 */
@Slf4j
public class ThreadGroupCreate {

    public static void main(String[] args) {

        log.info("main線程組的名稱爲:{}",Thread.currentThread().getThreadGroup().getName());

        //父線程默認爲main 線程組
        ThreadGroup threadGroupOne = new ThreadGroup("thread group one");
        Thread threadOne = new Thread(threadGroupOne,()->{
            try {
                Thread currentThread = Thread.currentThread();
                ThreadGroup currentGroup = currentThread.getThreadGroup();
                ThreadGroup parentGroup = currentThread.getThreadGroup().getParent();
                //當前線程組的活動線程個數
                int currentActiveAcount = currentGroup.activeCount();
				//父線程組的活動線程個數(api文檔中介紹,無法訪問父線程的相關信息,其實是可以訪問的)
                int parentActiveAcount = parentGroup.activeCount();
                log.info("當前線程:{},所屬線程組:{},父線程組:{}",currentThread.getName(),currentGroup.getName(),parentGroup.getName());
                log.info("當前線程組的活動線程數量:{},父線程組的活動線程數量:{}",currentActiveAcount,parentActiveAcount);
                Thread.sleep(10_000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"threadOne");//創建線程的時候,顯示指定線程組
        threadOne.start();

        ThreadGroup threadGroupTwo = new ThreadGroup(threadGroupOne,"thread group two");
        Thread threadTwo = new Thread(threadGroupTwo,()->{
            try {
                Thread currentThread = Thread.currentThread();
                ThreadGroup currentGroup = currentThread.getThreadGroup();
                ThreadGroup parentGroup = currentThread.getThreadGroup().getParent();
                //當前線程組的活動線程個數
                int currentActiveAcount = currentGroup.activeCount();
				//父線程組的活動線程個數(api文檔中介紹,無法訪問父線程的相關信息,其實是可以訪問的)
                int parentActiveAcount = parentGroup.activeCount();
                log.info("當前線程:{},所屬線程組:{},父線程組:{}",currentThread.getName(),currentGroup.getName(),parentGroup.getName());
                log.info("當前線程組的活動線程數量:{},父線程組的活動線程數量:{}",currentActiveAcount,parentActiveAcount);
                Thread.sleep(10_000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"threadTwo");
        threadTwo.start();
    }
}

創建了兩個線程組,第一個沒有顯示指定父線程組,則會將這個線程組的父線程組設置爲啓動該線程組的主線程組。第二個顯示設置了線程組。具體運行結果如下所示:

在這裏插入圖片描述

線程組的複製

上述代碼線程組與線程之間的關係如下所示

在這裏插入圖片描述

獲取活躍線程數量

其實就是一個activeCount方法,JDK文檔中,關於這個方法的說明如下:

Returns an estimate of the number of active threads in this thread,group and its subgroups. Recursively iterates over all subgroups in,this thread group.

只是返回一個預估的活躍線程數量,會遞歸的找出子線程中的活躍線程數量

對上述代碼中的Thread Group One和Thread Group Two調用activeCount會得到如下結果

在這裏插入圖片描述

複製

複製線程

複製線程只是單純的複製線程組中的線程,有遞歸和非遞歸兩種

具體源碼如下

/**
默認就是遞歸拷貝的方式
*/
public int enumerate(Thread list[]) {
    checkAccess();
    return enumerate(list, 0, true);
}

public int enumerate(Thread list[], boolean recurse) {
    checkAccess();
    return enumerate(list, 0, recurse);
}

具體實例

/**
 * autor:liman
 * createtime:2020/6/21
 * comment:線程組的複製
 */
@Slf4j
public class ThreadGroupDulip {

    public static void main(String[] args) {
        log.info("main線程組的名稱爲:{}",Thread.currentThread().getThreadGroup().getName());

        //父線程默認爲main 線程組
        ThreadGroup threadGroupOne = new ThreadGroup("thread group one");
        Thread threadOne = new Thread(threadGroupOne,()->{
            try {
                Thread currentThread = Thread.currentThread();
                ThreadGroup currentGroup = currentThread.getThreadGroup();
                ThreadGroup parentGroup = currentThread.getThreadGroup().getParent();
                Thread.sleep(10_000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"threadOne");//創建線程的時候,顯示指定線程組
        threadOne.start();

        ThreadGroup threadGroupTwo = new ThreadGroup(threadGroupOne,"thread group two");
        Thread threadTwo = new Thread(threadGroupTwo,()->{
            try {
                Thread currentThread = Thread.currentThread();
                ThreadGroup currentGroup = currentThread.getThreadGroup();
                ThreadGroup parentGroup = currentThread.getThreadGroup().getParent();
                Thread.sleep(10_000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"threadTwo");
        threadTwo.start();

        Thread[] targetThreadList = new Thread[threadGroupOne.activeCount()];

        duplicate(threadGroupOne,targetThreadList);
    }

    public static void duplicate(ThreadGroup originalThreadGroup,Thread[] threadList){
        String orginalThreadGroupName = originalThreadGroup.getName();
        originalThreadGroup.enumerate(threadList,false);//無遞歸拷貝
        log.info("線程組:{},無遞歸拷貝後的線程名稱爲",orginalThreadGroupName);
        Arrays.asList(threadList).forEach(t->{if(t!=null) log.info("{}",t.getName());});

        //這裏也可以不指定第二個參數爲true,因爲默認爲true,即默認遞歸獲取拷貝線程
        originalThreadGroup.enumerate(threadList,true);
        log.info("線程組:{},遞歸拷貝後的線程名稱爲",orginalThreadGroupName);
        Arrays.asList(threadList).forEach(t->{if(t!=null) log.info("{}",t.getName());});
    }
}

在這裏插入圖片描述

複製線程組

與複製線程大同小異,源碼的複製拷貝方法名稱與複製線程一樣

/**
默認就是遞歸拷貝的方式
*/
public int enumerate(ThreadGroup list[]) {
    checkAccess();//評估當前線程是否有權限改變這個線程組,如果沒有權限,則會拋出異常。
    return enumerate(list, 0, true);
}

public int enumerate(ThreadGroup list[], boolean recurse) {
    checkAccess();
    return enumerate(list, 0, recurse);
}

線程組的中斷和銷燬

中斷

線程組的interrupt會將線程組中所有的線程都設置中斷標誌。這個直接看源碼

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();
    }
}

銷燬

對於線程組而言,並不是真正意義上的銷燬,而是簡單將該線程組從父線程中移出。但是前提是,被刪除的線程組中沒有活躍線程存在。

守護線程組

與線程一樣,線程組也有一個守護線程組的概念,對於線程組而言,如果這個線程組是守護線程,當其中沒有任何一個活躍線程的時候線程組將會自動銷燬

總結

對於線程組而言,沒有過多可總結的,只是有些框架的源碼還用到線程組這個東西,需要一個簡單的總結

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章