1. 程式人生 > >二、JAVA多執行緒:深入理解Thread建構函式(Thread、Runnable、守護執行緒、ThreadGroup)

二、JAVA多執行緒:深入理解Thread建構函式(Thread、Runnable、守護執行緒、ThreadGroup)

本章主要介紹了所有與Thread有關的建構函式,執行緒的父子關係(並非繼承關係,而是一種包含關係),Thread和ThreadGroup之間的關係,Thread與虛擬機器棧的關係(學習這部分內容需要讀者有JVM的相關基礎,尤其是對棧記憶體要有深入的理解),最後還介紹了守護執行緒的概念、特點和使用場景。

 

執行緒命名

建構函式:

Constructor and Description
Thread()

Allocates a new Thread object.

Thread
(Runnable target)

Allocates a new Thread object.

Thread(Runnable target, String name)

Allocates a new Thread object.

Thread(String name)

Allocates a new Thread object.

Thread(ThreadGroup group, 
Runnable
 target)

Allocates a new Thread object.

Thread(ThreadGroup group, Runnable target, String name)

Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group

.

Thread(ThreadGroup group, Runnable target, String name, long stackSize)

Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group, and has the specified stack size.

Thread(ThreadGroup group, String name)

Allocates a new Thread object.

 

執行緒在啟動之後,名稱不允許修改:

/**
 * Changes the name of this thread to be equal to the argument
 * <code>name</code>.
 * <p>
 * First the <code>checkAccess</code> method of this thread is called
 * with no arguments. This may result in throwing a
 * <code>SecurityException</code>.
 *
 * @param      name   the new name for this thread.
 * @exception  SecurityException  if the current thread cannot modify this
 *               thread.
 * @see        #getName
 * @see        #checkAccess()
 */
public final synchronized void setName(String name) {
    checkAccess();
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;
    if (threadStatus != 0) {
        setNativeName(name);
    }
}

 

執行緒的父子關係

Thread類所有的建構函式都會呼叫init方法,都會呼叫init方法,如下程式碼

 

/**
 * Allocates a new {@code Thread} object. This constructor has the same
 * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
 * {@code (null, null, gname)}, where {@code gname} is a newly generated
 * name. Automatically generated names are of the form
 * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
 */
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
/**
 * Allocates a new {@code Thread} object. This constructor has the same
 * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
 * {@code (null, null, name)}.
 *
 * @param   name
 *          the name of the new thread
 */
public Thread(String name) {
    init(null, null, name, 0);
}

 

/**
 * Allocates a new {@code Thread} object. This constructor has the same
 * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
 * {@code (null, target, name)}.
 *
 * @param  target
 *         the object whose {@code run} method is invoked when this thread
 *         is started. If {@code null}, this thread's run method is invoked.
 *
 * @param  name
 *         the name of the new thread
 */
public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}

 

檢視init方法

/**
 * Initializes a Thread.
 *
 * @param g the Thread group
 * @param target the object whose run() method gets called
 * @param name the name of the new Thread
 * @param stackSize the desired stack size for the new thread, or
 *        zero to indicate that this parameter is to be ignored.
 * @param acc the AccessControlContext to inherit, or
 *            AccessController.getContext() if null
 * @param inheritThreadLocals if {@code true}, inherit initial values for
 *            inheritable thread-locals from the constructing thread
 */
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();

    ..................


}

 

任何一個執行緒都有一個父執行緒

currentThread 代表的是建立它的那個執行緒,我們建立的執行緒都是main函式建立的,意味著我們所有的建立的執行緒的父執行緒都是main執行緒

結論:

          一個執行緒的建立肯定是由另一個執行緒完成的

          被建立執行緒的父執行緒就是建立他的執行緒

 

Thread和ThreadGroup

線上程初始化的時候(init方法)可以指定ThreadGroup

/**
 * Initializes a Thread.
 *
 * @param g the Thread group
 * @param target the object whose run() method gets called
 * @param name the name of the new Thread
 * @param stackSize the desired stack size for the new thread, or
 *        zero to indicate that this parameter is to be ignored.
 * @param acc the AccessControlContext to inherit, or
 *            AccessController.getContext() if null
 * @param inheritThreadLocals if {@code true}, inherit initial values for
 *            inheritable thread-locals from the constructing thread
 */
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    ...................................

    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        /* Determine if it's an applet or not */

        /* If there is a security manager, ask the security manager
           what to do. */
        if (security != null) {
            g = security.getThreadGroup();
        }

        /* If the security doesn't have a strong opinion of the matter
           use the parent thread group. */
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    /* checkAccess regardless of whether or not threadgroup is
       explicitly passed in. */
    g.checkAccess();

    /*
     * Do we have the required permissions?
     */
    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);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();
}

 

