线程是Java并发编程的核心执行单元,理解其生命周期与状态转换机制,以及interrupt()、wait()、notify()、join()等核心方法的底层原理,是编写高效、稳定并发程序的基础。
一、Java线程的生命周期与状态转换
Java线程的状态由java.lang.Thread.State枚举定义,共包含6种状态,这些状态在特定条件下会相互转换,形成完整的生命周期流转。
1.1 线程状态详解
NEW(新建)
线程对象已创建,但尚未调用start()方法启动。此时线程仅在JVM内存中存在,未分配CPU时间片,也未开始执行。
RUNNABLE(可运行)
线程已调用start()方法启动,处于可执行状态。该状态包含两种子状态:
- Ready:线程已准备好,等待CPU时间片调度
- Running:线程正在CPU上执行
Java将这两种子状态统一称为RUNNABLE,因为线程在Ready和Running之间的切换由JVM线程调度器控制,应用程序无法直接感知。
BLOCKED(阻塞)
线程因等待监视器锁(synchronized)而被阻塞。当线程尝试进入被其他线程持有的synchronized块/方法时,会进入该状态,直到获取到锁才会转换回RUNNABLE。
WAITING(无限期等待)
线程进入无限期等待状态,需等待其他线程执行特定操作(如notify()/notifyAll())才能被唤醒。进入该状态的场景包括:
- 调用无超时参数的
Object.wait() - 调用无超时参数的
Thread.join() - 调用
LockSupport.park()
TIMED_WAITING(有时限等待)
线程进入有时限的等待状态,超过指定时间后会自动唤醒,或在等待时间内被其他线程唤醒。进入该状态的场景包括:
- 调用
Thread.sleep(long millis) - 调用带超时参数的
Object.wait(long timeout) - 调用带超时参数的
Thread.join(long millis) - 调用
LockSupport.parkNanos(long nanos) - 调用
LockSupport.parkUntil(long deadline)
TERMINATED(终止)
线程执行完毕,生命周期结束。线程进入该状态的原因包括:
run()方法正常执行完毕- 线程执行过程中抛出未捕获的异常
1.2 状态转换全流程
二、核心方法的底层原理与正确使用场景
2.1 interrupt():线程中断机制
底层原理
interrupt()的核心作用是设置线程的中断标志位,而非强制终止线程。线程需要主动检查中断标志位并响应中断,这是一种协作式的中断机制。
相关方法说明:
void interrupt():设置线程的中断标志位为trueboolean isInterrupted():检查线程的中断标志位,不清除标志位static boolean interrupted():检查当前线程的中断标志位,清除标志位(将其重置为false)
当线程处于WAITING或TIMED_WAITING状态时,调用interrupt()会抛出InterruptedException,并清除中断标志位,因此在捕获该异常后,通常需要重新设置中断标志位,以便上层逻辑感知。
正确使用场景
interrupt()主要用于优雅地停止线程,避免使用已废弃的stop()方法(该方法会强制终止线程,导致资源未释放、数据不一致等问题)。
代码实例
package com.jam.demo;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class InterruptDemo implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
log.info("线程正在执行任务");
} catch (InterruptedException e) {
log.info("捕获到InterruptedException,重置中断标志位");
Thread.currentThread().interrupt();
}
}
log.info("线程响应中断,结束运行");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new InterruptDemo(), "ken-interrupt-thread");
thread.start();
Thread.sleep(3500);
log.info("主线程调用interrupt()中断子线程");
thread.interrupt();
}
}
2.2 wait()、notify()、notifyAll():线程协作
底层原理
这三个方法是java.lang.Object类的native方法,需与synchronized关键字配合使用,核心是基于对象的监视器(Monitor)实现线程间的等待/通知机制。
-
Monitor结构:每个对象都关联一个Monitor,包含两个队列:
- Entry Set(入口集) :存放等待获取对象锁的线程(对应BLOCKED状态)
- Wait Set(等待集) :存放调用wait()后进入等待的线程(对应WAITING/TIMED_WAITING状态)
-
wait()原理:
- 释放当前持有的对象锁
- 将当前线程加入Wait Set
- 线程进入WAITING/TIMED_WAITING状态
-
notify()原理:
- 从Wait Set中随机选择一个线程唤醒
- 被唤醒的线程从Wait Set移动到Entry Set
- 被唤醒的线程等待获取对象锁,获取后进入RUNNABLE状态
-
notifyAll()原理:
- 唤醒Wait Set中的所有线程
- 所有被唤醒的线程从Wait Set移动到Entry Set
- 这些线程竞争对象锁,获取到锁的线程进入RUNNABLE状态
注意事项
- 必须在synchronized块/方法中调用:否则会抛出
IllegalMonitorStateException - 循环检查条件:避免虚假唤醒(Spurious Wakeups),即线程可能在没有被notify()/notifyAll()的情况下自动唤醒
- 优先使用notifyAll() :notify()可能导致信号丢失,notifyAll()能确保所有等待线程都有机会被唤醒
正确使用场景
主要用于实现线程间的协作,如生产者-消费者模式、Future任务实现等。
代码实例:生产者-消费者模式
package com.jam.demo;
import lombok.extern.slf4j.Slf4j;
import java.util.LinkedList;
import java.util.Queue;
@Slf4j
public class ProducerConsumerDemo {
private static final int QUEUE_CAPACITY = 5;
private final Queue<Integer> queue = new LinkedList<>();
public void produce() throws InterruptedException {
synchronized (queue) {
while (queue.size() == QUEUE_CAPACITY) {
log.info("队列已满,生产者线程等待");
queue.wait();
}
int product = (int) (Math.random() * 100);
queue.offer(product);
log.info("生产者生产产品:{},当前队列大小:{}", product, queue.size());
queue.notifyAll();
}
}
public void consume() throws InterruptedException {
synchronized (queue) {
while (queue.isEmpty()) {
log.info("队列为空,消费者线程等待");
queue.wait();
}
int product = queue.poll();
log.info("消费者消费产品:{},当前队列大小:{}", product, queue.size());
queue.notifyAll();
}
}
public static void main(String[] args) {
ProducerConsumerDemo demo = new ProducerConsumerDemo();
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
demo.produce();
Thread.sleep(400);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "ken-producer");
Thread consumer1 = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
demo.consume();
Thread.sleep(700);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "ken-consumer-1");
Thread consumer2 = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
demo.consume();
Thread.sleep(900);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "ken-consumer-2");
producer.start();
consumer1.start();
consumer2.start();
}
}
2.3 join():线程等待
底层原理
join()的底层是通过调用wait()实现的,让当前线程等待目标线程执行完毕。当目标线程执行完毕后,JVM会自动调用notifyAll()唤醒所有等待的线程。
核心逻辑(简化版):
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
正确使用场景
主要用于等待一个或多个线程执行完毕后再继续执行当前线程,如并行任务的结果汇总。
代码实例
package com.jam.demo;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread task1 = new Thread(() -> {
try {
log.info("任务1开始执行");
Thread.sleep(2000);
log.info("任务1执行完毕");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "ken-task-1");
Thread task2 = new Thread(() -> {
try {
log.info("任务2开始执行");
Thread.sleep(3000);
log.info("任务2执行完毕");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "ken-task-2");
task1.start();
task2.start();
log.info("主线程等待任务1和任务2执行完毕");
task1.join();
task2.join();
log.info("所有任务执行完毕,主线程继续执行");
}
}
三、易混淆技术点区分
3.1 wait() vs sleep()
| 特性 | wait() | sleep() |
|---|---|---|
| 所属类 | Object | Thread |
| 锁释放 | 释放当前持有的对象锁 | 不释放锁 |
| 使用场景 | 线程间协作 | 暂停当前线程执行 |
| 唤醒条件 | 需被notify()/notifyAll()唤醒,或超时 | 超时时间到,或被interrupt() |
| 异常声明 | 抛出InterruptedException | 抛出InterruptedException |
3.2 notify() vs notifyAll()
- notify() :只唤醒Wait Set中的一个线程,选择策略由JVM实现决定(通常是随机或FIFO)
- notifyAll() :唤醒Wait Set中的所有线程,这些线程会竞争对象锁
在大多数场景下,建议使用notifyAll(),避免信号丢失问题。
3.3 isInterrupted() vs interrupted()
- isInterrupted() :实例方法,检查调用该方法的线程的中断标志位,不清除标志位
- interrupted() :静态方法,检查当前线程的中断标志位, 清除标志位(重置为false)
总结
理解Java线程的生命周期与状态转换,以及interrupt()、wait()、notify()、join()等核心方法的底层原理,是编写高质量并发程序的关键。