Java Thread类的API和线程状态详解
理解 Java 线程的 7 种状态(NEW、RUNNABLE、BLOCKED 等)和状态转换是写多线程的基础。Thread 类提供了 start、sleep、join、interrupt 等关键 API,还有不少已废弃的危险方法要避开。
Java 线程的 7 种状态和 Thread 类的几个关键 API,是多线程编程的基础功课。很多人只知道"开启线程用 start"就完事了,不知道线程背后还有这么多状态转换和陷阱。
Java 线程的 7 种状态
线程从诞生到终亡,会经历不同的生命周期状态。java.lang.Thread.State 这个枚举定义了 7 种状态。
NEW
线程对象刚创建,还没调 start()。
Thread t = new Thread(() -> {
System.out.println("我在跑");
});
System.out.println(t.getState()); // NEW
this 时刻线程还没被操作系统调度,就是个 Java 对象而已。
RUNNABLE
调了 start() 之后,线程就进入 RUNNABLE 状态。
注意:RUNNABLE 不等于"正在跑",而是"可以被 CPU 调度运行"。操作系统的线程调度器随时可能让这个线程停下来,去跑其他线程。
Thread t = new Thread(() -> {
System.out.println("我在跑");
});
t.start();
System.out.println(t.getState()); // RUNNABLE
BLOCKED
线程在等待获取一个对象的 monitor 锁(通常是 synchronized 块)。
object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized(lock) {
try { Thread.sleep(10000); } catch (Exception e) {}
}
});
Thread t2 = new Thread(() -> {
try { Thread.sleep(100); } catch (Exception e) {} // 让 t1 先获得锁
synchronized(lock) { // 这里等着呢
System.out.println("我获得锁了");
}
});
t1.start();
t2.start();
Thread.sleep(200);
System.out.println(t2.getState()); // BLOCKED
一旦拿到锁,就回到 RUNNABLE。
WAITING
线程在调用 wait()、join() 或 LockSupport.park() 后进入这个状态,要等其他线程来唤醒。
Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized(lock) {
try {
lock.wait(); // 进入 WAITING
} catch (InterruptedException e) {}
}
});
Thread t2 = new Thread(() -> {
try { Thread.sleep(1000); } catch (Exception e) {}
synchronized(lock) {
lock.notifyAll(); // 唤醒 t1
}
});
t1.start();
t2.start();
Thread.sleep(500);
System.out.println(t1.getState()); // WAITING
WAITING 和 BLOCKED 的区别很关键:BLOCKED 是在"外面排队"等锁,WAITING 是"已经进去了",主动释放锁去等通知。
TIMED_WAITING
和 WAITING 类似,但有超时时间。比如调 sleep()、wait(timeout)、join(timeout)。
Thread t = new Thread(() -> {
try {
Thread.sleep(5000); // 进入 TIMED_WAITING
} catch (InterruptedException e) {}
});
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // TIMED_WAITING
时间到了或者被中断,就回到 RUNNABLE。
TERMINATED
线程执行完了,或者因异常终止了。这个状态是终点,回不来了。
Thread t = new Thread(() -> {
System.out.println("我要完了");
});
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // TERMINATED
线程状态转换图
NEW
|
| start()
v
RUNNABLE
|
+---> BLOCKED (等锁)
|
+---> WAITING (wait/join/park)
|
+---> TIMED_WAITING (sleep/wait/join with timeout)
|
v
TERMINATED
Thread 类的关键 API
start() vs run()
千万别直接调 run(),那样不会启动新线程,只是在当前线程里同步执行函数。
Thread t = new Thread(() -> {
System.out.println("当前线程:" + Thread.currentThread().getName());
});
t.start(); // 正确:启动新线程
// t.run(); // 错误:在当前线程同步执行
start() 会由 JVM 调用 native 方法启动真正的操作系统线程,然后那个线程会回调 run() 方法。
sleep(long millis)
让当前线程暂停指定毫秒数,进入 TIMED_WAITING 状态。
Thread.sleep(1000); // 暂停 1 秒
要点:
- sleep() 不释放锁(如果当前持有的话)
- 如果被 interrupt,会抛 InterruptedException
- 是静态方法,只能让当前线程睡,不能让别的线程睡
Object lock = new Object();
new Thread(() -> {
synchronized(lock) {
try {
Thread.sleep(2000); // 睡 2 秒,但还持着锁
System.out.println("我醒了,还握着锁呢");
} catch (InterruptedException e) {}
}
}).start();
Thread.sleep(500);
new Thread(() -> {
System.out.println("我想进去");
synchronized(lock) {
System.out.println("终于进去了"); // 要等 2 秒
}
}).start();
join()
"等我回来"。调用 t.join() 会阻塞当前线程,直到 t 线程执行完。
Thread t = new Thread(() -> {
System.out.println("我在干活");
try { Thread.sleep(2000); } catch (Exception e) {}
System.out.println("我干完了");
});
t.start();
System.out.println("主线程等等我");
t.join(); // 阻塞在这里,等 t 完成
System.out.println("好的,我回来了");
// 输出
// 主线程等等我
// 我在干活
// 我干完了
// 好的,我回来了
join() 也有超时版本 join(long millis)。
interrupt()
给线程发送中断信号,但不是强制停止,而是设置一个中断标志位。
Thread t = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("我被中断了");
}
});
t.start();
Thread.sleep(1000);
t.interrupt(); // 发送中断信号
// 输出
// 我被中断了
如果线程正在 sleep/wait/join,interrupt() 会立即抛 InterruptedException,线程可以捕获处理。
如果线程正在运行(不在等待状态),interrupt() 只是设置标志,线程可以用 Thread.interrupted() 或 isInterrupted() 来检查:
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) { // 检查中断标志
System.out.println("在干活");
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
System.out.println("停止了");
});
t.start();
Thread.sleep(500);
t.interrupt();
setName() / getName()
给线程起名字,方便调试。
Thread t = new Thread(() -> {
System.out.println("我的名字是:" + Thread.currentThread().getName());
});
t.setName("Worker-1");
t.start();
isDaemon() / setDaemon(boolean)
标记线程为守护线程(daemon thread)。
当所有非守护线程都终止了,程序会自动退出,守护线程也会被强制终止(不会等它们完成)。
通常用于后台任务,比如垃圾回收、日志记录。
Thread daemon = new Thread(() -> {
while (true) {
System.out.println("我在后台跑");
try { Thread.sleep(1000); } catch (Exception e) {}
}
});
daemon.setDaemon(true); // 要在 start() 前设置
daemon.start();
// 主线程 2 秒后结束
Thread.sleep(2000);
System.out.println("主线程完成,程序退出");
// 输出可能是
// 我在后台跑
// 我在后台跑
// 主线程完成,程序退出
// (守护线程被强制终止,不再输出)
已废弃的危险方法
Java 已经废弃(deprecated)了几个 Thread 方法,千万别用:
- stop():强制停止线程,可能导致数据不一致。应该用 interrupt() + 自己检查标志。
- suspend() / resume():暂停/恢复线程,容易导致死锁。
- destroy():销毁线程,几乎不可用。
这些方法为啥被废弃?因为它们都是强制操作,不给线程机会做清理工作,容易破坏数据状态。
一个完整的可中断线程例子
public class GracefulThread extends Thread {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 做一些工作
System.out.println("工作中");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("被中断了");
} finally {
// 清理资源
System.out.println("清理完成");
}
}
}
// 使用
GracefulThread t = new GracefulThread();
t.start();
Thread.sleep(3500);
t.interrupt(); // 礼貌地停止
这样线程能捕获 interrupt 信号,进行必要的清理后才终止。
小结
Java 线程的 7 种状态和状态转换是核心,记住几个要点:
- BLOCKED 是外面排队等锁,WAITING 是里面等通知
- sleep() 不释放锁,wait() 释放锁
- interrupt() 不是强制停止,而是发信号
- 别用 stop/suspend/destroy 这些危险方法
理解透彻这些概念,多线程编程就能避开一大堆坑。
