切换状态 - 图1

  1. 状态的本质:一个变量值

Thread 类中的一个变量,叫
private volatile int threadStatus = 0;
可以通过映射关系(VM.toThreadState),转换成一个枚举类。

  1. public enum State {
  2. NEW,
  3. RUNNABLE,
  4. BLOCKED,
  5. WAITING,
  6. TIMED_WAITING,
  7. TERMINATED;
  8. }

所以,我们就盯着 threadStatus 这个值的变化就好了。就是这么简单。

NEW

还没有任何 Thread 类的对象呢,也就不存在线程状态一说.

一切的起点,要从把一个 Thread 类的对象创建出来,开始说起。Thread t = new Thread();

新建一个 Thread 类的对象,就是创建了一个新的线程,此时这个线程的状态,是 NEW(初始态)

切换状态 - 图2

RUNNABLE

Thread t = new Thread();只是做了些表面功夫,在 Java 语言层面将自己的一个对象中的属性附上值罢了,根本没碰到操作系统级别的东西呢。
Thread 对象,在调用了 start() 方法后,才显现生机。t.start();

这个方法最终会调用native方法:
private native void start0();

最终调用了:pthread_create(...);

此时,在操作系统内核中,才有了一个真正的线程,被创建出来。 linux 操作系统,是没有所谓的刚创建但没启动的线程这种说法的,创建即刻开始运行

切换状态 - 图3
总结:
1. 在 Java 调用 start() 后,操作系统中才真正出现了一个线程,并且立刻运行。
2. Java 中的线程,和操作系统内核中的线程,是一对一的关系。
3. 调用 start 后,线程状态变为 RUNNABLE,这是由 native 方法里的某部分代码造成的。

RUNNING 和 READY

CPU 一个核心,同一时刻,只能运行一个线程。具体执行哪个线程,要看操作系统 的调度机制。

上面的 RUNNABLE 状态,准确说是,得到了可以随时准备运行的机会的状态。

  1. 正在运行的线程
  2. 处于就绪态等cpu分配时间片的线程

切换状态 - 图4

  • 就绪态处于就绪队列中,等待操作系统调度,进入cpu运行
  • 运行中是正在cpu运行

无论是 Java 语言,还是操作系统,都不区分这两种状态,在 Java 中统统叫 RUNNABLE。

TERMINATED

**
线程执行完毕,线程的状态就变为TERMINATED.

切换状态 - 图5

BLOCKED

在进入 synchronized 块时,因为无法拿到锁,会使线程状态变为 BLOCKED
当该线程获取到了锁后,便可以进入 synchronized 块,此时线程状态变为 RUNNABLE。

切换状态 - 图6
当进入 synchronized 块或方法,获取不到锁时,线程会进入一个该锁对象的同步队列

当持有锁的这个线程,释放了锁之后,会唤醒该锁对象同步队列中的所有线程,这些线程会继续尝试抢锁。如此往复。

比如,有一个锁对象 A,线程 1 此时持有这把锁。线程 2、3、4 分别尝试抢这把锁失败。

切换状态 - 图7
线程 1 释放锁,线程 2、3、4 重新变为 RUNNABLE,继续抢锁,假如此时线程 3 抢到了锁。

切换状态 - 图8

WAITING

**

wait/notify

  1. new Thread(() -> {
  2. synchronized (lock) {
  3. ...
  4. lock.wait();
  5. ...
  6. }
  7. }).start();

当这个 lock.wait() 方法一调用,会发生三件事。
1. 释放锁对象 lock(隐含着必须先获取到这个锁才行)
2. 线程状态变成 WAITING
3. 线程进入 lock 对象的等待队列
**
切换状态 - 图9

必须由另一个线程,调用同一个对象的 notify/notifyAll 方法。这个线程被唤醒,从等待队列中移出,并从 WAITING 状态返回 RUNNABLE 状态

  1. new Thread(() -> {
  2. synchronized (lock) {
  3. ...
  4. lock.notify();
  5. ...
  6. }
  7. }).start();

切换状态 - 图10

只不过 notify 是只唤醒一个线程,而 notifyAll 是唤醒所有等待队列中的线程。
但需要注意,被唤醒后的线程,从等待队列移出,状态变为 RUNNABLE,但仍然需要抢锁,抢锁成功了,才可以从 wait 方法返回,继续执行。

如果失败了,就和上一部分的 BLOCKED 流程一样了。

切换状态 - 图11

切换状态 - 图12

join

主线程这样写。

  1. public static void main(String[] args) {
  2. thread t = new Thread(...);
  3. t.start();
  4. t.join();
  5. ...
  6. }

当执行到 t.join() 的时候,主线程会变成 WAITING 状态,直到线程 t 执行完毕,主线程才会变回 RUNNABLE 状态,继续往下执行。
切换状态 - 图13

底层:

  1. // Thread.java
  2. // 无参的 join 有用的信息就这些,省略了额外分支
  3. public synchronized void join() {
  4. while (isAlive()) {
  5. wait();
  6. }
  7. }

他的本质仍然是执行了 wait() 方法,而锁对象就是 Thread t 对象本身。

线程 t 结束后,由 jvm 自动调用 t.notifyAll(),不用我们程序显示写出。

切换状态 - 图14

TIMED_WAITING

**
将上面导致线程变成 WAITING 状态的那些方法,都增加一个超时参数,就变成了将线程变成 TIMED_WAITING 状态的方法了,我们直接更新流程图。

切换状态 - 图15

这些方法的唯一区别就是,从 TIMED_WAITING 返回 RUNNABLE,不但可以通过之前的方式,还可以通过到了超时时间,返回 RUNNABLE 状态。

Thread.sleep(long) 仅仅让线程挂起,只能通过等待超时时间到了再被唤醒呢。

切换状态 - 图16