二、JAVA多執行緒:深入理解Thread建構函式(Thread、Runnable、守護執行緒、ThreadGroup)
本章主要介紹了所有與Thread有關的建構函式,執行緒的父子關係(並非繼承關係,而是一種包含關係),Thread和ThreadGroup之間的關係,Thread與虛擬機器棧的關係(學習這部分內容需要讀者有JVM的相關基礎,尤其是對棧記憶體要有深入的理解),最後還介紹了守護執行緒的概念、特點和使用場景。
執行緒命名
建構函式:
Constructor and Description |
---|
Thread() Allocates a new |
Thread Allocates a new |
Thread(Runnable target, String name) Allocates a new |
Thread(String name) Allocates a new |
Thread(ThreadGroup group, Allocates a new |
Thread(ThreadGroup group, Runnable target, String name) Allocates a new |
Thread(ThreadGroup group, Runnable target, String name, long stackSize) Allocates a new |
Thread(ThreadGroup group, String name) Allocates a new |
執行緒在啟動之後,名稱不允許修改:
/**
* 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高併發程式設計詳解:多執行緒與架構設計》 --汪文君