0%

Thread 源码解析

守护线程

每个线程可以被标记为守护线程,当创建新线程的线程是守护线程时,新线程也是守护线程(初始化时,see init),默认为非守护线程。与之对应的是用户线程。

daemon

守护线程的优先级很低,当 JVM 退出时,不会关心是否还存在守护线程。即使还存在守护线程,JVM 仍会退出。而用户线程如果还在运行,会阻止 JVM 进程退出。

当 JVM 启动时,通常会有一个非守护线程(通常是类的 main 方法)。当发生以下任一情况时 JVM 才会停止执行:

  1. Runtime 类的 exit 方法被调用(即终止当前运行的 Java 虚拟机),且安全管理器已允许退出操作的发生
  2. 所有不是守护线程的线程都已经死亡(调用 run 方法后返回 或 抛出异常)

有一个注意的点是,如果要调用 setDaemon 设置守护线程,必须在线程启动之前调用此方法,否则会抛出 IllegalThreadStateException。

setDaemon

场景

  1. 垃圾回收线程

  2. 一些监控工具
    当 JVM 需要退出时无需关注监控是否正在运行,直接退出不会对业务产生任何影响。

优先级

每个线程都有一个优先级,优先级高的线程优先于优先级低的线程执行。当创建一个新的 Thread 对象时,新线程的优先级最初设置为与创建线程的优先级相等(初始化时,see init)。

优先级最低 1,最高 10,默认 5。

priority

如果要让新线程的优先级与创建线程的优先级相等,则创建线程的 setPriority 应该在 new Thread() 之前调用;

如果只想改变新线程的优先级,则新线程的 setPriority 应该在 start() 之前调用。

setPriority

默认名称

默认名称为(构造方法中,see Constructor):”Thread-“ + 一个整数

该整数计算逻辑为:

nextThreadNum

所以第一个新线程默认名称为:Thread-0。

线程状态

NEW

尚未启动的线程处于 NEW 状态。

RUNNABLE

在 JVM 中执行的线程处于 RUNNABLE 状态。但它可能正在等待来自操作系统的其他资源。

BLOCKED

被阻塞等待监视器锁(monitor lock)的线程处于 BLOCKED 状态。处于阻塞状态的线程正在等待监视器锁进入同步 阻塞/方法 或在调用 Object#wait() 后重入同步 阻塞/方法。

WAITING

无限期等待另一个线程执行特定操作的线程处于 WAITING 状态。

调用以下方法之一:

  1. Object#wait()
  2. Thread#join()
  3. LockSupport#park()

例如,一个线程在一个对象上调用了 Object.wait() 方法,正在等待另一个线程在那个对象上调用 Object.notify() 方法或 Object.notifyAll() 方法;调用 Thread.join() 的线程正在等待指定的线程终止。

TIMED_WAITING

等待另一个线程执行操作直到指定等待时间的线程处于 TIMED_WAITING 状态。

调用以下方法之一:

  1. Thread#sleep
  2. Object#wait(long)
  3. Thread#join(long)
  4. LockSupport#parkNanos
  5. LockSupport#parkUntil

TERMINATED

已退出的线程处于 TERMINATED 状态。线程已完成执行。

状态流转图

status

两种方式创建新线程

继承 Thread 并重写 run 方法

demo:计算大于规定值的素数(质数)的线程

1
2
3
4
5
6
7
8
9
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
}
}

启动运行

1
2
PrimeThread p = new PrimeThread(143);
p.start();

实现 Runnable 接口并实现 run 方法

demo(同上):计算大于规定值的素数(质数)的线程

1
2
3
4
5
6
7
8
9
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
}
}

启动运行

1
2
PrimeRun p = new PrimeRun(143);
new Thread(p).start();

Other Source Code

Constructor

一共 9 个构造函数,其中有一个不是 public 的,也就是说,用户可用的构造函数一共有 8 个。

constructor

一个一个看:

