面试必知必会(1):线程状态和创建方式
本文详细介绍了Java线程的6种状态及其转换过程:NEW(新建)、RUNNABLE(可运行)、BLOCKED(阻塞)、WAITING(无限等待)、TIMED_WAITING(限时等待)和TERMINATED(终止)。重点阐述了各状态的定义、触发条件及核心特征,包括RUNNABLE状态包含就绪和运行两个子状态,BLOCKED状态仅与synchronized锁相关,以及WAITING/TIMED_WA
·
Java面试系列文章
一、Java线程的状态:从生到死的完整生命周期
Java线程的状态由Thread.State枚举类定义(JDK 5+),共6种状态:新建(NEW)、运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、超时等待(TIMED_WAITING)、终止(TERMINATED)。这些状态反映了线程在操作系统层面的调度状态与Java虚拟机(JVM)的管理逻辑。
1、状态定义与核心特征
1.1、NEW(新建状态)
- 定义:当通过
new Thread()创建线程对象后,尚未调用start()方法时,线程处于新建状态 - 核心特征:
- 线程对象已创建,此时线程仅存在于 Java 虚拟机的内存中,没有执行任何代码
- 仅能调用start()方法进入 RUNNABLE 状态,调用其他方法(如run())不会改变状态,仅相当于普通方法调用
1.2、RUNNABLE(可运行状态)
- 定义:调用
start()方法后,线程进入RUNNABLE状态,这是最核心的运行相关状态 - 核心特征:
- 该状态包含两个子状态:
就绪(Ready)和运行中(Running)- 就绪:线程已具备运行条件,
等待 CPU 调度(由操作系统的线程调度器决定) - 运行中:线程获得 CPU 时间片,正在
执行run()方法中的代码,时间片用完或被抢占后回到就绪态
- 就绪:线程已具备运行条件,
- 就绪状态不消耗 CPU
计算资源(不执行代码、不占用 CPU 时间片),仅占用“调度资源”(在就绪队列排队)
- 该状态包含两个子状态:
1.3、BLOCKED(阻塞状态)
- 定义:线程因等待获取对象监视器锁(synchronized 锁) 而阻塞时的状态
- 核心特征:
- 仅与
synchronized锁相关,Lock 锁(如 ReentrantLock)的等待不会进入 BLOCKED 状态 - 当线程获取到锁后,会从 BLOCKED 切换回 RUNNABLE 状态
- 仅与
1.4、WAITING(无超时等待状态)
- 定义:线程进入
无时间限制的等待状态,必须由其他线程显式唤醒,否则会永久等待 - 触发场景(核心方法):
Object.wait():无参数,需配合synchronized使用,唤醒需Object.notify()/notifyAll()Thread.join():无参数,等待目标线程执行完毕LockSupport.park():无参数,唤醒需LockSupport.unpark(线程对象)
- 核心特征:
BLOCKED/WAITING/TIMED_WAITING不占用 CPU 资源,线程调度器会将其移出调度队列
1.5、TIMED_WAITING(超时等待状态)
- 定义:线程进入
有时间限制的等待状态,超时后会自动唤醒,也可被其他线程提前唤醒 - 触发场景(核心方法):
Thread.sleep(long millis):最常用,让线程休眠指定时间Object.wait(long timeout):带超时参数的等待,超时自动唤醒Thread.join(long millis):带超时参数的等待LockSupport.parkNanos(long nanos)/parkUntil(long deadline):带超时的 park 方法
- 核心特征:
- 与 WAITING 的唯一区别是有超时时间,避免永久等待
wait/join和synchronized绑定,会释放锁,sleep/park不绑定,不会释放锁
1.6、TERMINATED(终止状态)
- 定义:线程执行完毕(
run()方法正常结束)或因异常终止后,进入终止状态 - 核心特征:
- 终止状态的线程无法再次启动(调用start()会抛出IllegalThreadStateException)
- 线程的资源会被 JVM 回收,但线程对象本身仍存在(可通过对象引用获取状态)
2、状态转换的典型流程
2.1、NEW → RUNNABLE
调用start()方法后,线程进入就绪队列(RUNNABLE)。注意:直接调用run()方法不会启动新线程,仅会作为普通方法在当前线程执行!
Thread t = new Thread(() -> System.out.println("Running"));
t.run(); // 错误:在主线程执行,不启动新线程
t.start(); // 正确:进入RUNNABLE状态,由JVM调度执行
2.2、RUNNABLE → BLOCKED
当线程尝试进入synchronized修饰的代码块/方法时,若锁已被其他线程持有,当前线程进入BLOCKED状态,直到锁被释放并竞争成功。
public class BlockedStateDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) { // t1持有锁,t2进入BLOCKED
System.out.println("t2 acquired lock");
}
});
t1.start();
try { Thread.sleep(100); } catch (InterruptedException e) {} // 确保t1先获取锁
t2.start();
}
}
通过
jstack <PID>命令可观察到t2的状态为BLOCKED (on object monitor)
2.3、RUNNABLE → WAITING/TIMED_WAITING
- WAITING:调用
wait()(需先获取对象锁)、join()(等待目标线程终止)、park()无超时参数的方法 - TIMED_WAITING:调用
sleep(time)、wait(time)、join(time)、parkNanos(time)等带超时参数的方法
public class WaitingStateDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); // 释放锁,进入WAITING(需其他线程notify)
} catch (InterruptedException e) {}
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000); // TIMED_WAITING状态,2秒后自动唤醒
} catch (InterruptedException e) {}
});
t1.start();
t2.start();
Thread.sleep(100); // 确保t1已进入wait()
System.out.println("t1 state: " + t1.getState()); // 输出WAITING
System.out.println("t2 state: " + t2.getState()); // 输出TIMED_WAITING
}
}
2.4、任意状态 → TERMINATED
线程执行完run()方法或因未捕获异常退出,进入TERMINATED。
Thread t = new Thread(() -> {});
t.start();
t.join(); // 等待t终止
System.out.println(t.getState()); // TERMINATED
t.start(); // 抛出异常:IllegalThreadStateException
3、常见误区澄清
RUNNABLE vs 操作系统线程状态:Java的RUNNABLE包含操作系统的"就绪"(Ready)和"运行"(Running),即线程可能在等待CPU时间片(就绪)或正在执行(运行),但JVM统一标记为RUNNABLEBLOCKED vs WAITING:BLOCKED是因竞争锁失败被动阻塞;WAITING是主动调用方法释放锁并等待唤醒(如wait()会释放锁)sleep()不会释放锁:调用sleep(time)时,线程仍持有synchronized锁,其他线程无法进入该锁的代码块
二、Java线程的创建方式:从基础到进阶
Java提供多种线程创建方式,核心差异在于对线程控制权的分配(继承vs组合)、是否支持返回值、异常处理能力等。以下从传统方式到现代并发工具逐一解析。
1、继承Thread类(基础但不推荐)
- 实现方式:重写
Thread类的run()方法,通过start()启动线程
/**
* 继承Thread类创建线程
*/
public class ThreadExtendDemo extends Thread {
// 重写run方法,定义线程执行逻辑
@Override
public void run() {
for (int i = 0; i < 5; i++) {
// Thread.currentThread().getName()获取当前线程名称
System.out.println("继承Thread的线程:" + Thread.currentThread().getName() + ",执行次数:" + i);
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建线程对象
ThreadExtendDemo thread1 = new ThreadExtendDemo();
ThreadExtendDemo thread2 = new ThreadExtendDemo();
// 设置线程名称(便于调试)
thread1.setName("线程1");
thread2.setName("线程2");
// 启动线程(核心:调用start(),而非直接调用run())
thread1.start();
thread2.start();
// 错误示例:直接调用run(),仅为普通方法调用,不会创建新线程
// thread1.run();
}
}
- 优点:简单直观,适合快速验证小功能
- 缺点:
单继承限制:Java不支持多继承,若类已继承其他父类则无法使用线程与任务强耦合:Thread既是线程载体又是任务逻辑载体,不符合"组合优于继承"原则无法获取返回值:需通过共享变量或回调间接传递结果,增加复杂度
2、实现Runnable接口(推荐的基础方式)
- 实现方式:实现
Runnable接口的run()方法,作为参数传入Thread构造器
/**
* 实现Runnable接口创建线程
*/
public class RunnableImplDemo implements Runnable {
// 共享变量(多线程共享)
private int count = 0;
// 实现run方法,定义线程执行逻辑
@Override
public void run() {
synchronized (this) { // 同步代码块,保证线程安全
for (int i = 0; i < 5; i++) {
count++;
System.out.println("实现Runnable的线程:" + Thread.currentThread().getName() +
",count值:" + count);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
// 创建Runnable实现类对象(线程逻辑载体)
RunnableImplDemo runnable = new RunnableImplDemo();
// 创建Thread对象,传入Runnable实例
Thread thread1 = new Thread(runnable, "线程A");
Thread thread2 = new Thread(runnable, "线程B");
// 启动线程
thread1.start();
thread2.start();
}
}
- 优点:
规避单继承限制:实现接口后仍可继承其他类解耦线程逻辑与对象:同一 Runnable 实例可被多个 Thread 复用便于共享资源:多个线程可共享同一个 Runnable 对象的成员变量
- 缺点:仍无法获取线程执行结果(无返回值)
3、实现Callable接口+FutureTask(支持返回值与异常)
- 实现方式:
Callable<V>是泛型接口,其call()方法可返回结果并抛出异常;需通过FutureTask(实现了Runnable和Future)包装后传入Thread
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 实现Callable接口创建线程(带返回值)
*/
public class CallableImplDemo implements Callable<Integer> {
// 线程执行的任务:计算1~n的和
private int n;
public CallableImplDemo(int n) {
this.n = n;
}
// 实现call方法(有返回值,可抛出异常)
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
System.out.println("Callable线程:" + Thread.currentThread().getName() +
",计算到:" + i + ",当前和:" + sum);
Thread.sleep(100);
}
return sum; // 返回执行结果
}
public static void main(String[] args) {
// 1. 创建Callable实现类对象
Callable<Integer> callable = new CallableImplDemo(10);
// 2. 创建FutureTask对象(封装Callable,用于获取结果)
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 3. 创建Thread对象,传入FutureTask(FutureTask实现了Runnable接口)
Thread thread = new Thread(futureTask, "计算线程");
// 4. 启动线程
thread.start();
// 5. 获取线程执行结果(get()方法会阻塞(等待状态),直到线程执行完毕)
try {
Integer result = futureTask.get();
System.out.println("线程执行结果:1~10的和 = " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
- 优点:
- 支持返回值:通过
FutureTask.get()获取call()的执行结果 - 异常处理:可捕获
call()抛出的受检异常(如Exception),避免线程静默崩溃 - 超时控制:
get(timeout)防止无限阻塞
- 支持返回值:通过
- 缺点:需额外包装(
FutureTask),代码稍复杂
更多推荐


所有评论(0)