- 状态的本质:一个变量值
Thread 类中的一个变量,叫private volatile int threadStatus = 0;
可以通过映射关系(VM.toThreadState),转换成一个枚举类。
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
所以,我们就盯着 threadStatus 这个值的变化就好了。就是这么简单。
NEW
还没有任何 Thread 类的对象呢,也就不存在线程状态一说.
一切的起点,要从把一个 Thread 类的对象创建出来,开始说起。Thread t = new Thread();
新建一个 Thread 类的对象,就是创建了一个新的线程,此时这个线程的状态,是 NEW(初始态)。
RUNNABLE
Thread t = new Thread();
只是做了些表面功夫,在 Java 语言层面将自己的一个对象中的属性附上值罢了,根本没碰到操作系统级别的东西呢。
Thread 对象,在调用了 start() 方法后,才显现生机。t.start();
这个方法最终会调用native方法:private native void start0();
最终调用了:pthread_create(...);
此时,在操作系统内核中,才有了一个真正的线程,被创建出来。 linux 操作系统,是没有所谓的刚创建但没启动的线程这种说法的,创建即刻开始运行。
总结:
1. 在 Java 调用 start() 后,操作系统中才真正出现了一个线程,并且立刻运行。
2. Java 中的线程,和操作系统内核中的线程,是一对一的关系。
3. 调用 start 后,线程状态变为 RUNNABLE,这是由 native 方法里的某部分代码造成的。
RUNNING 和 READY
CPU 一个核心,同一时刻,只能运行一个线程。具体执行哪个线程,要看操作系统 的调度机制。
上面的 RUNNABLE 状态,准确说是,得到了可以随时准备运行的机会的状态。
- 正在运行的线程
- 处于就绪态等cpu分配时间片的线程
- 就绪态处于就绪队列中,等待操作系统调度,进入cpu运行
- 运行中是正在cpu运行
无论是 Java 语言,还是操作系统,都不区分这两种状态,在 Java 中统统叫 RUNNABLE。
TERMINATED
**
线程执行完毕,线程的状态就变为TERMINATED.
BLOCKED
在进入 synchronized 块时,因为无法拿到锁,会使线程状态变为 BLOCKED。
当该线程获取到了锁后,便可以进入 synchronized 块,此时线程状态变为 RUNNABLE。
当进入 synchronized 块或方法,获取不到锁时,线程会进入一个该锁对象的同步队列。
当持有锁的这个线程,释放了锁之后,会唤醒该锁对象同步队列中的所有线程,这些线程会继续尝试抢锁。如此往复。
比如,有一个锁对象 A,线程 1 此时持有这把锁。线程 2、3、4 分别尝试抢这把锁失败。
线程 1 释放锁,线程 2、3、4 重新变为 RUNNABLE,继续抢锁,假如此时线程 3 抢到了锁。
WAITING
wait/notify
new Thread(() -> {
synchronized (lock) {
...
lock.wait();
...
}
}).start();
当这个 lock.wait() 方法一调用,会发生三件事。
1. 释放锁对象 lock(隐含着必须先获取到这个锁才行)
2. 线程状态变成 WAITING
3. 线程进入 lock 对象的等待队列
**
必须由另一个线程,调用同一个对象的 notify/notifyAll 方法。这个线程被唤醒,从等待队列中移出,并从 WAITING 状态返回 RUNNABLE 状态
new Thread(() -> {
synchronized (lock) {
...
lock.notify();
...
}
}).start();
只不过 notify 是只唤醒一个线程,而 notifyAll 是唤醒所有等待队列中的线程。
但需要注意,被唤醒后的线程,从等待队列移出,状态变为 RUNNABLE,但仍然需要抢锁,抢锁成功了,才可以从 wait 方法返回,继续执行。
如果失败了,就和上一部分的 BLOCKED 流程一样了。
join
主线程这样写。
public static void main(String[] args) {
thread t = new Thread(...);
t.start();
t.join();
...
}
当执行到 t.join() 的时候,主线程会变成 WAITING 状态,直到线程 t 执行完毕,主线程才会变回 RUNNABLE 状态,继续往下执行。
底层:
// Thread.java
// 无参的 join 有用的信息就这些,省略了额外分支
public synchronized void join() {
while (isAlive()) {
wait();
}
}
他的本质仍然是执行了 wait() 方法,而锁对象就是 Thread t 对象本身。
线程 t 结束后,由 jvm 自动调用 t.notifyAll(),不用我们程序显示写出。
TIMED_WAITING
**
将上面导致线程变成 WAITING 状态的那些方法,都增加一个超时参数,就变成了将线程变成 TIMED_WAITING 状态的方法了,我们直接更新流程图。
这些方法的唯一区别就是,从 TIMED_WAITING 返回 RUNNABLE,不但可以通过之前的方式,还可以通过到了超时时间,返回 RUNNABLE 状态。
Thread.sleep(long)
仅仅让线程挂起,只能通过等待超时时间到了再被唤醒呢。