Java线程状态与并发协作机制详解
线程的六种状态解析
在Java中,每个线程在其生命周期中会经历不同的状态。这些状态由java.lang.Thread.State枚举定义,共包括六种:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING 和 TERMINATED。
| 状态 | 触发条件 |
|---|---|
| NEW | 线程对象已创建,但尚未调用start()方法 |
| RUNNABLE | 线程正在JVM中运行,可能正在执行代码,也可能正等待CPU调度 |
| BLOCKED | 线程尝试获取一个被其他线程持有的监视器锁时进入此状态 |
| WAITING | 线程调用wait()、join()或LockSupport.park()后无限期等待其他线程唤醒 |
| TIMED_WAITING | 线程在指定时间内等待,如调用sleep(long)、wait(long)、join(long)等带超时参数的方法 |
| TERMINATED | 线程任务完成或因未捕获异常而终止 |
TIMED_WAITING 状态深入理解
当线程调用Thread.sleep(1000)时,它会进入计时等待状态,期间不参与CPU调度,也不会竞争锁资源。时间结束后自动恢复为RUNNABLE状态,但并不保证立即执行,仍需重新竞争处理器资源。
new Thread(() -> {
try {
System.out.println("线程开始休眠");
Thread.sleep(2000); // 进入TIMED_WAITING
System.out.println("线程苏醒");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
BLOCKED 状态产生场景
多个线程竞争同一把同步锁时,若某一线程持有锁,则其余线程将处于BLOCKED状态,直到锁被释放并成功获取为止。
WAITING 与线程协作
WAITING状态体现的是线程间的协作关系。例如,一个线程调用对象的wait()方法后进入等待,必须由另一个线程在同一对象上调用notify()或notifyAll()才能将其唤醒。
线程间通信:等待唤醒机制
当多个线程操作共享资源且任务不同(如生产者-消费者模型),需要通过协调机制确保数据一致性和执行顺序。
核心方法说明
- wait():释放当前对象锁,使线程进入WAITING状态,等待其他线程通知。
- notify():随机唤醒一个在该对象上等待的线程。
- notifyAll():唤醒所有在该对象上等待的线程。
这三个方法属于Object类,因此任何对象都可以作为锁进行调用。使用时必须满足以下条件:
- 必须在同步上下文中调用(即synchronized块或方法内);
- 调用者必须是对应对象的监视器持有者;
- 通常配合循环判断使用,防止虚假唤醒。
经典案例:生产者与消费者
以包子铺为例,生产者线程制作包子,消费者线程吃包子。两者共享"是否有包子"的状态变量。
public class BunShop {
private boolean hasBun = false;
public synchronized void makeBun() throws InterruptedException {
while (hasBun) {
this.wait(); // 包子存在,生产者等待
}
System.out.println("生产者:开始做包子");
Thread.sleep(1000);
hasBun = true;
this.notifyAll(); // 唤醒消费者
}
public synchronized void eatBun() throws InterruptedException {
while (!hasBun) {
this.wait(); // 没有包子,消费者等待
}
System.out.println("消费者:吃掉包子");
hasBun = false;
this.notifyAll(); // 唤醒生产者
}
}
注意使用while而非if判断条件,避免因多线程唤醒导致的状态不一致问题。
线程池:高效管理并发任务
频繁创建和销毁线程会造成系统资源浪费。线程池通过复用已有线程来提升性能和稳定性。
线程池的优势
- 降低开销:减少线程创建/销毁次数;
- 提高响应速度:任务到达即可执行,无需等待线程初始化;
- 可控性增强:限制最大并发数,防止资源耗尽导致系统崩溃。
核心接口与实现
ExecutorService 是主要的线程池接口,常用创建方式来自工具类 Executors:
newFixedThreadPool(int nThreads):创建固定大小的线程池;newCachedThreadPool():可缓存线程池,适合短期异步任务;newSingleThreadExecutor():单线程池,保证顺序执行任务。
基本使用流程
- 创建线程池:
ExecutorService pool = Executors.newFixedThreadPool(3); - 提交任务:
pool.submit(() -> System.out.println("执行任务")); // Runnable Future<String> result = pool.submit(() -> "返回结果"); // Callable - 关闭线程池:
pool.shutdown(); // 平滑关闭,不再接收新任务 // 或 pool.shutdownNow(); 强制中断
调用shutdown()后,主线程不会立即结束,因为线程池中的线程仍在运行已提交的任务。只有所有任务完成后,JVM才会正常退出。