測試程式碼:

結論:

main執行緒所在的ThreadGroup成為main

構造一個執行緒的時候,如果沒有顯示的指明ThreadGroup,那麼她和所在的父執行緒同處一個ThreadGroup,並且具有同樣的優先順序

 

 

Thread和Runnable

 

Thread: 負責執行緒本身相關的職責和控制

Runnable: 邏輯執行單元的部分

 

Thread和JVM虛擬機器棧

在Thread的建構函式中有一個stackSize , 程式碼如下:

/*
 * The requested stack size for this thread, or 0 if the creator did
 * not specify a stack size.  It is up to the VM to do whatever it
 * likes with this number; some VMs will ignore it.
 */
private long stackSize;

 

Thread和stackSize

一般情況下,建立執行緒的時候,不會手動指定棧記憶體的地址空間位元組陣列,統一通過xss引數設定即可

stacksize 越大代表著 執行緒內方法呼叫遞迴的深度就越深

stacksize 越小代表著 建立執行緒數量就越多(依賴平臺)

 

對平臺的依賴測試:

         

 

JVM記憶體結構

 


 

 

Java堆(Heap,Java虛擬機器所管理的記憶體中最大的一塊。Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時建立。此記憶體區域的唯一目的就是存放物件例項,幾乎所有的物件例項都在這裡分配記憶體。

方法區(Method Area,方法區(Method Area)與Java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。

程式計數器(Program Counter Register,程式計數器(Program Counter Register)是一塊較小的記憶體空間,它的作用可以看做是當前執行緒所執行的位元組碼的行號指示器。

JVM棧(JVM Stacks,與程式計數器一樣,Java虛擬機器棧(Java Virtual Machine Stacks)也是執行緒私有的,它的生命週期與執行緒相同。虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於儲存區域性變量表、操作棧、動態連結、方法出口等資訊。每一個方法被呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。

本地方法棧(Native Method Stacks,本地方法棧(Native Method Stacks)與虛擬機器棧所發揮的作用是非常相似的,其區別不過是虛擬機器棧為虛擬機器執行Java方法(也就是位元組碼)服務,而本地方法棧則是為虛擬機器使用到的Native方法服務。

 

 

 

太多了,就只是放了幾張圖。

Thread與虛擬機器棧

虛擬機器棧記憶體是執行緒私有的,也就是說每一個執行緒都會佔有指定的記憶體大小。

可以粗略的理解一個JAVA程序的記憶體大小為: 堆記憶體+執行緒數量*棧記憶體

 

執行緒數量=(最大地址空間(MaxProcessMemory)-  JVM堆記憶體 - ReserverdOsMemory / ThreadStackSize(Xss))

ReserverdOsMemory: 系統保留記憶體,一般在136Mb左右

 

執行緒數量還與作業系統的一些核心配置有很大的關係,如Linux下

         /proc/sys/kernel/threads-max

         /proc/sys/kernel/pid-max

         /proc/sys/vm/max_map_count

 

守護執行緒

守護執行緒是一類比較特殊的執行緒,一般用於處理一些後臺的工作。 如JDK的垃圾回收執行緒。

當你希望關閉某些執行緒,或者退出JVN程序的時候,一些執行緒能夠自動關閉。

程式碼:

public class DamomThread {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread( ()-> {
            while (true){
                try {
                    Thread.sleep(1000);
                    System.out.println("內部執行緒:正在執行。。。。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

        }  ) ;

        //開啟守護程序
//        thread.setDaemon(true);
        thread.start();

        Thread.sleep(2_000L );


        System.out.println("Main thread finished lifecycle !!!!!!!!!!");

    }


}

 

該程式碼一共開啟了兩個執行緒,一個是main執行緒,另一個是裡面執行的執行緒thread

 

未開始守護程序:    thread.setDaemon(false);

外面的執行緒main執行緒,結束退出,裡面的執行緒,依舊繼續執行,如截圖

 

開始守護程序:    thread.setDaemon(true);

main執行緒退出,內部執行緒一起退出。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本文整理來源於:

《Java高併發程式設計詳解:多執行緒與架構設計》 --汪文君