JAVA中多线程的操作对于初学者而言是比较难理解的,其实联想到底层操作系统时我们可能会稍微明白些,对于程序而言最终都是硬件上运行二进制指令,然而,这些又太过底层,今天来看一下JAVA中的线程,浅析JDK源码中的Thread类,之后能帮助我们更好的处理线程问题
JDK版本号:1.8.0_171
在Thread注释中可以看到大佬对其进行的解释:
每个线程都有优先级.高优先级线程优先于低优先级线程执行
每个线程都可以(不可以)被标记为守护线程
当线程中的run()方法代码里面又创建了一个新的线程对象时,新创建的线程优先级和父线程优先级一样
当且仅当父线程为守护线程时,新创建的线程才会是守护线程
当JVM启动时,通常会有唯一的一个非守护线程(这一线程用于调用指定类的main()方法)
JVM会持续执行线程直到下面某一个情况发生为止:
1.类运行时exit()方法被调用且安全机制允许此exit()方法的调用.
2.所有非守护类型的线程均已经终止,或者run()方法调用返回或者在run()方法外部抛出了一些可传播性的异常.
可以联想下JVM的启动过程,从main方法启动,可以自己写代码查看下线程情况
Thread注释类上清楚的写明了线程的两种实现方式:
示例如下,相信各位读者应该非常熟悉了
public static void main(String[] args) { // 1.继承Thread类 ExtendsThread extendsThread = new ExtendsThread("test1"); extendsThread.start(); // 2.实现Runnable接口 ImplementsRunnable implementsRunnable = new ImplementsRunnable("test2"); // 实现Runnable接口的类不能单独使用,最终还是要依赖Thread Thread implementsRunnableThread = new Thread(implementsRunnable); implementsRunnableThread.start(); } static class ExtendsThread extends Thread{ private String name; public ExtendsThread(String name) { this.name = name; } @Override public void run() { System.out.println("ExtendsThread is " + name); } } static class ImplementsRunnable implements Runnable{ private String name; public ImplementsRunnable(String name) { this.name = name; } @Override public void run() { System.out.println("ImplementsRunnable is " + name); } }
从上面的实现中我们能看到最终都需要实现Runnable接口,也就是实现run方法,Thread本身也是实现了Runnable接口(可参考源码部分)
先看下Runnable接口实现:
public interface Runnable { public abstract void run(); }
非常简单,就一个run方法,也就意味着我们只要将自定义代码逻辑在run方法中实现即可,我们在平常的线程实现中最简单的使用方式也就是如上面线程实现的方式,所以我们最终需要了解的部分还是在于Thread,接下来就看看本文的重点Thread
public class Thread implements Runnable
先了解下static块的部分,这里在类首次加载时执行,主要作用就是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦
通俗点说就是我们在之后调用native方法时,jvm会直接去调用本地系统方法完成操作,不会再去查找这个方法在哪,再去链接等等,相当于先进行注册,之后就直接操作使用,因为涉及到底层c语言,这里不过多深入解释
/* Make sure registerNatives is the first thing <clinit> does. */ private static native void registerNatives(); static { registerNatives(); }
Thread内部实现了一些接口和类,下面来一一进行说明
未捕获异常处理器,在线程由于未捕获的异常终止时,JVM会进行一些处理,处理流程如下:
整个调用处理过程可参考下列源码部分:
// 内部异常处理接口 public interface UncaughtExceptionHandler { void uncaughtException(Thread t, Throwable e); } public UncaughtExceptionHandler getUncaughtExceptionHandler() { // 返回handle本身或线程组(实现了这个接口) return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; } // ThreadGroup中代码逻辑 public void uncaughtException(Thread t, Throwable e) { if (parent != null) { parent.uncaughtException(t, e); } else { Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); if (ueh != null) { // 默认handle非空则调用 ueh.uncaughtException(t, e); } else if (!(e instanceof ThreadDeath)) { // 未设置基本都是这里进行打印堆栈信息 System.err.print("Exception in thread /"" + t.getName() + "/" "); e.printStackTrace(System.err); } } }
可参考下列测试代码理解:
public static void main(String[] args) { // 实现UncaughtExceptionHandler接口 Thread.UncaughtExceptionHandler handler = (t, e) -> { System.out.println("test:" + t.getName()); e.printStackTrace(); }; // 设置默认UncaughtExceptionHandler // Thread.setDefaultUncaughtExceptionHandler(handler); ExtendsThread extendsThread = new ExtendsThread("test"); // 设置UncaughtExceptionHandler extendsThread.setUncaughtExceptionHandler(handler); extendsThread.start(); } static class ExtendsThread extends Thread { private String name; public ExtendsThread(String name) { this.name = name; } @Override public void run() { int a = 1 / 0; System.out.println(a); } }
从这个接口我们可以知道对于线程异常停止我们其实是可以进行一些后续操作的,通过实现UncaughtExceptionHandler接口来进行线程异常后续处理操作,不过我还没见过有什么源码中用过,一般都是直接catch中处理,这里就先了解下即可
我们可以看到WeakClassKey这个内部类继承了WeakReference,而WeakClassKey被Caches所使用,从名字我们也能明白其部分含义,本地缓存,WeakClassKey是弱引用相关类,至于弱引用的使用大家可以自行Google,这里不多说,如果你看过《深入理解Java虚拟机》,应该多少有点了解
subclassAudits提供了一个哈希表缓存,该缓存的键类型为java.lang.Thread.WeakClassKey,注意看它的值类型是一个java.lang.Boolean类型的,从其代码注释可以知道这个哈希表缓存中保存的是所有子类的代码执行安全性检测结果
subclassAuditsQueue定义了一个Queue队列,保存已经审核过的子类弱引用
/** cache of subclass security audit results */ /* Replace with ConcurrentReferenceHashMap when/if it appears in a future * release */ private static class Caches { /** cache of subclass security audit results */ // 缓存安全检查结果 static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits = new ConcurrentHashMap<>(); // 队列 /** queue for WeakReferences to audited subclasses */ static final ReferenceQueue<Class<?>> subclassAuditsQueue = new ReferenceQueue<>(); } /** * Weak key for Class objects. **/ static class WeakClassKey extends WeakReference<Class<?>> { /** * saved value of the referent's identity hash code, to maintain * a consistent hash code after the referent has been cleared */ private final int hash; /** * Create a new WeakClassKey to the given object, registered * with a queue. */ WeakClassKey(Class<?> cl, ReferenceQueue<Class<?>> refQueue) { super(cl, refQueue); hash = System.identityHashCode(cl); } /** * Returns the identity hash code of the original referent. */ @Override public int hashCode() { return hash; } /** * Returns true if the given object is this identical * WeakClassKey instance, or, if this object's referent has not * been cleared, if the given object is another WeakClassKey * instance with the identical non-null referent as this one. */ @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj instanceof WeakClassKey) { Object referent = get(); return (referent != null) && (referent == ((WeakClassKey) obj).get()); } else { return false; } } }
在源码中我们可以看到主要在 isCCLOverridden
中使用到了,这里直接看下这个方法的源码,注释上也写明了,验证实例(可能是子类)没有违反安全约束可以被构建,子类不能重写安全相关的非final方法,否则需要检查enableContextClassLoaderOverride运行权,主要是进行安全检查的,简单理解就好
/** * Verifies that this (possibly subclass) instance can be constructed * without violating security constraints: the subclass must not override * security-sensitive non-final methods, or else the * "enableContextClassLoaderOverride" RuntimePermission is checked. */ private static boolean isCCLOverridden(Class<?> cl) { if (cl == Thread.class) return false; processQueue(Caches.subclassAuditsQueue, Caches.subclassAudits); // 生成key WeakClassKey key = new WeakClassKey(cl, Caches.subclassAuditsQueue); // 从缓存查找 Boolean result = Caches.subclassAudits.get(key); if (result == null) { result = Boolean.valueOf(auditSubclass(cl)); Caches.subclassAudits.putIfAbsent(key, result); } // 返回结果 return result.booleanValue(); }
线程状态枚举类,表明线程处于生命周期中的哪个阶段,线程在任意时刻只会处于下列其中一种状态,只反映JVM中的线程状态,不是反映操作系统线程状态
public enum State { /** * 初始态 * 线程创建完毕还未启动,未调用start方法 */ NEW, /** * 包含两种状态 * 1.就绪态 * 2.运行态 */ RUNNABLE, /** * 阻塞态 * synchronized会导致线程进入blocked状态 */ BLOCKED, /** * * 等待态 * 3种情况调用后会导致线程处于这个状态: * 1.Object.wait * 2.Thread.join * 3.LockSupport.park * * 等待态的线程会等待其他线程执行特定的操作 * * 例如一个线程调用了Object.wait之后进入等待态,另一个线程调用Object.notify或Object.notifyAll可以将其唤醒,被唤醒的线程需要获取对象的锁才能恢复执行 * 调用Thread.join会等待指定的线程终止 */ WAITING, /** * * 超时等待态 * 线程等待指定的时间再执行 * 5种情况调用后会导致线程处于这个状态: * 1.Thread.sleep * 2.Object.wait 传入等待时长 * 3.Thread.join 传入等待时长 * 4.LockSupport.parkNanos * 5.LockSupport.parkUntil */ TIMED_WAITING, /** * 终止态 * 线程执行完毕 */ TERMINATED; }
线程状态转化可参考下图理解
在Java中线程分为6种状态,上面枚举类也已经说明了,这里稍微详细点说明下每种状态的含义:
运行态在Java中包括就绪态和运行态
1.就绪态:
2.运行态:
其中有几点需要注意的:
以上对Thread进行了简单的介绍说明,对于Thread的状态转换需要多理解理解,写些代码debug或者通过jdk工具观察下jvm的线程状态还是很有必要的
以上内容如有问题欢迎指出,笔者验证后将及时修正,谢谢