上一篇文章對Thread的狀態轉換和其中的部分源碼進行了一個說明,今天繼續對Thread源碼進行解讀,有不少涉及到底層的知識,筆者這裏也只能進行簡單瞭解,一塊兒看一看吧java
JDK版本號:1.8.0_171
在併發操做中,線程做爲基本的操做單位,對於其瞭解的深度或多或少都會影響咱們在多線程中的操做,今天就繼續對Thread源碼進行分析,看一看其中涉及的線程操做,理解下其是如何實現的,固然,因爲其大量調用了native方法,咱們也只能瞭解個大概,不過這也不妨礙咱們對其更深一步的理解編程
// 線程名稱 private volatile String name; // 優先級 private int priority; // 下面這兩個暫時還沒明白是作什麼的 // debug時eetop是有值的 private Thread threadQ; private long eetop; /* Whether or not to single_step this thread. */ // 是不是單步執行 private boolean single_step; /* Whether or not the thread is a daemon thread. */ // 是不是守護線程,默認false private boolean daemon = false; /* JVM state */ // 虛擬機狀態 private boolean stillborn = false; /* What will be run. */ // Runnable實現類,最終調用的業務處理代碼就在這了 private Runnable target; /* The group of this thread */ // 所屬線程組 private ThreadGroup group; /* The context ClassLoader for this thread */ // 當前線程的ClassLoader private ClassLoader contextClassLoader; /* The inherited AccessControlContext of this thread */ // 當前線程繼承的權限控制上下文,和安全機制相關 private AccessControlContext inheritedAccessControlContext; /* For autonumbering anonymous threads. */ // 自動編號匿名線程,線程名中的編號獲取的就是這個變量值 private static int threadInitNumber; /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ // 此線程的本地變量值.此map由ThreadLocal類進行維護,由於這個類在ThreadLocal中是包級私有的 // ThreadLocalMap是一個用於維護線程本地變量的hashmap ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ // 與當前線程有關繼承過來的本地變量值,由InheritableThreadLocal維護 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; /* * 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. */ // 線程請求棧大小,未指定則默認0,最終由所使用的虛擬機決定,一些虛擬機會忽略這個參數 private long stackSize; /* * JVM-private state that persists after native thread termination. */ // 在本地線程終止以後保留jvm私有狀態,沒搞懂什麼意思 private long nativeParkEventPointer; /* * Thread ID */ // 線程id private long tid; /* For generating thread ID */ // 用來生成線程id,tid變量的設置是經過這個值設置的 private static long threadSeqNumber; /* Java thread status for tools, * initialized to indicate thread 'not yet started' */ // 線程狀態,初始化時標記默認未啓動 private volatile int threadStatus = 0; /** * The argument supplied to the current call to * java.util.concurrent.locks.LockSupport.park. * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker * Accessed using java.util.concurrent.locks.LockSupport.getBlocker */ // 用於調用java.util.concurrent.locks.LockSupport.park的參數 // 值由java.util.concurrent.locks.LockSupport.setBlocker設置 // 由java.util.concurrent.locks.LockSupport.getBlocker訪問 volatile Object parkBlocker; /* The object in which this thread is blocked in an interruptible I/O * operation, if any. The blocker's interrupt method should be invoked * after setting this thread's interrupt status. */ // 可中斷I/O操做時這個線程的這個對象處於阻塞狀態 // 當線程中斷狀態被設置時,這個blocker的interrupt方法應該被調用 // 可參考interrupt方法 private volatile Interruptible blocker; // 設置block用到的對象鎖 private final Object blockerLock = new Object(); /* Set the blocker field; invoked via sun.misc.SharedSecrets from java.nio code * 設置blocker的值,經過java.nio代碼中的 sun.misc.SharedSecrets進行調用 */ void blockedOn(Interruptible b) { synchronized (blockerLock) { blocker = b; } } /** * The minimum priority that a thread can have. */ // 最小優先級 public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ // 線程默認優先級 public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ // 最高優先級 public final static int MAX_PRIORITY = 10; private static final RuntimePermission SUBCLASS_IMPLEMENTATION_PERMISSION = new RuntimePermission("enableContextClassLoaderOverride"); // The following three initially uninitialized fields are exclusively // managed by class java.util.concurrent.ThreadLocalRandom. These // fields are used to build the high-performance PRNGs in the // concurrent code, and we can not risk accidental false sharing. // Hence, the fields are isolated with @Contended. // 如下三個初始未初化的變量專門由java.util.concurrent.ThreadLocalRandom管理 // 這些變量用在併發代碼中構建高性能的PRNGs,因爲存在共享失敗的狀況因此咱們不能冒險共享 // 所以,這些變量使用註解@Contended隔離 // 避免僞共享,偏底層,有興趣能夠找資料研究下 /** The current seed for a ThreadLocalRandom */ @sun.misc.Contended("tlr") long threadLocalRandomSeed; /** Probe hash value; nonzero if threadLocalRandomSeed initialized */ @sun.misc.Contended("tlr") int threadLocalRandomProbe; /** Secondary seed isolated from public ThreadLocalRandom sequence */ @sun.misc.Contended("tlr") int threadLocalRandomSecondarySeed;
其中有不少變量我也不是很明白,這裏你們先了解下就好,也不用太過糾結,畢竟有不少涉及到了底層部分,可能要進行JVM的源碼學習才能明白作了什麼,這裏我稍微簡單說明下部分變量數組
eetop可能有些偏向底層了,網上沒有仔細說明的,本地debug的時候確實有值,只是不知道存儲的是什麼,不過我在stackoverflow上卻是搜到了提問,你們有興趣能夠看看:安全
https://stackoverflow.com/que...多線程
threadLocals和inheritableThreadLocals兩個變量值我在以後ThreadLocal源碼部分會進行說明,這裏能夠先簡單理解,畢竟ThreadLocal也確實比較重要併發
parkBlocker的部分涉及到了LockSupport,parkBlocker是用於記錄線程是被誰阻塞的。能夠經過LockSupport的getBlocker獲取到阻塞的對象。用於監控和分析線程用的,後面有時間也會進行LockSupport的源碼分析,這裏大概瞭解下就好app
構造方法仍是挺多的,最終調用都是init方法,主要入參爲線程組,實現Runnable接口的類,線程名,線程id,線程棧大小,稍後分析init方法less
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); }
其中咱們能夠看到在默認設置name的時候調用了nextThreadNum,方法實現以下,使用了synchronized來保證併發安全,這個變量只是在設置name時使用,這樣也解釋了爲何咱們在debug的時候看到的線程名都是Thread-0,Thread-1等等這樣順序的線程名dom
private static synchronized int nextThreadNum() { return threadInitNumber++; }
stackSize默認爲0,通常咱們也不會設置這個,虛擬機自己有默認的大小。同時可傳入AccessControlContext(安全機制相關,不深刻,可自行查找相關資料)。咱們須要關注的也就是group,target,name三個參數了,因此相對來講仍是比較簡單的jvm
其中涉及到不少native方法,先來了解下其含義
/** * Returns a reference to the currently executing thread object. * 返回當前正在執行線程對象的引用,當前線程自己 * 咱們常常用的方法,Thread.currentThread()獲取當前線程對象 * @return the currently executing thread. */ public static native Thread currentThread(); /** * 提示線程調度器當前線程願意放棄當前CPU的使用。固然調度器能夠忽略這個提示 * 讓出CPU是一種嘗試,防止某些線程過分使用,通常不多使用,併發時可能比較有用 * 另外讓出只是放棄,可是以後CPU時間片的分配仍是有可能繼續分到這個讓出線程上 */ public static native void yield(); /** * 此方法使得當前執行線程暫停指定毫秒數 * 上一篇文章我也說過,sleep會釋放cpu使用權,可是不會釋放鎖,也就是資源還在被佔用着 */ public static native void sleep(long millis) throws InterruptedException; /** * 啓動線程 */ private native void start0(); /** * 測試一些線程是否被中斷 * 入參是否清理中斷狀態 * 中斷狀態會被重置或者並不依賴於以前的ClearInterrupted的值 */ private native boolean isInterrupted(boolClearInterrupted); /** * 當且僅當當前線程持有對象的monitor鎖時返回true * 這個方法設計目的是程序自身代表已經獲取了某個對象的鎖 * assert Thread.holdsLock(obj) */ public static native boolean holdsLock(Object obj); // 堆棧信息數組,獲取線程堆棧信息時用到 private native static StackTraceElement[][] dumpThreads(Thread[] threads); // 獲取線程數組 private native static Thread[] getThreads(); /* Some private helper methods */ // 私有的本地輔助方法 // 設置優先級 private native void setPriority0(int newPriority); // 中止 private native void stop0(Object o); // 暫停 private native void suspend0(); // 從新開始 private native void resume0(); // 中斷 private native void interrupt0(); // 設置線程名 private native void setNativeName(String name); // 線程是否存活 public final native boolean isAlive(); @Deprecated public native int countStackFrames();
非native方法大部分最終仍是進行了native方法的調用,能夠理解,畢竟涉及到操做系統層面的調用,下面咱們來看下其中的部分源碼實現
init經過入參初始化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(); 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); } } // 線程組中未啓動線程數加1 g.addUnstarted(); this.group = g; // 這裏咱們看到當前線程是否爲守護進程是由建立當前線程的父線程決定的 // 父線程是守護線程,則其建立的子線程也是守護線程 this.daemon = parent.isDaemon(); // 優先級一樣與父線程保持一致 this.priority = parent.getPriority(); // 設置contextClassLoader if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; // 設置inheritedAccessControlContext this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); // 設置inheritableThreadLocals 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(); }
能夠看到最終仍是調用毫秒的native sleep方法,只是對納秒進行了處理
public static void sleep(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } sleep(millis); }
不支持clone方法,能夠理解,克隆的線程是沒有意義的,只能重建
/** * Throws CloneNotSupportedException as a Thread can not be meaningfully * cloned. Construct a new Thread instead. * * @throws CloneNotSupportedException * always */ @Override protected Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); }
在註釋上說明了:start的方法調用會使得線程開始執行,JVM會調用此線程的run()方法。結果就是兩個線程併發執行:當前線程(調用start方法的線程)和另外一個線程(也就是新的線程,執行run方法的線程)。
start方法添加了synchronized同步鎖,防止併發問題,同時檢查設置狀態,避免了屢次啓動,這也就是咱們程序裏調用的線程啓動的方法入口
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ // 建立線程時設置0狀態(初始態),這裏判斷非0直接拋錯 if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ // 添加線程到所屬線程組 // 會進行一些處理,好比記錄當前線程到線程數組中,啓動線程數+1,未啓動線程數-1 group.add(this); // 啓動狀態 boolean started = false; try { // 調用native方法啓動 start0(); // 成功啓動設置標識 started = true; } finally { try { if (!started) { // 未啓動成功則線程組要進行處理 // 從線程數組中移除當前線程,同時啓動線程數-1,未啓動線程數+1 // 和上面add是對應的 group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } }
重寫run也很簡單清晰,就是若是有runnable對象,則調用咱們傳入的runnable對象的run方法,不然什麼都不執行
@Override public void run() { if (target != null) { target.run(); } }
此方法由系統調用,用於在一個線程退出前作一些清理工做,爲了釋放資源將變量都置空操做
/** * This method is called by the system to give a Thread * a chance to clean up before it actually exits. */ private void exit() { if (group != null) { // 通知線程組線程已經終止了,從線程組中移除終止的線程 // 同時線程組自身進行一些處理 group.threadTerminated(this); group = null; } /* Aggressively null out all reference fields: see bug 4006245 */ // target置空以便儘快釋放資源 // 地址參考:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4006245 target = null; /* Speed the release of some of these resources */ // 其餘變量也同樣置空,儘快釋放資源 threadLocals = null; inheritableThreadLocals = null; inheritedAccessControlContext = null; blocker = null; uncaughtExceptionHandler = null; }
中斷當前線程,註釋上寫了不少內容,大概翻譯以下:
public void interrupt() { // 當前線程檢查是否容許中斷 if (this != Thread.currentThread()) checkAccess(); // 同步代碼塊完成blocker的interrupt調用 synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } // 調用native方法 interrupt0(); }
檢查當前線程是否被中斷,中斷狀態會被清理,換句話說若是兩次調用都能成功,則第二次返回結果爲false,除非第一次以後清理了中斷狀態,第二次調用前被再次中斷,而後再調用這個方法才返回true
咱們能夠看到isInterrupted方法那個boolean入參名是ClearInterrupted,也就能明白其含義了,是否清理中斷狀態
public static boolean interrupted() { return currentThread().isInterrupted(true); }
檢查當前線程是否被中斷,中斷狀態不會被處理
public boolean isInterrupted() { return isInterrupted(false); }
設置線程優先級,其中線程的優先級不能超過線程組的最高優先級
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); } }
同步方法設定線程名
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); } }
獲取當前線程所屬線程組的活躍線程數
public static int activeCount() { return currentThread().getThreadGroup().activeCount(); }
獲取線程所屬線程組中的全部活躍線程,拷貝到tarray中,註釋上推薦只用在debug和監聽上
public static int enumerate(Thread tarray[]) { return currentThread().getThreadGroup().enumerate(tarray); }
最多等待millis毫秒當前線程執行,若是爲0則表示一直等待直到執行結束
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { // 爲循環調用isAlive執行wait while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } // 等待delay毫秒 wait(delay); // 若是等待中間被喚醒則繼續循環等待剩下的時間 now = System.currentTimeMillis() - base; } } } // 納秒處理 public final synchronized void join(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } join(millis); } // 至關於一直等待 public final void join() throws InterruptedException { join(0); }
打印當前線程的標準error流堆棧信息,只用於debug
public static void dumpStack() { new Exception("Stack trace").printStackTrace(); }
標記線程是守護線程仍是用戶線程,當jvm中只有守護線程時將中止運行,此方法只能線程啓動前設置
public final void setDaemon(boolean on) { checkAccess(); if (isAlive()) { throw new IllegalThreadStateException(); } daemon = on; }
咱們能夠經過toString看到線程的幾個屬性被打印出來了:name,優先級,組名
public String toString() { ThreadGroup group = getThreadGroup(); if (group != null) { return "Thread[" + getName() + "," + getPriority() + "," + group.getName() + "]"; } else { return "Thread[" + getName() + "," + getPriority() + "," + "" + "]"; } }
獲取線程的狀態
public State getState() { // get current thread state return sun.misc.VM.toThreadState(threadStatus); }
Thread基本的使用方法初學者應該都是瞭解的,上一篇文章中我也說明了其兩種實現方式,咱們下面驗證下線程使用中的部分問題
1.同一個Thread線程能不能屢次啓動?
Thread test = new Thread(new Runnable() { @Override public void run() { System.out.println("test"); } }); test.start(); test.start(); test.start();
結果:
很明顯答案是不能,咱們能夠看到第二次啓動就報錯了,直接看堆棧信息就能看到是由於threadStatus != 0,也就是說線程在一次使用生命週期後,狀態不會被重置爲0,也就是初始態,只能被使用一次
2.線程被中斷後會不會當即中止?
非阻塞中的線程中斷:
Thread test = new Thread(new Runnable() { @Override public void run() { while(true){ if(Thread.currentThread().isInterrupted()){ System.out.println("Interrupted"); } else{ System.out.println("NoInterrupted"); } } } }); test.start(); Thread.sleep(2000); test.interrupt();
對於非阻塞中的線程,能夠運行上面例子看到線程中斷狀態確實改變了,可是線程還在執行,沒有終止,由於調用了interrupt以後咱們只是給了test線程一箇中斷信號,改變了中斷狀態而已
阻塞中的線程中斷:
Thread test = new Thread(new Runnable() { @Override public void run() { while(true){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } if(Thread.currentThread().isInterrupted()){ System.out.println("Interrupted"); } else{ System.out.println("NoInterrupted"); } } } }); test.start(); Thread.sleep(1000); test.interrupt();
結果:
對於阻塞中的線程,運行上面例子看到線程中斷狀態未改變,在sleep的catch處拋錯了,能夠取看下我上面對interrupt方法的說明,其中第二點說明了處於阻塞狀態的線程進行中斷會出現什麼結果?中斷狀態將被清除,同時會收到中斷異常InterruptedException,和上圖中的結果是一致的,由於咱們捕獲了,因此test線程未受影響繼續執行
從上面兩個簡單例子咱們應該明白,線程被中斷後不會當即中止,當咱們須要中止線程時,直接調用interrupt是沒用的,咱們須要本身在代碼裏進行處理
固然interrupt還有其餘兩種異常,這裏須要根據狀況進行處理,使用到時能夠本身思考下中斷的處理,這裏再也不詳述
3.join方法是怎麼回事?
private static AtomicInteger i = new AtomicInteger(1); public static void main(String[] args) throws InterruptedException { Thread test = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程test中:" + i.addAndGet(1)); } }); test.start(); test.join(); System.out.println("線程main中:" + i.addAndGet(1)); }
咱們可使用join來先完成test線程的執行,test線程執行完畢以後才進行main線程的處理,使之處理上變的有序
至此,關於Thread的部分已經基本瞭解完畢,其中最重要的仍是對於線程狀態轉換的理解,明白了線程處於的狀態時才能更好的對其進行處理,好比上面例子中的阻塞態下的線程調用中斷方法時會出現什麼現象?咱們編程過程當中須要考慮到,再好比,咱們如何中止一個while循環的線程,我想做爲技術人員都應該多思考下吧。思考的多了,理解的也就深入了
以上內容若有問題歡迎指出,筆者驗證後將及時修正,謝謝