作为一名有着十年Java开发经验的老兵,我见过太多因为对线程状态理解不透彻而导致的线上事故。记得有一次,我们的支付系统在高峰期突然卡死,排查后发现是因为开发人员混淆了wait()和sleep()的锁释放机制,导致线程死锁。今天,我就带大家彻底搞懂Java线程的6种状态及其转换机制。
Java线程的6种状态定义在java.lang.Thread.State枚举中:
java复制public enum State {
NEW, // 新建但未启动
RUNNABLE, // 可运行状态(包含就绪和运行)
BLOCKED, // 阻塞等待锁
WAITING, // 无限期等待
TIMED_WAITING,// 有限期等待
TERMINATED; // 终止
}
每种状态的关键特征如下:
| 状态 | 触发条件 | 典型场景 | 恢复条件 |
|---|---|---|---|
| NEW | Thread对象创建但未start() | new Thread() | 调用start() |
| RUNNABLE | 调用start()或阻塞状态恢复 | 线程就绪或运行中 | 时间片用完或进入阻塞 |
| BLOCKED | 尝试获取已被占用的synchronized锁 | 进入同步代码块 | 锁被释放且当前线程抢到锁 |
| WAITING | 调用wait()/join()/park() | 等待通知 | 被notify()/unpark()唤醒 |
| TIMED_WAITING | 调用sleep()/wait(timeout) | 限时等待 | 超时或提前唤醒 |
| TERMINATED | run()执行完毕或异常终止 | 线程结束 | 不可恢复 |
mermaid复制stateDiagram-v2
[*] --> NEW
NEW --> RUNNABLE: start()
RUNNABLE --> BLOCKED: 获取锁失败
RUNNABLE --> WAITING: wait()/join()
RUNNABLE --> TIMED_WAITING: sleep()/wait(timeout)
BLOCKED --> RUNNABLE: 获取锁成功
WAITING --> RUNNABLE: 被notify()
TIMED_WAITING --> RUNNABLE: 超时/被唤醒
RUNNABLE --> TERMINATED: run()结束
BLOCKED状态最容易引发死锁问题。来看一个典型示例:
java复制public class BlockedDemo {
private static final Object lockA = new Object();
private static final Object lockB = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lockA) {
try { Thread.sleep(100); } catch (Exception e) {}
synchronized (lockB) {
System.out.println("Thread1 got both locks");
}
}
}).start();
new Thread(() -> {
synchronized (lockB) {
try { Thread.sleep(100); } catch (Exception e) {}
synchronized (lockA) {
System.out.println("Thread2 got both locks");
}
}
}).start();
}
}
这个经典死锁案例中,两个线程互相持有对方需要的锁,都会进入BLOCKED状态。通过jstack可以看到:
code复制"Thread-1" #12 prio=5 os_prio=0 tid=0x00007f487c0b2000 nid=0x2b03 waiting for monitor entry [0x00007f487b5f6000]
java.lang.Thread.State: BLOCKED (on object monitor)
at DeadlockDemo$2.run(DeadlockDemo.java:25)
- waiting to lock <0x000000076b8b2800> (a java.lang.Object)
- locked <0x000000076b8b2810> (a java.lang.Object)
"Thread-0" #11 prio=5 os_prio=0 tid=0x00007f487c0b0000 nid=0x2b02 waiting for monitor entry [0x00007f487b6f7000]
java.lang.Thread.State: BLOCKED (on object monitor)
at DeadlockDemo$1.run(DeadlockDemo.java:15)
- waiting to lock <0x000000076b8b2810> (a java.lang.Object)
- locked <0x000000076b8b2800> (a java.lang.Object)
这两个等待状态最容易被混淆,我们通过代码来看区别:
java复制public class WaitingDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// WAITING状态示例
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("t1进入WAITING");
lock.wait(); // 无限期等待
System.out.println("t1被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// TIMED_WAITING状态示例
Thread t2 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("t2进入TIMED_WAITING");
lock.wait(3000); // 限时等待
System.out.println("t2自动唤醒或提前被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
Thread.sleep(100);
System.out.println("t1状态: " + t1.getState()); // WAITING
System.out.println("t2状态: " + t2.getState()); // TIMED_WAITING
// 3秒后查看状态
Thread.sleep(3100);
System.out.println("3秒后t1状态: " + t1.getState());
System.out.println("3秒后t2状态: " + t2.getState());
}
}
关键区别总结:
| 特性 | WAITING | TIMED_WAITING |
|---|---|---|
| 触发方法 | wait()/join() | wait(timeout)/sleep() |
| 超时机制 | 无限期等待 | 有限期等待 |
| 锁释放 | 释放锁 | wait(timeout)释放,sleep()不释放 |
| jstack标识 | WAITING (on object monitor) | TIMED_WAITING (sleeping) |
wait()/notify()是线程间通信的基础,必须配合synchronized使用:
java复制public class WaitNotifyDemo {
private static final Object lock = new Object();
private static boolean condition = false;
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
while (!condition) {
try {
System.out.println("等待线程进入WAITING");
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("条件满足,继续执行");
}
}).start();
new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(2000); // 模拟耗时操作
condition = true;
lock.notify();
System.out.println("通知线程发送通知");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
}
}
常见问题:
sleep()会让线程进入TIMED_WAITING状态,但不释放任何锁:
java复制public class SleepDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程1获取锁,即将sleep");
Thread.sleep(3000); // 不释放锁
System.out.println("线程1sleep结束");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
new Thread(() -> {
try {
Thread.sleep(500); // 确保线程1先获取锁
synchronized (lock) {
System.out.println("线程2终于获取到锁");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
输出结果:
code复制线程1获取锁,即将sleep
(等待3秒)
线程1sleep结束
线程2终于获取到锁
jstack是分析线程状态的利器,常用命令:
bash复制# 查找Java进程ID
jps -l
# 生成线程转储
jstack <pid> > thread_dump.txt
# 分析特定状态线程
grep -A 20 "BLOCKED" thread_dump.txt
典型问题特征:
案例1:线程池任务卡死
java复制ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> {
synchronized (sharedResource) {
// 长时间操作
while (!condition) {
sharedResource.wait(); // 可能永远等待
}
}
});
解决方案:
案例2:资源竞争导致性能下降
java复制// 粗粒度锁导致并发度低
public synchronized void process() {
// 耗时操作
}
优化方案:
java复制// 正确的线程终止方式
public class ProperShutdown {
private volatile boolean running = true;
private final Object lock = new Object();
public void run() {
while (running && !Thread.currentThread().isInterrupted()) {
synchronized (lock) {
try {
// 使用wait(timeout)而不是wait()
lock.wait(1000);
// 处理业务逻辑
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
break;
}
}
}
// 清理资源
}
public void shutdown() {
running = false;
synchronized (lock) {
lock.notifyAll(); // 唤醒所有等待线程
}
}
}
Q:为什么wait()必须在同步块中调用?
A:这是Java设计的安全机制。wait()会释放锁,如果在非同步环境下调用:
正确模式:
java复制synchronized(lock) {
while (!condition) { // 必须用while而不是if
lock.wait();
}
// 处理条件满足后的逻辑
}
Q:如何优雅地停止线程?
A:推荐两种方式:
java复制public class GracefulStop implements Runnable {
private volatile boolean stop = false;
public void run() {
while (!stop && !Thread.currentThread().isInterrupted()) {
try {
// 工作逻辑
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
}
}
}
public void stop() {
stop = true;
}
}
java复制ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(task);
// 需要停止时
future.cancel(true); // true表示中断正在执行的线程
陷阱1:虚假唤醒
即使没有notify(),wait()也可能返回。必须用while循环检查条件:
java复制synchronized (lock) {
while (!condition) { // 不能用if
lock.wait();
}
}
陷阱2:锁升级导致的死锁
java复制// 线程1
synchronized (lockA) {
synchronized (lockB) {
// ...
}
}
// 线程2
synchronized (lockB) {
synchronized (lockA) {
// ...
}
}
解决方案:
Lock接口:比synchronized更灵活
java复制Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while (!conditionMet) {
condition.await();
}
// ...
} finally {
lock.unlock();
}
并发集合:ConcurrentHashMap, CopyOnWriteArrayList等
同步工具类:CountDownLatch, CyclicBarrier, Semaphore
场景:电商系统在秒杀活动时出现严重性能问题
分析:
优化:
效果:TPS从200提升到5000+
现象:系统偶尔卡死,必须重启才能恢复
排查步骤:
解决方案:
记住,并发编程的精髓在于:理解各组件的行为边界,设计简单可靠的交互协议,而不是追求复杂的技巧。希望这篇深入解析能帮助你在并发编程的道路上走得更稳更远。