Java多線程拾遺(二)——Thread構造函數

前言

上一篇博客中我們總結了多線程的幾種狀態,已經梳理了如何簡單的構建多線程,本篇博客打算繼續梳理Thread的幾種構造函數和守護線程。

Thread的構造方法

打開Thread的源碼,發現其有如下幾個構造函數

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

Thread(Runnable target, AccessControlContext acc) {
    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}

public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}

public Thread(String name) {
    init(null, null, name, 0);
}

public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}

public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize) {
    init(group, target, name, stackSize);
}

其實這些所有的構造函數,底層都會調用如下方法

//初始化一個線程,並設置線程組和線程id
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        if (security != null) {
            g = security.getThreadGroup();
        }

        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    g.checkAccess();


    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();

    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    this.stackSize = stackSize;

    tid = nextThreadID();
}

其實這裏面主要是完成一些線程id和線程組的設置,其中還有一些細節,後面會梳理到。

Thread的名稱

"Thread-" + nextThreadNum()

init方法中有時候會傳遞這個方法,這個方法默認構造Thread的名稱。

private static int threadInitNumber;//初始值爲0
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

可以看出,線程默認的命名方式是以"Thread-"爲前綴,然後加上一個自增的數字,這個自增的數字在整個JVM進程中將會不斷自增。

利用如下構造函數,我們其實也可以自己定義一個線程的名稱。

public Thread(String name) {
    init(null, null, name, 0);
}

不論我們使用默認的線程名稱還是指定一個我們自定義的線程名稱,在線程啓動之前還可以修改線程的名稱,一旦線程啓動,我們就無法再修改線程的名稱了。

Thread初始化時的ThreadGroup

//g是傳遞進來的ThreadGroup參數,這裏會獲取當前線程作爲父線程。
Thread parent = currentThread();
//獲取當前線程的安全管理程序
SecurityManager security = System.getSecurityManager();
if (g == null) {
    if (security != null) {
        g = security.getThreadGroup();
    }

    //如果在初始化線程的時候,沒有指定線程組,則將當前線程加入到父線程的線程組中。
    if (g == null) {
        g = parent.getThreadGroup();
    }
}

可以看到,如果在初始化線程的時候,沒有給初始化的線程設定線程組,則將當前線程加入到父線程的線程組中,那麼什麼是當前線程的父線程呢?
之前我們說過,在沒有完成Init之前,這個時候的線程並沒有啓動,還是new狀態,而在Init方法中currentThread獲取的是當前線程,這裏的當前線程指的是創建這個線程的線程。

簡單來說,我們可以總結成如下幾點:

1、一個線程的創建肯定是由另一個線程完成的

2、被創建線程的父線程是創建它的線程(例如,線程A在main中創建,則main線程就是這個線程的父線程)

我們知道main線程是由JVM創建的,我們在main函數中創建的線程其父線程都是main線程。

同時在上面的代碼中我們還會看到,在線程init的時候沒有顯示的指定線程所在的ThreadGroup,則會將線程加入到父線程所在的線程組

參看如下實例:

/**
 * autor:liman
 * createtime:2020/6/6
 * comment: ThreadGroup的實例
 */
@Slf4j
public class InitThreadGroup {

    public static void main(String[] args) {
        Runnable r = ()->{
            try {
//                Thread.sleep(50000l);
            } catch (Exception e) {
                e.printStackTrace();
            }
            log.info("this is child thread");
        };

        ThreadGroup threadGroup = new ThreadGroup("slef thread group");
        ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();
        Thread thread = new Thread(threadGroup,r,"childThread");
//        Thread thread = new Thread(r);//如果打開這一行註釋,最後一行輸出會成爲true
        thread.start();
        log.info("mainThread belongs thread group : {}",mainThreadGroup.getName());
        log.info("childThread belongs thread group : {}",threadGroup.getName());
        log.info("two thread group is same : {}",mainThreadGroup==thread.getThreadGroup());
    }

}

Thread與JVM虛擬機棧

上述在構造Thread的時候還有一個參數,stacksize,這個參數有什麼作用呢,這個就和jvm的內容有關了

Thread與stacksize

很多情況下我們創建線程不需要指定stacksize,會通過-xss的虛擬機參數進行設置即可。關於stack size,JDK的官網中是這樣介紹的

The stack size is the approximate number of bytes of address space that the virtual machine is to allocate for this thread's stack.The effect of the stackSize parameter,if any, is highly platform dependent.

這句話翻譯過來就是stack size 是虛擬機分配給線程的虛擬機棧的大小,這個參數高度依賴於不同的系統平臺。**stack size這個值越小,那麼JVM可以創建的線程數量就越多。但是這個值越大,隨之而來的函數調用的深度也就越大。**可以初略的認爲,一個Java進程的大小=堆內存+線程數量*棧內存(stack size)

JVM內存模型

stacksize無需手動構建,但是在學習線程的過程中會發現,線程和棧內存的關係非常密切,在瞭解這兩者關係之前需要熟悉JVM內存模型,這個之前總結過,這裏不再贅述——傳送門

守護線程

這個是一個比較特殊的線程,一般只是完成一些後臺處理的工作,關於守護線程,其實JDK文檔中有一個描述

The Java Virsual Machine exits when the only threads running are all daemon threads.

翻譯過來就是:在正常情況下,如果JVM中沒有一個非守護線程,則JVM的進程會退出。

來看一個實例

@Slf4j
public class DaemonThreadDemo {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
           while(true){
               try {
                   log.info("self thread start");
                   Thread.sleep(1);
                   log.info("self thread end");
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        //將我們自己的線程設置爲守護線程
        thread.setDaemon(true);
        thread.start();
        log.info("Main thread finished");
    }
}

這個時候會發現,我們的線程並不能持久的運行下去,因爲main線程結束之後,我們的進程中並沒有非守護線程了,這個時候進程就會終止。運行結果如下。如果將thread.setDaemon(true)註釋掉,則我們的線程會一直運行下去。

在這裏插入圖片描述

由於我們的main線程是一個非守護線程,如果將thread.setDaemon(true)註釋掉,main線程結束之後,當前進程中再無守護線層,則該進程會終止。同時順便總結一句,thread.setDaemon()的操作必須在線程start之前完成,否則會拋出異常。

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