Java线程状态和线程方法
线程状态
-
新建 NEW
new了线程之后,jvm为其分配内存并初始化了成员变量的值。
-
就绪 RUNNABLE
thread.start()之后就进入了就绪状态,jvm创建了方法调用栈和程序计数器。等待调度。
-
运行 RUNNING
从就绪状态获得了CPU,开始执行run()方法
-
阻塞 BLOCKED
- 运行状态的线程在获取对象的同步锁时,如果锁正在被占用,jvm会把线程放入锁池(lock pool),然后线程进入阻塞状态。
- 等待阻塞 运行中的线程调用了object.wait(), (显然这这个线程在此之前是获得了object的锁),jvm会把线程放入等待队列(waitting queue),线程进入阻塞状态。当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
- t.join()、sleep()等,jvm会把当前线程设置为阻塞状态。等子进程执行完毕或sleep完,线程将进入就绪状态。
-
死亡 DEAD
run()或call()方法执行完毕,抛出未捕获的异常,stop方法(官方弃用)
线程状态图

上面是理论书本上的线程模型规定的线程状态,而实际上Java中线程的状态是怎么样的呢?
Java线程状态
线程状态定义在java.lang.Thread.State
中,是个枚举类型。
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
一共是6种状态,与上文中的理论模型稍微有些调整:
- 由于无法完全控制由操作系统决定的线程得到CPU timeslice与否、Java中就将就绪和运行两种状态统称为RUNNABLE可执行。
- 而将阻塞状态细分为BLOCKED、WAITING、TIMED_WAITING三种。分别对应等待获取锁、获取锁之后将锁暂时释放而后等待通知重新竞争锁、以及sleep等几种情形。
- 剩下的新建与终止状态则与理论模型一致。

如图是Java线程的各个状态,以及各个状态之间转换的方式。我们来过一下这个图。
- 线程实例化之后进入NEW,然后start()启动之后就进入了RUNNABLE可运行状态。然后虽然java没有做单独的枚举状态做区分,但我们知道,可执行状态按是否得到CPU时间片分为READY就绪和RUNNING运行中两种状态,READY得到了时间片就会变为RUNNING,而RUNNING状态的线程即可能被OS调度失去CPU时间片、也可以通过yield()线程让步方法来主动的让出CPU时间片、与其他线程一起重新参与OS的调度。
- RUNNABLE状态可以通过Object.wait(),LockSupport.park()等方法进入WAITING等待状态。这也算一种阻塞,需要说明的是如果使用object.wait(),那么必须先获得object的同步锁,然后才能调用其wait()方法,且调用wait()时会进入等待阻塞而让出CPU时间、且立即释放这个锁。而处于WAITING状态的线程、当其他线程调用Object.notify()、LockSupport.unpark(Thread)时也会被唤醒而结束阻塞,转为RUNNABLE状态。
- TIMED_WAITING与WAITING很相似,两者与RUNNABLE状态之间的状态转移所依靠的方法也非常相似。与WAITING的一直阻塞等待不同的是TIMED_WAITING是可以指定阻塞等待时间的,比如Object.wait(long)、LockSupport.parkNanos()等。当然Thread.sleep(long)方法也会使线程从RUNNABLE进入TIMED_WAITING,需要注意的是sleep会使得线程进入可超时等待阻塞而让出CPU时间片,但是不会释放锁。
- BLOCKED阻塞状态的线程一旦获取到锁就会变为RUNNABLE,反过来,运行中的线程等待进入synchronized关键字的代码、也就是等待获取锁的时候会进入BLOCKED阻塞状态。
以上介绍了Java线程的状态以及在各种状态间变化的线程方法,关于线程方法还有一个interrupt方法需要注意。本质上线程的interrupt()方法只是改变了线程内的一个中断标志位而已,并不是说这样就会导致这个线程因此阻塞或终止。许多方法比如引导线程进入TIMED_WAITING状态的一些个方法比如sleep,其内部是spin的,经由判断当前线程的中断标志位来确定spin是否应该停止,如果这时候调用了thread.interrupt(),线程内部会通过抛出InterruptedException来跳出spin从而执行完run而终止线程。另一方面,对于我们自己编写的线程spin逻辑,也可以使用这个中断标志位来达到停止线程的目的,效果相当于在线程间共享一个当作信号的volatile变量,比如:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
logger.info("t1子线程开始执行...");
while(!Thread.currentThread().isInterrupted()) {
//logger.info("t1子线程还没被中断,继续执行");
/*
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
logger.info("t1被中断了,跳出spin,执行完毕");
}});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
logger.info("t2子线程将会在10秒后对t1子线程发起中断");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
}});
t1.start();
t2.start();
t1.join();
}
输出结果:
[2021-07-20 15:48:08] - t2子线程将会在10秒后对t1子线程发起中断
[2021-07-20 15:48:08] - t1子线程开始执行...
[2021-07-20 15:48:18] - t1被中断了,跳出spin,执行完毕
需要提一下的是如果把上面的sleep那里的注释放开的话,大概率t2向t1发起中断的时候t1在sleep,这样t1会以throw InterruptedException的方式来响应这个中断,然后抛出异常之后会重置线程内部的中断标志位,这样t1的下一次while判断就会判断中断没有发生了,从而t1会继续执行。