JDK源码那些事儿之浅析Thread上篇
JAVA中多线程的操作对于初学者而言是比较难理解的,其实联想到底层操作系统时我们可能会稍微明白些,对于程序而言最终都是硬件上运行二进制指令,然而,这些又太过底层,今天来看一下JAVA中的线程,浅析JDK源码中的Thread类,之后能帮助我们更好的处理线程问题
前言
JDK版本号:1.8.0_171
在Thread注释中可以看到大佬对其进行的解释:
Thread就是程序中一个线程的执行.JVM允许一个应用中多个线程并发执行
每个线程都有优先级.高优先级线程优先于低优先级线程执行
每个线程都可以(不可以)被标记为守护线程
当线程中的run()方法代码里面又创建了一个新的线程对象时,新创建的线程优先级和父线程优先级一样
当且仅当父线程为守护线程时,新创建的线程才会是守护线程当JVM启动时,通常会有唯一的一个非守护线程(这一线程用于调用指定类的main()方法)
JVM会持续执行线程直到下面某一个情况发生为止:
1.类运行时exit()方法被调用且安全机制允许此exit()方法的调用.
2.所有非守护类型的线程均已经终止,或者run()方法调用返回或者在run()方法外部抛出了一些可传播性的异常.
可以联想下JVM的启动过程,从main方法启动,可以自己写代码查看下线程情况
线程实现
Thread注释类上清楚的写明了线程的两种实现方式:
- 定义一个继承Thread类的子类,子类可覆写父类的run()方法
- 实现Runnable接口
示例如下,相信各位读者应该非常熟悉了
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
先看下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内部实现了一些接口和类,下面来一一进行说明
UncaughtExceptionHandler
未捕获异常处理器,在线程由于未捕获的异常终止时,JVM会进行一些处理,处理流程如下:
- JVM调用终止线程的getUncaughtExceptionHandler方法获取终止线程的uncaughtExceptionHandler
- 非null则调用uncaughtExceptionHandler的uncaughtException方法,同时将此终止线程和其异常作为参数传入
- null则找到终止线程所在的最上级线程组,调用其uncaughtException方法,同时将此终止线程和其异常作为参数传入
- 调用Thread.getDefaultUncaughtExceptionHandler获取handle,非空则调用其uncaughtException方法,空则判断调用e.printStackTrace(System.err)处理
整个调用处理过程可参考下列源码部分:
// 内部异常处理接口
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中处理,这里就先了解下即可
Caches/WeakClassKey
我们可以看到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();
}
State
线程状态枚举类,表明线程处于生命周期中的哪个阶段,线程在任意时刻只会处于下列其中一种状态,只反映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种状态,上面枚举类也已经说明了,这里稍微详细点说明下每种状态的含义:
初始态(NEW):
- 创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态
运行态(RUNNABLE):
运行态在Java中包括就绪态和运行态
1.就绪态:
- 该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行
- 所有就绪态的线程存放在就绪队列中
2.运行态:
- 获得CPU执行权,正在执行的线程
- 由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一个运行态的线程
阻塞态(BLOCKED)
- 当一条正在执行的线程请求某一资源失败时,就会进入阻塞态
- 而在Java中,阻塞态专指请求锁失败时进入的状态
- 由一个阻塞队列存放所有阻塞态的线程。处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行
等待态(WAITING)
- 当前线程中调用wait、join、park函数时,当前线程就会进入等待态
- 也有一个等待队列存放所有等待态的线程
- 线程处于等待态表示它需要等待其他线程的指示才能继续运行
- 进入等待态的线程会释放CPU执行权,并释放资源(如:锁)
超时等待态(TIMED_WAITING)
- 当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态
- 它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后被其他线程唤醒或超时自动唤醒
- 进入该状态后释放CPU执行权和占有的资源,其中wait()方法会释放CPU执行权和占有的锁,sleep(long)方法仅释放CPU使用权,锁仍然占用
- 与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁
终止态(TERMINATED)
- 线程执行结束后的状态
其中有几点需要注意的:
- yield方法仅释放CPU执行权,锁仍然占用,线程会被放入就绪队列,会在短时间内再次执行
- wait和notify必须配套使用,即必须使用同一把锁调用
- wait和notify必须放在一个同步块中调用,wait和notify的对象必须是他们所处同步块的锁对象
总结
以上对Thread进行了简单的介绍说明,对于Thread的状态转换需要多理解理解,写些代码debug或者通过jdk工具观察下jvm的线程状态还是很有必要的
以上内容如有问题欢迎指出,笔者验证后将及时修正,谢谢
参考
- Java线程中wait状态和block状态的区别? (https://www.zhihu.com/question/27654579)
JDK源码那些事儿之浅析Thread上篇的更多相关文章
- JDK源码那些事儿之并发ConcurrentHashMap上篇
前面已经说明了HashMap以及红黑树的一些基本知识,对JDK8的HashMap也有了一定的了解,本篇就开始看看并发包下的ConcurrentHashMap,说实话,还是比较复杂的,笔者在这里也不会过 ...
- JDK源码那些事儿之红黑树基础下篇
说到HashMap,就一定要说到红黑树,红黑树作为一种自平衡二叉查找树,是一种用途较广的数据结构,在jdk1.8中使用红黑树提升HashMap的性能,今天就来说一说红黑树,上一讲已经给出插入平衡的调整 ...
- JDK源码那些事儿之ConcurrentLinkedDeque
非阻塞队列ConcurrentLinkedQueue我们已经了解过了,既然是Queue,那么是否有其双端队列实现呢?答案是肯定的,今天就继续说一说非阻塞双端队列实现ConcurrentLinkedDe ...
- JDK源码那些事儿之ConcurrentLinkedQueue
阻塞队列的实现前面已经讲解完毕,今天我们继续了解源码中非阻塞队列的实现,接下来就看一看ConcurrentLinkedQueue非阻塞队列是怎么完成操作的 前言 JDK版本号:1.8.0_171 Co ...
- JDK源码那些事儿之LinkedTransferQueue
在JDK8的阻塞队列实现中还有两个未进行说明,今天继续对其中的一个阻塞队列LinkedTransferQueue进行源码分析,如果之前的队列分析已经让你对阻塞队列有了一定的了解,相信本文要讲解的Lin ...
- JDK源码那些事儿之DelayQueue
作为阻塞队列的一员,DelayQueue(延迟队列)由于其特殊含义而使用在特定的场景之中,主要在于Delay这个词上,那么其内部是如何实现的呢?今天一起通过DelayQueue的源码来看一看其是如何完 ...
- JDK源码那些事儿之SynchronousQueue上篇
今天继续来讲解阻塞队列,一个比较特殊的阻塞队列SynchronousQueue,通过Executors框架提供的线程池cachedThreadPool中我们可以看到其被使用作为可缓存线程池的队列实现, ...
- JDK源码那些事儿之PriorityBlockingQueue
今天继续说一说阻塞队列的实现,今天的主角就是优先级阻塞队列PriorityBlockingQueue,从命名上看觉得应该是有序的,毕竟是优先级队列,那么实际上是什么情况,我们一起看下其内部实现,提前说 ...
- JDK源码那些事儿之LinkedBlockingQueue
今天继续讲解阻塞队列,涉及到了常用线程池的其中一个队列LinkedBlockingQueue,从类命名部分我们就可以看出其用意,队列中很多方法名是通用的,只是每个队列内部实现不同,毕竟实现的都是同一个 ...
随机推荐
- TIJ——Chapter Ten:Inner Classes
先提纲挈领地来个总结: 内部类(Inner Class)有四种: member inner class,即成员内部类.可以访问外部类所有方法与成员变量.生成成员内部类对象的方法:OuterClass. ...
- 阿里靠什么支撑 EB 级计算力?
作者 关涛 阿里云智能事业群 研究员 导读:MaxCompute 是阿里EB级计算平台,经过十年磨砺,它成为阿里巴巴集团数据中台的计算核心和阿里云大数据的基础服务.去年MaxCompute 做了哪些工 ...
- jq 监听返回事件
<script> $(document).ready(function(e) { var counter = 0; if (window.hi ...
- 《C语言深度解剖》学习笔记之预处理
第3章 预处理 1.下面两行代码都是错的.因为注释先于预处理指令被处理,当这两行被展开成“//……”和“/*……*/”时,注释已处理完毕,所以出现错误 #define BSC // #define B ...
- oracle函数 current_timestamp
[功能]:以timestamp with time zone数据类型返回当前会话时区中的当前日期 [参数]:没有参数,没有括号 [返回]:日期 [示例]select current_timestamp ...
- 开发板ping通虚拟机与主机
刚因为虚拟机与主机没法互相ping通的事情,奋战到将近凌晨一点.现在把这个过程总结一下,以方便后加入该行业的广大IT精英. VMWare提供了三种工作模式:bridged(桥接模式).NAT(网络地址 ...
- day5_python之hashlib模块
用来校验文本内容hash:一种算法 ,3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法三个特点:1.内容相同则h ...
- cesium 基础
scaleByDistance : new Cesium.NearFarScalar(1.5e2, 1.5, 8.0e6, 0.0),--(近值,近端放大率,远值,远端放大率) 给定距离视点的近值和远 ...
- python基础之包的导入
包的导入 python是一门灵活性的语言 ,也可以说python是一门胶水语言,顾名思义,就是可一导入各类的包, python的包可是说是所有语言中最多的.当然导入包大部分是为了更方便,更简便,效率更 ...
- 二叉堆&&左偏堆 代码实现
今天打算学习左偏堆,可是想起来自己二叉堆都没有看懂,于是就跑去回顾二叉堆了.发现以前看不懂的二叉堆,今天看起来特简单,随手就写好了一个堆了. 简单的说一下我对二叉堆操作的理解.我不从底层函数说上去,相 ...