本文主要結合 java.lang.Thread
源碼,梳理 Java 線程的總體脈絡;java
對於 Java 中的線程主要是依賴於系統的 API 實現的,這一點能夠從 java.lang.Thread
;源碼中關鍵的方法都是 native
方法看出,也能夠直接查看 OpenJDK 源碼看出來,這一點後面還會講到;對於 JDK1.8 而言,他的 Windows 版和 Linux 版使用的都是 1:1 線程模型,即系統內核線程和輕量級進程的比是 1:1
;編程
如圖所示:安全
優勢:多線程
缺點:併發
Java 線程的整個生命週期可能會經歷如下5中狀態,如圖所示:jvm
/* Make sure registerNatives is the first thing <clinit> does. */ private static native void registerNatives(); static { registerNatives(); }
這段代碼在不少地方都出現過,好比:ide
java.lang.System java.lang.Object java.lang.Class
其做用就是在使用 JNI 時須要向 JVM 註冊,其方法名默認爲 Java_<fully qualified class name>_method
;可是若是以爲這樣的名字太長,這是就可使用 registerNatives()
向 JVM 註冊任意的函數名;函數
Thread 中的 native 方法有:源碼分析
private native void start0(); private native void stop0(Object o); public final native boolean isAlive(); private native void suspend0(); private native void resume0(); private native void setPriority0(int newPriority); public static native void yield(); public static native void sleep(long millis) throws InterruptedException; public static native Thread currentThread(); public native int countStackFrames(); private native void interrupt0(); private native boolean isInterrupted(boolean ClearInterrupted); public static native boolean holdsLock(Object obj); private native static Thread[] getThreads(); private native static StackTraceElement[][] dumpThreads(Thread[] threads); private native void setNativeName(String name);
其對應 JVM 源碼this
// openjdk\jdk\src\share\native\java\lang\Thread.c static JNINativeMethod methods[] = { {"start0", "()V", (void *)&JVM_StartThread}, {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, {"isAlive", "()Z", (void *)&JVM_IsThreadAlive}, {"suspend0", "()V", (void *)&JVM_SuspendThread}, {"resume0", "()V", (void *)&JVM_ResumeThread}, {"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority}, {"yield", "()V", (void *)&JVM_Yield}, {"sleep", "(J)V", (void *)&JVM_Sleep}, {"currentThread", "()" THD, (void *)&JVM_CurrentThread}, {"countStackFrames", "()I", (void *)&JVM_CountStackFrames}, {"interrupt0", "()V", (void *)&JVM_Interrupt}, {"isInterrupted", "(Z)Z", (void *)&JVM_IsInterrupted}, {"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock}, {"getThreads", "()[" THD, (void *)&JVM_GetAllThreads}, {"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads}, {"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName}, };
其具體實現能夠查看
openjdk\hotspot\src\share\vm\prims\jvm.h openjdk\hotspot\src\share\vm\prims\jvm.cpp
public class Thread implements Runnable { private volatile String name; // 線程名稱,若是沒有指定,就經過 Thread-線程序列號 命名 private int priority; // 線程優先級,1-10 默認與父線程優先級相同(main 線程優先級爲 5) private boolean daemon = false; // 是不是守護線程 private Runnable target; // Runnable 對象 private ThreadGroup group; // 所屬線程組 ThreadLocal.ThreadLocalMap threadLocals = null; // 線程本地變量 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // 可繼承的線程本地變量 private long tid; // 線程 tid ... public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } 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); } 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(); } ... }
能夠看到左右的構造方法最終都會調用 init()
;並初始化所屬線程組、名字、 Runnable、棧大小等信息;整個過程至關於配置了一個線程工廠,此時只是初始化了全部的配置,線程尚未真正建立,固然資源一樣也尚未分配,只有在調用 start()
的時候線程纔會真正建立;
此外能夠看到線程建立過程當中會有不少的權限檢查,例如:
SecurityManager security = System.getSecurityManager(); if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } }
一般狀況下權限的檢查默認是沒有開啓的,因此 security
一直都是 null
;這裏須要在啓動 JVM 的時候指定 -Djava.security.manager
;固然也能夠指定特定的 SecurityManager
;可是在開啓的時候極可能會遇到相似:java.security.AccessControlException: access denied
;權限檢查失敗的錯誤;
此時能夠在 jre\lib\security\java.policy
中添加相應的權限;或者直接開啓全部權限 permission java.security.AllPermission;
// jre\lib\security\java.policy grant { permission java.lang.RuntimePermission "stopThread"; permission java.net.SocketPermission "localhost:0", "listen"; permission java.util.PropertyPermission "java.version", "read"; ... permission java.security.AllPermission; };
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { // } } } private native void start0();
能夠看到這是一個同步方法,而且同一個線程不能啓動兩次;這裏首先將線程加入對應的線程組,再真正建立線程,若是建立失敗就在線程組中標記;對應的這個 native
方法 start0
,的源碼一樣能夠查看 openjdk\hotspot\src\share\vm\prims\jvm.cpp
,這裏就不詳細介紹了;
private void exit() { if (group != null) { group.threadTerminated(this); group = null; } /* Aggressively null out all reference fields: see bug 4006245 */ target = null; /* Speed the release of some of these resources */ threadLocals = null; inheritableThreadLocals = null; inheritedAccessControlContext = null; blocker = null; uncaughtExceptionHandler = null; }
exit
方法則是由系統調用,在 Thread 銷燬前釋放資源;
在源碼裏面還有幾個棄用的方法:
public final void stop() { } // 中止線程 public final void suspend() { } // 暫停線程 public final void resume() { } // 恢復線程
其實全部的多線程問題,其本質都是線程之間的通信問題,也有的說是通信和同步兩個問題(線程間操做的順序);但我以爲同步仍然是線程之間經過某種方式進行通信,肯定各自執行的相對順序;因此仍然能夠算做是一種通信問題;這裏線程之間的通信問題能夠分紅兩種:
下面咱們將介紹和 Thread 類直接相關的幾種通信,關於鎖的部分以後的博客還會詳細介紹;
@Slf4j public class WaitNotify { private static boolean flag = true; private static final Object LOCK = new Object(); public static void main(String[] args) throws Exception { Thread waitThread = new Thread(new Wait(), "WaitThread"); waitThread.start(); TimeUnit.SECONDS.sleep(1); Thread notifyThread = new Thread(new Notify(), "NotifyThread"); notifyThread.start(); } private static class Wait implements Runnable { @Override public void run() { // 加鎖,擁有lock的Monitor synchronized (LOCK) { // 當條件不知足時,繼續wait,同時釋放了lock的鎖 while (flag) { try { log.info("flag is true. wait"); LOCK.wait(); } catch (InterruptedException e) { } } // 條件知足時,完成工做 log.info("flag is false. running"); } } } private static class Notify implements Runnable { @Override public void run() { // 加鎖,擁有lock的Monitor synchronized (LOCK) { // 獲取lock的鎖,而後進行通知,通知時不會釋放lock的鎖, // 直到當前線程釋放了lock後,WaitThread才能從wait方法中返回 log.info("hold lock. notify"); LOCK.notify(); flag = false; SleepUtils.second(5); } // 再次加鎖 synchronized (LOCK) { log.info("hold lock again. sleep"); SleepUtils.second(5); } } } }
// 打印:
[13 21:18:18,533 INFO ] [WaitThread] WaitNotify - flag is true. wait [13 21:18:19,533 INFO ] [NotifyThread] WaitNotify - hold lock. notify [13 21:18:24,535 INFO ] [NotifyThread] WaitNotify - hold lock again. sleep [13 21:18:29,536 INFO ] [WaitThread] WaitNotify - flag is false. running
@Slf4j public class Join { public static void main(String[] args) throws Exception { Thread previous = Thread.currentThread(); for (int i = 0; i < 5; i++) { // 每一個線程擁有前一個線程的引用,須要等待前一個線程終止,才能從等待中返回 Thread thread = new Thread(new Domino(previous), String.valueOf(i)); thread.start(); previous = thread; } TimeUnit.SECONDS.sleep(5); log.info("terminate."); } private static class Domino implements Runnable { private Thread thread; public Domino(Thread thread) { this.thread = thread; } @Override public void run() { try { thread.join(); } catch (InterruptedException e) { } log.info("terminate."); } } }
// 打印:
[13 21:27:27,573 INFO ] [main] Join - terminate. [13 21:27:27,574 INFO ] [0] Join - terminate. [13 21:27:27,574 INFO ] [1] Join - terminate. [13 21:27:27,574 INFO ] [2] Join - terminate. [13 21:27:27,574 INFO ] [3] Join - terminate. [13 21:27:27,574 INFO ] [4] Join - terminate.
以上 wait\notify、join
都比較簡單,你們直接看代碼應該就能理解;可是 interrupt 機制
則比較複雜一點,咱們先從源碼分析;
interrupt 方法:
private volatile Interruptible blocker; private final Object blockerLock = new Object(); // Set the blocker field; invoked via sun.misc.SharedSecrets from java.nio code void blockedOn(Interruptible b) { synchronized (blockerLock) { blocker = b; } } public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } interrupt0(); } private native void interrupt0();
在 Thread 的源碼上有詳細的註釋,如下我簡單翻譯:
java.lang.InterruptedException
;java.nio.channels.ClosedByInterruptException
;java.nio.channels.Selector.wakeup()
方法同樣;其中 Interruptible blocker
就是在 NIO
操做的時候經過 sun.misc.SharedSecrets
設置的(其效果同反射,可是不會生成其餘對象,也就是不會觸發 OOM);
interrupted 、isInterrupted 方法:
public static boolean interrupted() { return currentThread().isInterrupted(true); } public boolean isInterrupted() { return isInterrupted(false); } private native boolean isInterrupted(boolean ClearInterrupted);
能夠很清楚的看到他們都是經過 isInterrupted(boolean ClearInterrupted)
方法實現的,可是 interrupted
會清除中斷狀態,而 isInterrupted
則不會清除;
以上 interrupt 機制
就經過設置 interrupt flag
,查詢中斷狀態,以及中斷異常構成了一套完整的通信機制;也能夠看做是經過 interrupt flag
共享變量實現的,下面咱們簡單舉例:
@Slf4j public class Interrupted { public static void main(String[] args) throws Exception { // sleepThread不停的嘗試睡眠 Thread sleepThread = new Thread(new SleepRunner(), "SleepThread"); sleepThread.setDaemon(true); // busyThread不停的運行 Thread busyThread = new Thread(new BusyRunner(), "BusyThread"); busyThread.setDaemon(true); sleepThread.start(); busyThread.start(); // 休眠5秒,讓sleepThread和busyThread充分運行 TimeUnit.SECONDS.sleep(5); sleepThread.interrupt(); busyThread.interrupt(); log.info("SleepThread interrupted is {}", sleepThread.isInterrupted()); log.info("BusyThread interrupted is {}", busyThread.isInterrupted()); // 防止sleepThread和busyThread馬上退出 TimeUnit.SECONDS.sleep(5); log.info("exit"); } static class SleepRunner implements Runnable { @Override public void run() { try { while (true) { Thread.sleep(2000); } } catch (InterruptedException e) { log.error("SleepThread interrupted is {}", Thread.currentThread().isInterrupted()); Thread.currentThread().interrupt(); log.error("SleepThread interrupted is {}", Thread.currentThread().isInterrupted()); } log.info("exit"); } } static class BusyRunner implements Runnable { @Override public void run() { if (1 == 1) { while (true) { } } log.info("exit"); } } }
// 打印:
[14 10:20:55,269 INFO ] [main] Interrupted - SleepThread interrupted is false [14 10:20:55,269 ERROR] [SleepThread] Interrupted - SleepThread interrupted is false [14 10:20:55,270 INFO ] [main] Interrupted - BusyThread interrupted is true [14 10:20:55,270 ERROR] [SleepThread] Interrupted - SleepThread interrupted is true [14 10:20:55,271 INFO ] [SleepThread] Interrupted - exit [14 10:21:00,271 INFO ] [main] Interrupted - exit
從日誌中能夠看到:
InterruptedException
的時候會清楚中斷標記,因此這裏能夠再次設置中斷標記;固然以上只是簡單的舉例,中斷機制如何使用仍是要根據具體的業務邏輯來肯定;另外以上的實例代碼是出自《Java 併發編程的藝術》,有興趣的也能夠找書來看一下;