本文主要结合 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 并发编程的艺术》,有兴趣的也能够找书来看一下;