Thread类源码解析
1.基本架构
Thread类涉及较多基本概念,理解基本概念是读源码的前提。
类注释
- 每个线程都有优先级,高优先级的线程可能会优先执行
- 父线程创建子线程后,子线程的优先级、是否为守护线程等属性与父线程是一致的
- JVM启动时,会将主线程执行,主线程是非守护线程。JVM在如下几种情况下才会停止,否则将持续运行
1)类运行时调用exit方法,且安全管理器已允许进行退出操作
2)全部非守护线程都已停止运行,无论是正常执行完成或是运行出错 - 每个线程都有名字,多个线程可以具有相同的名字。如果构造方法中没有传入指定名称,则会自动生成
任务状态
目前比较多的是如下两个版本,
-
版本1
注意在RUNNABLE和RUNNING之间是双向箭头,当线程切换时,原本正在执行的线程从RUNNING状态转移至RUNNABLE状态。 -
版本2(目前应用更多)
任务状态说明,
1)NEW
线程对象刚创建完成,但是还没有运行。在new Thread()
之后没有调用start方法之前的状态。
2)RUNNABLE
父线程调用子线程的start方法后,该线程对象才在JVM中挂号(JVM知道该线程要搞事)。版本1中的解释是此时处于可运行状态,但是还没有被CPU选中。版本2中则是调用start方法后,无论是否被CPU选中都称为RUNNABLE状态。
3)RUNNING
只出现在版本1中,一旦被CPU选中,才能执行线程的run方法,此时才算真正意义上的运行。版本2中RUNNING状态的转化
- 转入RUNNABLE状态
当CPU切换到其他线程时,正在运行的线程状态变为RUNNABLE,因为run方法没有在被执行 - 转入BLOCKED状态
正在执行的线程调用了wait或sleep方法时,就会进入BLOCKED状态。区别是wait方法会放下当前CPU使用权,而sleep方法继续占用CPU - 转入TERMINATED状态
run方法执行完毕或意外终止。或者正在被执行的线程被调用了stop方法
4)BLOCKED
阻塞,由RUNNABLE或RUNNING进入BLOCKED的原因有,
- 线程等待获得 monitor lock 锁,比如在等待进入 synchronized 修饰的代码块或方法时,会从 RUNNABLE 变成 BLOCKED
- 正在执行的线程调用了wait或sleep方法后就会进入BLOCKED状态(版本1中的解释)。在版本2中,执行了wait方法后,会进入到WAITING或TIMED_WAITING状态。
由BLOCKED进入RUNNABLE状态的时机,
- 阻塞操作结束,等待CPU再次选中的这段过程
- 阻塞过程被打断,如其他线程调用了interrupt方法
- sleep方法设定的休眠时间结束后会回到RUNNABLE状态
- 由于wait操作进入BLOCKED状态的线程,其他线程发出notify或notifyAll的信号时,会唤醒进入到RUNNABLE状态
线程在BLOCKED状态下如果被调用了stop方法时会转入TERMINATED状态。
5)TERMINATED
TERMINATED 状态意味着线程的生命周期已经走完。这是线程的终止状态。此状态的线程不会再转化为其它任何状态。
处于 RUNNING 或者 BLOCKED 状态的线程都有可能变为 TERMINATED 状态,但原因是类似的,如下:
- 线程运行正常结束
- 线程运行异常终止
- JVM意外停止
6)WAITING和TIMED_WAITING
版本2中,遇到Object#wait
、Thread#join
、LockSupport#park
这些方法时,线程会等待另一个线程完成特定动作之后才结束等待。二者的区别是TIMED_WAITING有设定等待时间。
注意,这几种状态并不是任务所有的状态,只是在 Java 源码中列举出了几种状态, Java 线程的处理方法都是围绕这几种状态的。
优先级
优先级代表线程执行的机会的大小,优先级高的可能先执行,低的可能后执行,在 Java 源码中,优先级从低到高分别是 1 到 10,线程默认 new 出来的优先级都是 5,源码如下,
// 最低优先级
public final static int MIN_PRIORITY = 1;
// 普通优先级,也是默认的
public final static int NORM_PRIORITY = 5;
// 最大优先级
public final static int MAX_PRIORITY = 10;
请注意优先级大仅仅是概率会更大,并不意味着就一定能够先于优先级低的获取。和摇车牌号一个道理,即使现在中签概率是标准的数倍,但摇中依然摇摇无期。而身边却时不时的出现第一次摇号就中的人。如果在 CPU 比较空闲的时候,那么优先级就没有用了。
守护线程
默认创建的线程都是非守护线程。
创建守护线程时,需要将 Thread 的 daemon 属性设置成 true,守护线程的优先级很低,当 JVM 退出时,是不关心有无守护线程的,即使还有很多守护线程,JVM 仍然会退出。
守护线程类似于餐厅清洁员,一直在默默地做打扫卫生的工作。这个工作相对独立,不需要和别的角色有什么交互。而当其他所有人都不工作了,也就没有工作的必要了,因为不会有新的垃圾产生。那么可以下班,餐厅也就关门了。
ClassLoader
ClassLoader可以简单理解成类加载器,就是把类从文件、二进制数组、URL 等位置加载成可运行 Class。
2. 多线程实现的两种方式
无返回值的线程初始化方式有两种,
继承Thread类及start方法源码
class MyThread extends Thread{
@Override
public void run() {
log.info(Thread.currentThread().getName());
}
}
@Test
// 调用 start 方法即可,会自动调用到 run 方法的
public void extendThreadInit(){
new MyThread().start();
}
start方法源码如下,
public synchronized void start() {
// 如果没有初始化,抛异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
// started 是个标识符,动作发生之前标识符是 false,发生完成之后变成 true
boolean started = false;
try {
// 这里会创建一个新的线程,执行完成之后,新的线程已经在运行了,既 target 的内容已经在运行了
start0();
// 这里执行的还是主线程
started = true;
} finally {
try {
// 如果失败,把线程从线程组中删除
if (!started) {
group.threadStartFailed(this);
}
// Throwable 可以捕捉一些 Exception 捕捉不到的异常,比如说子线程抛出的异常
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
// 开启新线程使用的是 native 方法
private native void start0();
start方法主要逻辑如下,
- 检查线程状态,判断是否可启动
- 将子线程加入到线程组中
- 调用start0方法
start方法并不调用run方法,而是调用start0方法。start0方法是native方法,也称为JNI(Java Native Interface)方法。JNI方法是java和其它语言交互的方式。同样也是java代码和虚拟机交互的方式,虚拟机就是由C++和汇编所编写。
start0会进入JVM执行,run方法的执行在源码中无法找到,但是在start方法的注释中有这样一句话,
the Java Virtual Machine* calls the
run
method of this thread.
所以run方法的调用过程如下,
实现Runnable及run方法源码
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
log.info("{} begin run",Thread.currentThread().getName());
}
});
// 开一个子线程去执行
thread.start();
// 不会新起线程,是在当前主线程上继续运行
thread.run();
这种就是实现 Runnable 的接口,并作为 Thread 构造器的入参。
调用时使用了两种方式,可以根据情况选择使用 start 或 run 方法,
- 使用
start
会开启子线程来执行 run 里面的内容 - 使用
run
方法执行的还是主线程
run方法源码如下,
public void run() {
if (target != null) {
target.run();
}
}
将Runnable对象传入Thread类中,调用run方法的过程,
Thread和Runnable之间的关系
看若兄弟,实为父子。Thread类的定义如下,
public class Thread implements Runnable
Thread实现了Runnable接口。
Thread类的run方法上有@Override
注解。所以继承thread类实现多线程,其实也相当于是实现runnable接口的run方法。此时,不需要再传入一个Runnable类去启动。它自己已具备了thread的功能,自己就可以运转起来。
Thread类也实现了Runnable接口,那么Thread子类对象也可以传入另外的Thread对象,让其执行自己的run方法。
3.初始化方法
源码关键部分如下,
// 无参构造器,线程名字自动生成
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
// g 代表线程组,线程组可以对组内的线程进行批量的操作
// target 是Runnable对象
// name 线程的名字,自定义或自动生成
// stackSize 可以设置堆栈的大小
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
// 创建Thread对象的线程作为父线程
Thread parent = currentThread();
this.group = g;
// 子线程会继承父线程的守护属性
this.daemon = parent.isDaemon();
// 子线程继承父线程的优先级属性
this.priority = parent.getPriority();
// classLoader
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);
// 当父线程的 inheritableThreadLocals 的属性值不为空时
// 会把 inheritableThreadLocals 里面的值全部传递给子线程
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
/* Set thread ID */
// 线程 id 自增
tid = nextThreadID();
}