constructor-1

constructor-2

该构造函数不是 public 的。

constructor-3

constructor-4

constructor-5

constructor-6

constructor-7

constructor-8

constructor-9

init

Constructor 可以看到,所有的构造函数都是调用了 init 方法。一共有两个 init 方法,其中一个是另一个的重载。

init-1

init-2

终于到了完整的 init 逻辑,其中有一些 Java Security 的代码可以不用过多关注。

init-3

start

中间会调用一个 native method start0()。

start

可以看出:如果多次调用 start 方法,会抛出 IllegalThreadStateException。

run

run 方法比较简单,其中的 target 是在 init 方法中赋值的 Runnable(如果有的话),如果 target 为 null 则什么也不做(这也是继承 Thread 后需要重写 run 方法的原因)。

run

sleep

sleep 有 2 个方法

一个参数的方法

是 native method。

sleep-1

两个参数的方法

根据注释是为了实现纳秒级别的睡眠。

sleep-2

但有意思的是,两个参数的方法在实现上和注释有点不太相符。。

注释中说,休眠时间 = 毫秒数 + 纳秒数,但其实真实情况是:

  1. 当 nanos 小于 0.5ms时,millis 不变;
  2. 当 nanos 大于等于 0.5ms时,millis 加 1;
  3. 当 nanos 不为 0 且 millis 为 0 时,millis 加 1
    也就是说该方法并没有实现 ns 级别的睡眠,最小的睡眠时间为 1ms。

主要代码在这里

sleep-3

所以这个方法存在的意义是什么呢?想不明白,从注释上也没看出来。

发现 stackoverflow 上也有一些讨论,具体的意义大家还是自行体会吧:
https://stackoverflow.com/questions/6553225/whats-the-purpose-of-sleeplong-millis-int-nanos

join

join 有 3 个方法

无参数的方法

是为了永远等待。

join-1

两个参数的方法

根据注释是为了实现纳秒级别的等待。

同两个参数的 sleep 方法,该方法也并没有实现 ns 级别的等待,最小的等待时间为 1ms。

join-2

一个参数的方法

是真正的实现逻辑。

实现方式是 while 循环。参数为 0 意味着永远等待。isAlive() / wait(Long) 都是 native method。

join-3

yield

是一个 native method,表示当前线程愿意放弃对 CPU 的使用,可以防止过度使用 CPU。

但是调度程序可以随意忽略此提示,另外重新竞争时,CPU 也有可能再次选中自己。

yield

中断线程

常见 API 有三个

interrupt

中间会调用一个 native method interrupt0()。

当调用 Object#wait / Thread#join / Thread#sleep 方法时,如果调用此方法,会抛出 InterruptedException。

要注意的是,当抛出 InterruptedException 时,线程的中断状态会被清除。

interrupt

isInterrupted

isInterrupted 有 2 个方法

无参数的方法

是测试此线程是否已被中断。

isInterrupted-1

一个参数的方法

是 native method,可以根据传入的参数决定是否重置中断状态。该方法也是 interrupted 的底层实现。

isInterrupted-2

interrupted

底层实现依赖上面 isInterrupted 一个参数的方法。

interrupted

ThreadLocal & InheritableThreadLocal 变量

这两个变量也必须强行漏个脸。可以看到每个线程维护自己的变量。

ThreadLocal & InheritableThreadLocal

Deprecated Methods

一共 6 个 deprecated methods

  1. Thread#stop() / Thread#stop(Throwable) / Thread#destroy() / Thread#suspend() / Thread#resume()

deprecated 原因:
https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html。

  1. 还有一个 native method: Thread#countStackFrames()

该方法作用主要是:计算线程中的堆栈帧数,而此时线程必须被挂起,否则会抛出 IllegalThreadStateException。

deprecated 原因:此调用的定义取决于 deprecated method Thread#suspend()。此外,此调用的结果从未明确定义。


欢迎关注我的其它发布渠道