1. 程式人生 > 實用技巧 >第三章、執行緒組和執行緒優先順序

第三章、執行緒組和執行緒優先順序

3.1 執行緒組(ThreadGroup)

Java中用ThreadGroup來表示執行緒組,我們可以使用執行緒組對執行緒進行批量控制。

ThreadGroup和Thread的關係就如同他們的字面意思一樣簡單粗暴,每個Thread必然存在於一個ThreadGroup中,Thread不能獨立於ThreadGroup存在。執行main()方法執行緒的名字是main,如果在new Thread時沒有顯式指定,那麼預設將父執行緒(當前執行new Thread的執行緒)執行緒組設定為自己的執行緒組。

示例程式碼:

public class ThreadGroup_Demo {
    public static
void main(String[] args) { new Thread(()->{ System.out.println("測試執行緒名稱:"+Thread.currentThread().getName()); System.out.println("測試執行緒組名稱:"+Thread.currentThread().getThreadGroup().getName()); }).start(); System.out.println("主執行緒mian執行緒名稱:"+Thread.currentThread().getName()); System.out.println(
"主執行緒mian執行緒組名稱:"+Thread.currentThread().getThreadGroup().getName()); } }

ThreadGroup管理著它下面的Thread,ThreadGroup是一個標準的向下引用的樹狀結構,這樣設計的原因是防止上級執行緒被下級執行緒引用而無法有效地被GC回收

3.2 執行緒的優先順序

Java中執行緒優先順序可以指定,範圍是1~10。但是並不是所有的作業系統都支援10級優先順序的劃分(比如有些作業系統只支援3級劃分:低,中,高),Java只是給作業系統一個優先順序的參考值,執行緒最終在作業系統的優先順序是多少還是由作業系統決定。

Java預設的執行緒優先順序為5,執行緒的執行順序由排程程式來決定,執行緒的優先順序會線上程被呼叫之前設定。

通常情況下,高優先順序的執行緒將會比低優先順序的執行緒有更高的機率得到執行。我們使用方法Thread類的setPriority()例項方法來設定執行緒的優先順序。

public class Priority_Demo {
    public static void main(String[] args) {

        Thread thread = new Thread();

        System.out.println("執行緒預設優先順序:"+thread.getPriority());

        thread.setPriority(10);
        System.out.println("設定後的執行緒優先順序"+thread.getPriority());
    }
}

既然有1-10的級別來設定了執行緒的優先順序,這時候可能有些讀者會問,那麼我是不是可以在業務實現的時候,採用這種方法來指定一些執行緒執行的先後順序?

對於這個問題,我們的答案是:No!

Java中的優先順序來說不是特別的可靠,Java程式中對執行緒所設定的優先順序只是給作業系統一個建議,作業系統不一定會採納。而真正的呼叫順序,是由作業系統的執行緒排程演算法決定的

public class Priority_Demo {
    public static void main(String[] args) {

        IntStream.range(1,10).forEach(i->{
            Thread thread = new Thread(() -> {
                System.out.println(String.format("當前執行的執行緒是:%s,優先順序:%d",
                        Thread.currentThread().getName(),
                        Thread.currentThread().getPriority()));
            });
            thread.setPriority(i);
            thread.start();
        });
    }
}

Java提供一個執行緒排程器來監視和控制處於RUNNABLE狀態的執行緒。執行緒的排程策略採用搶佔式,優先順序高的執行緒比優先順序低的執行緒會有更大的機率優先執行。在優先順序相同的情況下,按照“先到先得”的原則。每個Java程式都有一個預設的主執行緒,就是通過JVM啟動的第一個執行緒main執行緒。

還有一種執行緒稱為守護執行緒(Daemon),守護執行緒預設的優先順序比較低。

如果某執行緒是守護執行緒,那如果所有的非守護執行緒都結束了,這個守護執行緒也會自動結束。

應用場景是:當所有非守護執行緒結束時,結束其餘的子執行緒(守護執行緒)自動關閉,就免去了還要繼續關閉子執行緒的麻煩。

一個執行緒預設是非守護執行緒,可以通過Thread類的setDaemon(boolean on)來設定。

在之前,我們有談到一個執行緒必然存在於一個執行緒組中,那麼當執行緒和執行緒組的優先順序不一致的時候將會怎樣呢?我們用下面的案例來驗證一下:

