在Java并发编程中,理解线程的生命周期是每个开发者必须掌握的基础知识。就像人的一生会经历不同阶段一样,Java线程从创建到销毁也会经历多个状态变迁。这些状态定义在Thread.State枚举中,包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED。
我刚接触多线程编程时,常常困惑为什么线程"卡住"了,后来发现其实线程只是进入了某个特定状态。理解这些状态的转换条件和触发时机,能帮助我们更好地诊断并发问题,编写出更健壮的多线程程序。
当我们用new Thread()创建一个线程对象时,线程就处于NEW状态。这时候的线程就像一张白纸,还没有任何执行轨迹。此时调用isAlive()方法会返回false,因为线程尚未启动。
java复制Thread thread = new Thread(() -> {
System.out.println("线程执行中");
});
System.out.println(thread.getState()); // 输出 NEW
注意:创建线程对象后如果不调用start()方法,线程将永远停留在NEW状态,最终会被垃圾回收。
调用start()方法后,线程进入RUNNABLE状态。这个状态有点特殊,它实际上包含了两个子状态:
java复制thread.start();
System.out.println(thread.getState()); // 输出 RUNNABLE
在操作系统层面,RUNNABLE状态的线程可能正在执行,也可能在就绪队列中等待调度。Java虚拟机将这个细节抽象掉了,统一用RUNNABLE表示。
当线程试图获取一个已经被其他线程持有的对象锁时,就会进入BLOCKED状态。这种情况通常发生在同步代码块或同步方法中。
java复制Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("获取到锁");
}
});
t1.start();
Thread.sleep(100); // 确保t1先获取锁
t2.start();
Thread.sleep(200); // 给t2时间尝试获取锁
System.out.println(t2.getState()); // 输出 BLOCKED
经验:BLOCKED状态是性能瓶颈的常见表现,过多的线程阻塞会导致系统吞吐量下降。
线程进入WAITING状态通常是因为调用了以下方法之一:
java复制Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); // 进入WAITING状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread.sleep(100);
System.out.println(t1.getState()); // 输出 WAITING
WAITING状态的线程需要其他线程通过notify()/notifyAll()或者中断来唤醒。我在实际项目中遇到过因为忘记唤醒WAITING线程而导致的内存泄漏问题。
TIMED_WAITING与WAITING类似,但带有超时时间。常见触发方法包括:
java复制Thread t = new Thread(() -> {
try {
Thread.sleep(1000); // 休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // 输出 TIMED_WAITING
技巧:相比WAITING,TIMED_WAITING更安全,因为它能自动恢复,避免永久等待的风险。
线程执行完run()方法或者因异常退出后,就进入TERMINATED状态。此时线程已经死亡,不能再被启动。
java复制Thread t = new Thread(() -> {
System.out.println("线程执行完毕");
});
t.start();
t.join(); // 等待线程结束
System.out.println(t.getState()); // 输出 TERMINATED
code复制NEW --start()--> RUNNABLE
RUNNABLE --获取锁--> 运行中
运行中 --释放锁/时间片用完--> 就绪
运行中 --wait()/join()--> WAITING
运行中 --sleep()/wait(timeout)--> TIMED_WAITING
运行中 --等待I/O--> RUNNABLE(就绪)
WAITING --notify()/notifyAll()/interrupt()--> 就绪
TIMED_WAITING --超时/notify()/interrupt()--> 就绪
RUNNABLE --执行完毕/异常退出--> TERMINATED
java复制Thread thread = new Thread(/*...*/);
Thread.State state = thread.getState(); // 获取线程状态
bash复制jstack <pid> > thread_dump.txt
分析线程转储文件时,重点关注:
java复制class SafeStopThread extends Thread {
private volatile boolean running = true;
public void run() {
while(running) {
// 执行任务
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 恢复中断状态
Thread.currentThread().interrupt();
break;
}
}
}
public void stopRunning() {
running = false;
interrupt();
}
}
Java线程状态是对操作系统原生线程状态的抽象。例如:
HotSpot虚拟机的线程状态转换更加细致,包括:
这些状态可以通过JVM参数-XX:+PrintThreadStacks打印出来。
理解线程状态对性能调优至关重要:
我在实际项目中通过分析线程状态分布,发现了一个数据库连接池配置不合理的问题,优化后系统吞吐量提升了30%。
可能原因:
java复制ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] threadIds = bean.findDeadlockedThreads();
if (threadIds != null) {
ThreadInfo[] infos = bean.getThreadInfo(threadIds);
for (ThreadInfo info : infos) {
System.out.println(info);
}
}
线程池中的工作线程在任务完成后会回到WAITING状态(通过getTask()),等待下一个任务,而不是终止。这解释了为什么线程池可以复用线程。
CompletableFuture内部也有类似的状态转换:
Java 19引入的虚拟线程(协程)状态与平台线程类似,但由于是轻量级线程,其状态转换开销更小,更适合高并发场景。
理解线程生命周期不仅是面试常问的知识点,更是解决实际并发问题的基础。我在处理一个生产环境死锁问题时,正是通过分析线程状态快速定位了问题根源。建议开发者在编码时时刻关注线程可能处于的状态,这样才能编写出健壮可靠的并发程序。