守护线程
每个线程可以被标记为守护线程,当创建新线程的线程是守护线程时,新线程也是守护线程(初始化时,see init),默认为非守护线程。与之对应的是用户线程。
守护线程的优先级很低,当 JVM 退出时,不会关心是否还存在守护线程。即使还存在守护线程,JVM 仍会退出。而用户线程如果还在运行,会阻止 JVM 进程退出。
当 JVM 启动时,通常会有一个非守护线程(通常是类的 main 方法)。当发生以下任一情况时 JVM 才会停止执行:
- Runtime 类的 exit 方法被调用(即终止当前运行的 Java 虚拟机),且安全管理器已允许退出操作的发生
- 所有不是守护线程的线程都已经死亡(调用 run 方法后返回 或 抛出异常)
有一个注意的点是,如果要调用 setDaemon 设置守护线程,必须在线程启动之前调用此方法,否则会抛出 IllegalThreadStateException。
场景
垃圾回收线程
一些监控工具
当 JVM 需要退出时无需关注监控是否正在运行,直接退出不会对业务产生任何影响。
优先级
每个线程都有一个优先级,优先级高的线程优先于优先级低的线程执行。当创建一个新的 Thread 对象时,新线程的优先级最初设置为与创建线程的优先级相等(初始化时,see init)。
优先级最低 1,最高 10,默认 5。
如果要让新线程的优先级与创建线程的优先级相等,则创建线程的 setPriority 应该在 new Thread() 之前调用;
如果只想改变新线程的优先级,则新线程的 setPriority 应该在 start() 之前调用。
默认名称
默认名称为(构造方法中,see Constructor):”Thread-“ + 一个整数
该整数计算逻辑为:
所以第一个新线程默认名称为:Thread-0。
线程状态
NEW
尚未启动的线程处于 NEW 状态。
RUNNABLE
在 JVM 中执行的线程处于 RUNNABLE 状态。但它可能正在等待来自操作系统的其他资源。
BLOCKED
被阻塞等待监视器锁(monitor lock)的线程处于 BLOCKED 状态。处于阻塞状态的线程正在等待监视器锁进入同步 阻塞/方法 或在调用 Object#wait() 后重入同步 阻塞/方法。
WAITING
无限期等待另一个线程执行特定操作的线程处于 WAITING 状态。
调用以下方法之一:
- Object#wait()
- Thread#join()
- LockSupport#park()
例如,一个线程在一个对象上调用了 Object.wait() 方法,正在等待另一个线程在那个对象上调用 Object.notify() 方法或 Object.notifyAll() 方法;调用 Thread.join() 的线程正在等待指定的线程终止。
TIMED_WAITING
等待另一个线程执行操作直到指定等待时间的线程处于 TIMED_WAITING 状态。
调用以下方法之一:
- Thread#sleep
- Object#wait(long)
- Thread#join(long)
- LockSupport#parkNanos
- LockSupport#parkUntil
TERMINATED
已退出的线程处于 TERMINATED 状态。线程已完成执行。
状态流转图
两种方式创建新线程
继承 Thread 并重写 run 方法
demo:计算大于规定值的素数(质数)的线程
1 | class PrimeThread extends Thread { |
启动运行
1 | PrimeThread p = new PrimeThread(143); |
实现 Runnable 接口并实现 run 方法
demo(同上):计算大于规定值的素数(质数)的线程
1 | class PrimeRun implements Runnable { |
启动运行
1 | PrimeRun p = new PrimeRun(143); |
Other Source Code
Constructor
一共 9 个构造函数,其中有一个不是 public 的,也就是说,用户可用的构造函数一共有 8 个。
一个一个看:
该构造函数不是 public 的。
init
从 Constructor 可以看到,所有的构造函数都是调用了 init 方法。一共有两个 init 方法,其中一个是另一个的重载。
终于到了完整的 init 逻辑,其中有一些 Java Security 的代码可以不用过多关注。
start
中间会调用一个 native method start0()。
可以看出:如果多次调用 start 方法,会抛出 IllegalThreadStateException。
run
run 方法比较简单,其中的 target 是在 init 方法中赋值的 Runnable(如果有的话),如果 target 为 null 则什么也不做(这也是继承 Thread 后需要重写 run 方法的原因)。
sleep
sleep 有 2 个方法
一个参数的方法
是 native method。
两个参数的方法
根据注释是为了实现纳秒级别的睡眠。
但有意思的是,两个参数的方法在实现上和注释有点不太相符。。
注释中说,休眠时间 = 毫秒数 + 纳秒数,但其实真实情况是:
- 当 nanos 小于 0.5ms时,millis 不变;
- 当 nanos 大于等于 0.5ms时,millis 加 1;
- 当 nanos 不为 0 且 millis 为 0 时,millis 加 1
也就是说该方法并没有实现 ns 级别的睡眠,最小的睡眠时间为 1ms。
主要代码在这里
所以这个方法存在的意义是什么呢?想不明白,从注释上也没看出来。
发现 stackoverflow 上也有一些讨论,具体的意义大家还是自行体会吧:
https://stackoverflow.com/questions/6553225/whats-the-purpose-of-sleeplong-millis-int-nanos
join
join 有 3 个方法
无参数的方法
是为了永远等待。
两个参数的方法
根据注释是为了实现纳秒级别的等待。
同两个参数的 sleep 方法,该方法也并没有实现 ns 级别的等待,最小的等待时间为 1ms。
一个参数的方法
是真正的实现逻辑。
实现方式是 while 循环。参数为 0 意味着永远等待。isAlive() / wait(Long) 都是 native method。
yield
是一个 native method,表示当前线程愿意放弃对 CPU 的使用,可以防止过度使用 CPU。
但是调度程序可以随意忽略此提示,另外重新竞争时,CPU 也有可能再次选中自己。
中断线程
常见 API 有三个
interrupt
中间会调用一个 native method interrupt0()。
当调用 Object#wait / Thread#join / Thread#sleep 方法时,如果调用此方法,会抛出 InterruptedException。
要注意的是,当抛出 InterruptedException 时,线程的中断状态会被清除。
isInterrupted
isInterrupted 有 2 个方法
无参数的方法
是测试此线程是否已被中断。
一个参数的方法
是 native method,可以根据传入的参数决定是否重置中断状态。该方法也是 interrupted 的底层实现。
interrupted
底层实现依赖上面 isInterrupted 一个参数的方法。
ThreadLocal & InheritableThreadLocal 变量
这两个变量也必须强行漏个脸。可以看到每个线程维护自己的变量。
Deprecated Methods
一共 6 个 deprecated methods
- Thread#stop() / Thread#stop(Throwable) / Thread#destroy() / Thread#suspend() / Thread#resume()
deprecated 原因:
https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html。
- 还有一个 native method: Thread#countStackFrames()
该方法作用主要是:计算线程中的堆栈帧数,而此时线程必须被挂起,否则会抛出 IllegalThreadStateException。
deprecated 原因:此调用的定义取决于 deprecated method Thread#suspend()。此外,此调用的结果从未明确定义。