public static void main(String[] args) {
    ThreadGroup threadGroup = new ThreadGroup("t1");
    threadGroup.setMaxPriority(6);
    Thread thread = new Thread(threadGroup,"thread");
    thread.setPriority(9);
    System.out.println("我是執行緒組的優先順序"+threadGroup.getMaxPriority());
    System.out.println("我是執行緒的優先順序"+thread.getPriority());
}

輸出:

我是執行緒組的優先順序6 我是執行緒的優先順序6

所以,如果某個執行緒優先順序大於執行緒所在執行緒組的最大優先順序,那麼該執行緒的優先順序將會失效,取而代之的是執行緒組的最大優先順序。

3.3 執行緒組的常用方法及資料結構

3.3.1 執行緒組的常用方法

Thread.currentThread().getThreadGroup().getName()

複製執行緒組

// 獲取當前的執行緒組
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
// 複製一個執行緒組到一個執行緒陣列(獲取Thread資訊)
Thread[] threads = new Thread[threadGroup.activeCount()];
threadGroup.enumerate(threads);

執行緒組統一異常處理

public class ThreadGroup_Exception {

    public static void main(String[] args) {

        ThreadGroup group1 = new ThreadGroup("group1") {
            // 繼承ThreadGroup並重新定義以下方法
            // 線上程成員丟擲unchecked exception
            // 會執行此方法
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(t.getName() + ": " + e.getMessage());
            }
        };

        // 這個執行緒是group1的一員
        Thread thread1 = new Thread(group1, ()-> {
                // 丟擲unchecked異常
                throw new RuntimeException("測試異常");
        });

        thread1.start();
    }
}

3.3.2 執行緒組的資料結構

執行緒組還可以包含其他的執行緒組,不僅僅是執行緒。

首先看看 ThreadGroup原始碼中的成員變

public class ThreadGroup implements Thread.UncaughtExceptionHandler {
    private final ThreadGroup parent; // 父親ThreadGroup
    String name; // ThreadGroupr 的名稱
    int maxPriority; // 執行緒最大優先順序
    boolean destroyed; // 是否被銷燬
    boolean daemon; // 是否守護執行緒
    boolean vmAllowSuspension; // 是否可以中斷

    int nUnstartedThreads = 0; // 還未啟動的執行緒
    int nthreads; // ThreadGroup中執行緒數目
    Thread threads[]; // ThreadGroup中的執行緒

    int ngroups; // 執行緒組數目
    ThreadGroup groups[]; // 執行緒組陣列
}

然後看看建構函式:

// 私有建構函式
private ThreadGroup() { 
    this.name = "system";
    this.maxPriority = Thread.MAX_PRIORITY;
    this.parent = null;
}

// 預設是以當前ThreadGroup傳入作為parent  ThreadGroup,新執行緒組的父執行緒組是目前正在執行執行緒的執行緒組。
public ThreadGroup(String name) {
    this(Thread.currentThread().getThreadGroup(), name);
}

// 建構函式
public ThreadGroup(ThreadGroup parent, String name) {
    this(checkParentAccess(parent), parent, name);
}

// 私有建構函式,主要的建構函式
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方法,這裡看看這個方法的原始碼:

// 檢查parent ThreadGroup
private static Void checkParentAccess(ThreadGroup parent) {
    parent.checkAccess();
    return null;
}

// 判斷當前執行的執行緒是否具有修改執行緒組的許可權
public final void checkAccess() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkAccess(this);
    }
}

這裡涉及到SecurityManager這個類,它是Java的安全管理器,它允許應用程式在執行一個可能不安全或敏感的操作前確定該操作是什麼,以及是否是在允許執行該操作的安全上下文中執行它。應用程式可以允許或不允許該操作。

比如引入了第三方類庫,但是並不能保證它的安全性。

其實Thread類也有一個checkAccess()方法,不過是用來當前執行的執行緒是否有許可權修改被呼叫的這個執行緒例項。(Determines if the currently running thread has permission to modify this thread.)

總結來說,執行緒組是一個樹狀的結構,每個執行緒組下面可以有多個執行緒或者執行緒組。執行緒組可以起到統一控制執行緒的優先順序和檢查執行緒的許可權的作用。