Thread執行緒原始碼解析
1 首先我們來看Thread類的大致註釋翻譯如下:
Thread是程式執行中的一個執行緒,JVM允許程式有多個執行緒併發執行。 每個執行緒都有一個優先順序,執行緒優先順序高的先執行,每個執行緒都可能被標記為守護程序。在程式執行時,一個執行緒建立了新的Thread物件,會為新的執行緒物件設定優先順序,其優先順序會等於建立他的執行緒的優先順序。 當JVM啟動時,會建立一個非守護執行緒(被叫做"main"的主執行緒),JVM會繼續執行執行緒直到如下情況: --> 呼叫exit時,執行緒退出。System.exit(-1); //異常退出,0/1正常退出 除了守護執行緒,其他執行緒都掛掉了或者return或者拋異常就退出 --> 有兩種方式實現Thread執行緒,一種是繼承Thread子類,另一種是宣告一個類實現Runnable 每個執行緒有特定標識,如果沒被指定,就會分配一個預設的執行緒名字
2 觀看前情提要:
注意點:執行緒組的出現時機。執行緒組的設計模式->組合模式(統一管理)
以 new Thread(myRunnable, "執行緒1").start(); 總結:
先看有沒執行緒組,有組就賦進去,有執行緒名就賦值進去,優先順序給進去。他的執行緒狀態得自父親,再看安全管理器有沒限制行為,有的話就限制一下,沒有就不限制。
3 程式碼感想:
setPriority:如果執行緒組不為空,設定的優先順序不能大於執行緒組優先順序getMaxPriority nextThreadID:通過自增++threadSeqNumber獲取執行緒名中的序列號 clone:會拋CloneNotSupportedException,Thread不支援克隆
4 構造器解析
//1 無參構造,用法:new Thread();
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
解析:呼叫init(),初始化group、daemon、priority、stackSize、contextClassLoader等屬性,其中daemon、priority、ContextClassLoader都預設從父類執行緒(即建立當前執行緒的執行緒)中獲取。並預設給了一個執行緒名 "Thread-" + nextThreadNum() ,數字自增方式顯示。
//2 用法:new Thread(Runnable runnable); public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
解析:將繼承了Runnable介面的物件傳入init並初始化,其他與1同。
//3 用法:new Thread(group, ruunable);
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
//4 new Thread("執行緒名");
public Thread(String name) {
init(null, null, name, 0);
}
//4 new Thread(group, "執行緒名");
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
//5 new Thread(runnable, "執行緒名");
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
//6 new Thread(group, runnable, "執行緒名");
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
//7 new Thread(group, runnable, name, stackSize);
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
init(group, target, name, stackSize);
}
上面的Thread進行了多次過載,都呼叫了init()方法,現在講解下init方法如下:
/**
* 初始化一個執行緒
* @param 一個執行緒組
* @param 呼叫run()方法的物件
* @param 新的執行緒名稱
* @param 新執行緒需要的堆疊大小,或者0為忽視
* @param 通過控制上下文來繼承,或可為null
**/
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(jdk1.8中的註釋):
** 安全管理器允許應用程式類實現一個安全策略,允許應用程式是否在安全或敏感的環境執行
** 安全管理器包含很多以check 開頭的方法,這些方法在java類庫中被個鐘敏感操作呼叫,如下示例:
** SecurityManager security = System.getSecurityManager();
** if (security != null) {
** security.check<i>XXX</i>(argument, . . . );
** }
** 安全管理器有機會阻止一個丟擲異常的操作,如果操作允許,安全管理器程式就會直接返回,
** 不允許則拋SecurityException異常,這個異常唯一的意外是checkTopLevelWindow,他返回的是boolean,不會丟擲異常。
** 當前安全管理器是通過System.setSecurityManager方法來設定,通過System.getSecurityManager來獲得
** 以下有一個特定的方法:
** 指定一個請求是否被允許或否定,預設的繼承呼叫如下:
** AccessController.checkPermission(perm)
** 請求允許,checkPermission就會返回空,否則拋SecurityException
** 以下是一個簡單的呼叫
** Object context = null;
** SecurityManager sm = System.getSecurityManager();
** if (sm != null) context = sm.getSecurityContext();
** if (sm != null) sm.checkPermission(permission, context);
**/
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) {
//驗證當前例項是否能在不違反安全規範的情況下被構建,子類不能覆蓋安全敏感的非final方法。
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
//將ThreadGroup中的就緒執行緒計數器+1 nUnstartedThreads++,此時執行緒還沒被加入ThreadGroup
g.addUnstarted();
//初始化Thread中屬性 執行緒組、守護程序標識、優先順序、
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;
//設定優先順序,如果當前設定的優先順序大於執行緒組優先順序則取當前執行緒組的最大優先順序
/**
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
**/
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 */
//設定執行緒id,return ++threadSeqNumber;
tid = nextThreadID();
}
注:預設情況下安全管理器是關閉的,可用-Djava.security.manager 開啟。
5 執行緒啟動過程講解
上面我們說了構造方法是如何初始化的,接下來講解下一些方法
初始化一個執行緒首先要實現他內部的run方法,然後再start(),示例如下:
new Thread() {
@Override
public void run() {
//執行的操作
}
}.start();
然後點開看Thread.java的原始碼可看到run()方法是調了runnable介面的run(),在沒傳入runnable即像這種new Thread(runnable)時可像上面一樣重寫run()。
@Override
public void run() {
if (target != null) {
target.run();
}
}
然後再看start()執行緒啟動方法如下:
public synchronized void start() {
......
start0();
......
}
從上面可看到一個關鍵的native方法start0(),這個本地方法就是呼叫java在執行start()啟動執行緒去呼叫run()方法的關鍵,如下:
private native void start0();
start()呼叫run()的過程如下:
大致流程圖為:
詳細過程如下:
1 執行緒啟動呼叫start()時,會呼叫一個native方法start0()。
public synchronized void start() {
......
start0();
......
}
private native void start0();
2 當start0被載入到jvm時,呼叫Thread.java類中registerNatives(該方法定義在Thread.c)進行註冊該本地方方法(stop0之類的也是一樣被註冊)
private static native void registerNatives();
static {
registerNatives();
}
3 然後可在Thread.c中看到定義好的執行緒公用資料和操作,可看到start0()對映的方法是JVM_StartThread如下:
static JNINativeMethod methods[] = {
{"start0", "()V",(void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive","()Z",(void *)&JVM_IsThreadAlive},
{"suspend0","()V",(void *)&JVM_SuspendThread},
..................
}
因此java的執行緒呼叫start()實際會調到C裡面的JVM_StartThread方法,然後JVM_StartThread又會呼叫thread_enrty如下:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
…
native_thread = new JavaThread(&thread_entry, sz);
…
static void thread_entry(JavaThread* thread, TRAPS) {
HandleMark hm(THREAD);
Handle obj(THREAD, thread->threadObj());
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,obj,
KlassHandle(THREAD,SystemDictionary::Thread_klass()),
vmSymbolHandles::run_method_name(), //重點:最終調到這個方法
vmSymbolHandles::void_method_signature(),THREAD);
}
所以可總結呼叫的鏈路如下:
Java啟動一個執行緒即thread.start()->呼叫native方法start0()->通過registerNatives()註冊start0()
->在Thread.c的methods找到start0對映的JVM_StartThread
->JVM_StartThread中new JavaThread
->呼叫thead_enrty->調vmSymbolHandles::run_method_name()
參考資料:
jdk1.8原始碼