Java 线程中断机制深度解析:从 API 到底层 C++ 实现
日期 :2026-05-13
标签 :Java并发、线程中断、Interrupt、park/unpark、HotSpot源码、AQS
阅读建议:配合 OpenJDK 源码理解中断标志与阻塞唤醒的协作机制
目录
- 中断的本质:不是停止,而是"协商"
- [三个核心 API 的深入理解](#三个核心 API 的深入理解)
- 中断与阻塞方法的协作
- [从 HotSpot C++ 源码看中断实现](#从 HotSpot C++ 源码看中断实现)
- [中断在 AQS 中的应用](#中断在 AQS 中的应用)
- 中断的三种响应模式
- 实战:如何正确停止线程
- 常见误区与面试题
一、中断的本质:不是停止,而是"协商"
1.1 中断的设计哲学
Java 的线程中断(Interrupt)不是强制终止 ,而是一种协作机制:
调用者:"嘿,我希望你停下来"(设置中断标志)
被调用者:"好的,我处理完手头的事就停"(检查标志并响应)
或 "现在不行,我在忙"(忽略标志)
或 "我先清理资源再停"(捕获异常后退出)
核心原则 :线程是否停止,由被中断的线程自己决定。
1.2 与 Thread.stop() 的区别
| 特性 | Thread.stop()(已废弃) |
Thread.interrupt()(推荐) |
|---|---|---|
| 机制 | 强制抛出 ThreadDeath 错误 |
仅设置标志位 |
| 安全性 | ❌ 资源泄漏、数据不一致 | ✅ 线程自主决定何时停止 |
| 锁状态 | 强制释放所有锁 | 不自动释放锁 |
| 使用建议 | 永远不要使用 | 标准协作式停止 |
二、三个核心 API 的深入理解
2.1 interrupt():发起中断请求
java
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 正常工作,定期检查中断标志
System.out.println("工作中...");
}
System.out.println("检测到中断,优雅退出");
});
worker.start();
Thread.sleep(100); // 让 worker 跑一会儿
worker.interrupt(); // 设置中断标志(不会强制停止!)
}
}
输出:
工作中...
工作中...
工作中...
检测到中断,优雅退出
2.2 isInterrupted():查询中断状态(不清除)
java
Thread t = Thread.currentThread();
t.interrupt(); // 设置中断标志
System.out.println(t.isInterrupted()); // true
System.out.println(t.isInterrupted()); // true(不清除!)
System.out.println(t.isInterrupted()); // true(一直 true)
2.3 Thread.interrupted():查询并清除中断状态
java
Thread t = Thread.currentThread();
t.interrupt(); // 设置中断标志
System.out.println(Thread.interrupted()); // true(清除标志)
System.out.println(Thread.interrupted()); // false(已清除)
关键区别:
| 方法 | 作用对象 | 是否清除标志 | 使用场景 |
|---|---|---|---|
isInterrupted() |
实例方法(任意线程) | ❌ 不清除 | 轮询检查中断状态 |
Thread.interrupted() |
静态方法(当前线程) | ✅ 清除 | 阻塞方法内部清理标志 |
三、中断与阻塞方法的协作
3.1 阻塞方法的中断响应
当线程调用阻塞方法时,如果收到中断请求,会立即退出阻塞 并抛出 InterruptedException。
java
public class BlockedInterruptDemo {
public static void main(String[] args) {
Thread sleeper = new Thread(() -> {
try {
System.out.println("准备睡 10 秒...");
Thread.sleep(10000); // 阻塞方法
System.out.println("睡醒了"); // 不会执行到这里
} catch (InterruptedException e) {
// 【关键】sleep 被中断后:
// 1. 立即抛出 InterruptedException
// 2. 清除中断标志(isInterrupted() 返回 false)
System.out.println("睡眠被中断!中断标志:" +
Thread.currentThread().isInterrupted()); // false
}
});
sleeper.start();
try { Thread.sleep(100); } catch (InterruptedException e) {}
sleeper.interrupt(); // 中断睡眠中的线程
}
}
输出:
准备睡 10 秒...
睡眠被中断!中断标志:false
3.2 会响应中断的阻塞方法
| 方法/类 | 中断响应行为 |
|---|---|
Thread.sleep() |
抛出 InterruptedException,清除中断标志 |
Object.wait() |
抛出 InterruptedException,清除中断标志,重新获取锁后退出 |
BlockingQueue.take()/put() |
抛出 InterruptedException |
Lock.lockInterruptibly() |
抛出 InterruptedException,未获取锁 |
Condition.await() |
抛出 InterruptedException |
Future.get() |
抛出 InterruptedException |
Selector.select() |
立即返回,不抛异常(通过 wakeup() 机制) |
Socket I/O |
旧版不响应,NIO 的 InterruptibleChannel 会响应 |
3.3 不响应中断的阻塞方法
java
// 1. synchronized 阻塞(不会响应中断!)
synchronized (obj) { // 如果获取不到锁,会一直 BLOCKED,不响应中断
// ...
}
// 2. 普通 I/O(InputStream.read())
inputStream.read(); // 阻塞时不响应中断,直到 OS 层 I/O 完成
// 3. 无限循环(不检查中断标志)
while (true) { // 永远不会退出!
// 不调用 isInterrupted() 或阻塞方法
}
四、从 HotSpot C++ 源码看中断实现
4.1 Thread 类的中断标志
src/hotspot/share/runtime/thread.hpp
cpp
class Thread: public ThreadShadow {
protected:
// ... 其他字段
// 中断状态(ParkEvent 内部也有中断状态)
volatile jint _osthread_interrupt_state; // OS 层面的中断标志
// ParkEvent:用于 park/unpark 阻塞
ParkEvent * _ParkEvent; // 普通 park
ParkEvent * _SleepEvent; // sleep 专用
ParkEvent * _MutexEvent; // 互斥量
public:
// 设置中断状态
void set_interrupt_thread(bool interrupt_thread) {
_osthread_interrupt_state = interrupt_thread ? 1 : 0;
}
// 查询中断状态
bool is_interrupted(bool clear_interrupted) {
bool interrupted = _osthread_interrupt_state != 0;
if (interrupted && clear_interrupted) {
_osthread_interrupt_state = 0; // 清除标志
// 同时清除 ParkEvent 的中断状态
_ParkEvent->reset_interrupt();
}
return interrupted;
}
};
4.2 Thread::interrupt() 的 C++ 实现
src/hotspot/share/runtime/thread.cpp
cpp
void Thread::interrupt(Thread* thread) {
// 1. 获取线程的 ParkEvent
ParkEvent * const park_event = thread->_ParkEvent;
// 2. 设置 OS 层面的中断标志
thread->set_interrupt_thread(true);
// 3. 【关键】unpark 被阻塞的线程!
// 如果线程正在 park/sleep/wait,立即唤醒它
park_event->unpark();
// 4. 对于 SleepEvent 也执行 unpark
ParkEvent * const sleep_event = thread->_SleepEvent;
if (sleep_event != NULL) {
sleep_event->unpark();
}
// 5. 对于 Solaris 的 _MutexEvent 也 unpark
// ...
}
核心逻辑:
- 设置
_osthread_interrupt_state = 1 - 调用
unpark()唤醒正在阻塞的线程 - 被唤醒的线程检查中断标志,抛出
InterruptedException
4.3 Thread.sleep() 的底层实现
src/hotspot/share/runtime/thread.cpp
cpp
void Thread::sleep(jlong millis) {
assert(millis >= 0, "negative sleep time");
// 1. 获取当前线程的 SleepEvent
ParkEvent * const sleep_event = _SleepEvent;
// 2. 检查是否已被中断
if (is_interrupted(true)) { // 查询并清除
throw new InterruptedException();
}
// 3. 设置超时时间
jlong prevtime = os::javaTimeMillis();
for (;;) {
// 4. 调用 park 阻塞(带超时)
// 底层:Linux futex(FUTEX_WAIT, timeout)
sleep_event->park(millis);
// 5. 被唤醒后,检查原因:
if (is_interrupted(true)) { // 被 interrupt() 唤醒
throw new InterruptedException(); // 抛出异常!
}
// 6. 检查是否超时
jlong newtime = os::javaTimeMillis();
millis -= (newtime - prevtime);
if (millis <= 0) {
return; // 正常超时返回
}
prevtime = newtime;
}
}
4.4 Object.wait() 的中断处理
src/hotspot/share/runtime/objectMonitor.cpp
cpp
void ObjectMonitor::wait(jlong millis, bool interruptable, TRAPS) {
JavaThread* self = THREAD;
// 1. 检查中断状态(进入 wait 前)
if (interruptable && Thread::is_interrupted(self, true)) {
throw new InterruptedException();
}
// 2. 构造等待节点,加入 _WaitSet
ObjectWaiter node(self);
node.set_state(ObjectWaiter::TS_WAIT);
AddWaiter(&node);
// 3. 释放锁(monitor exit)
exit(true, self);
// 4. 进入阻塞(park)
if (node._notified == 0) { // 未被 notify 唤醒
self->_ParkEvent->park(millis); // 阻塞等待
}
// 5. 被唤醒后,重新竞争锁
// 注意:interrupt() 也会 unpark,所以这里要检查
// 6. 【关键】获取锁后,检查中断状态
if (interruptable && Thread::is_interrupted(self, true)) {
// 从 _WaitSet 移除已在 enterI 中处理
throw new InterruptedException(); // 抛出异常!
}
// 7. 正常退出(被 notify 唤醒且未中断)
}
关键发现:
wait()被中断后,会先重新获取锁 ,再抛出InterruptedException- 这就是为什么
wait()必须在synchronized块内:中断后需要锁来安全清理状态
4.5 LockSupport.park() 与中断
src/hotspot/share/runtime/park.cpp
cpp
class ParkEvent : public os::PlatformEvent {
private:
volatile int _event; // 许可(permit):0=无许可,1=有许可
volatile int _interrupted; // 中断状态
public:
// park:阻塞当前线程,直到被 unpark 或中断
void park() {
// 1. 检查是否有许可(先消费许可)
if (Atomic::xchg(0, &_event) > 0) {
return; // 有许可,直接返回(不阻塞)
}
// 2. 检查中断状态
if (_interrupted != 0) {
_interrupted = 0;
return; // 被中断,直接返回(不抛异常!)
}
// 3. 调用 OS 阻塞(Linux: futex)
// 线程状态改为 WAITING
ThreadBlockInVM tbivm(JavaThread::current());
// 4. 进入 OS 等待
os::PlatformEvent::park();
// 5. 被唤醒后,清除事件状态
_event = 0;
}
// unpark:给许可,唤醒线程
void unpark() {
// 1. 设置许可
if (Atomic::xchg(1, &_event) >= 0) {
return; // 之前已有许可,不需要唤醒
}
// 2. 唤醒 OS 层面的等待
os::PlatformEvent::unpark();
}
// 中断处理
void interrupt() {
_interrupted = 1; // 设置中断标志
unpark(); // 唤醒线程
}
};
park() 与中断的关键区别:
| 场景 | 行为 |
|---|---|
Thread.sleep() + 中断 |
抛出 InterruptedException,清除中断标志 |
Object.wait() + 中断 |
抛出 InterruptedException,清除中断标志,重新获取锁后退出 |
LockSupport.park() + 中断 |
**不抛异常!**直接返回,中断标志不清除(需手动处理) |
BlockingQueue.take() + 中断 |
抛出 InterruptedException |
五、中断在 AQS 中的应用
5.1 AQS 对中断的处理模式
AbstractQueuedSynchronizer 提供了两种获取锁的方式:
java
// 1. 不响应中断(忽略中断,继续排队)
lock.lock(); // AQS::acquire()
// 2. 响应中断(收到中断立即退出)
lock.lockInterruptibly(); // AQS::acquireInterruptibly()
5.2 acquireInterruptibly() 源码
src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java
java
public final void acquireInterruptibly(int arg) throws InterruptedException {
// 1. 先检查中断(快速失败)
if (Thread.interrupted()) {
throw new InterruptedException();
}
// 2. 尝试获取锁
if (!tryAcquire(arg)) {
// 3. 获取失败,进入可中断的排队逻辑
doAcquireInterruptibly(arg);
}
}
private void doAcquireInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE); // 加入队列
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
// 尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
return;
}
// 【关键】park 前检查中断!
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) { // ← 这里响应中断
// 被中断唤醒,抛出异常,退出排队!
throw new InterruptedException();
}
}
} finally {
if (failed) {
cancelAcquire(node); // 取消获取,清理队列
}
}
}
// park 并检查中断
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 阻塞
return Thread.interrupted(); // 返回并清除中断状态
}
5.3 acquire()(不响应中断)对比
java
private void doAcquire(int arg) {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
boolean interrupted = false; // 记录中断,但不响应
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
// 【关键】返回前恢复中断标志!
if (interrupted) {
selfInterrupt(); // Thread.currentThread().interrupt();
}
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) {
// 【关键】收到中断,不退出,只记录!
interrupted = true;
}
}
} finally {
if (failed) cancelAcquire(node);
}
}
两种模式的区别:
| 模式 | 收到中断 | 行为 |
|---|---|---|
lock() |
记录但不响应 | 继续排队,获取锁后恢复中断标志 |
lockInterruptibly() |
立即响应 | 抛出 InterruptedException,退出排队 |
六、中断的三种响应模式
模式一:立即停止(Fast Fail)
java
public void task() {
while (!Thread.currentThread().isInterrupted()) {
try {
doWork();
} catch (InterruptedException e) {
// 收到中断,立即清理并退出
cleanup();
break; // 或 return;
}
}
}
模式二:延迟停止(Finish Current)
java
public void task() {
while (!Thread.currentThread().isInterrupted()) {
try {
doWork();
} catch (InterruptedException e) {
// 完成当前批次后再退出
finishCurrentBatch();
cleanup();
break;
}
}
}
模式三:忽略中断(Non-Interruptible)
java
public void task() {
while (true) {
try {
doWork();
} catch (InterruptedException e) {
// 忽略中断,继续工作(不推荐!)
// 至少要恢复中断标志:Thread.currentThread().interrupt();
}
}
}
七、实战:如何正确停止线程
7.1 生产者-消费者模型的优雅停止
java
public class GracefulShutdown {
private final BlockingQueue<Task> queue = new LinkedBlockingQueue<>(100);
private volatile boolean running = true;
// 生产者线程
class Producer extends Thread {
@Override
public void run() {
while (running && !Thread.currentThread().isInterrupted()) {
try {
Task task = produceTask();
queue.put(task); // 阻塞方法,会响应中断
} catch (InterruptedException e) {
// 收到停止信号
System.out.println("Producer 收到中断,停止生产");
running = false; // 通知其他线程
break;
}
}
}
}
// 消费者线程
class Consumer extends Thread {
@Override
public void run() {
while (running || !queue.isEmpty()) {
try {
// poll 带超时,定期检查 running 状态
Task task = queue.poll(100, TimeUnit.MILLISECONDS);
if (task != null) {
process(task);
}
} catch (InterruptedException e) {
System.out.println("Consumer 收到中断");
running = false;
break;
}
}
// 处理完队列剩余任务
System.out.println("Consumer 处理完剩余 " + queue.size() + " 个任务后退出");
}
}
// 优雅停止方法
public void shutdown() {
running = false; // 1. 先设置标志,让生产者自然停止
// 2. 中断所有工作线程(唤醒阻塞中的线程)
producer.interrupt();
consumer.interrupt();
// 3. 等待线程结束
try {
producer.join(5000);
consumer.join(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 4. 强制停止(如果还没结束)
if (producer.isAlive()) producer.interrupt();
if (consumer.isAlive()) consumer.interrupt();
}
}
7.2 线程池的优雅关闭
java
ExecutorService executor = Executors.newFixedThreadPool(4);
// 提交任务
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
doWork();
}
});
}
// 优雅关闭
executor.shutdown(); // 1. 不再接受新任务
executor.awaitTermination(60, TimeUnit.SECONDS); // 2. 等待现有任务完成
executor.shutdownNow(); // 3. 超时后中断所有线程
shutdownNow() 的底层实现:
java
public List<Runnable> shutdownNow() {
// 1. 设置线程池状态为 STOP
advanceRunState(STOP);
// 2. 中断所有工作线程
interruptWorkers(); // 遍历 workers,调用 thread.interrupt()
// 3. 返回未执行的任务列表
return tasks;
}
八、常见误区与面试题
8.1 常见误区
| 误区 | 真相 |
|---|---|
interrupt() 会立即停止线程 |
❌ 只是设置标志,线程自主决定何时停止 |
捕获 InterruptedException 后线程就停了 |
❌ 只是退出阻塞,是否停止看代码逻辑 |
isInterrupted() 会清除标志 |
❌ 不清除;Thread.interrupted() 才清除 |
| 所有阻塞方法都会响应中断 | ❌ synchronized、普通 I/O 不响应 |
| 中断标志被清除后无法恢复 | ❌ 可以手动调用 Thread.currentThread().interrupt() 恢复 |
8.2 面试高频题
Q1:interrupt() 和 isInterrupted() 的区别?
interrupt():设置目标线程的中断标志(实例方法)
isInterrupted():查询目标线程的中断状态,不清除(实例方法)
Thread.interrupted():查询当前线程的中断状态,并清除(静态方法)
Q2:为什么 sleep() 被中断后要清除中断标志?
设计约定:抛出 InterruptedException 时,中断标志已被"消费",所以清除。
如果不清除,调用者捕获异常后再次检查 isInterrupted() 会困惑(到底中断了没?)
Q3:LockSupport.park() 被中断后为什么不抛异常?
park/unpark 是底层原语,设计为不抛异常(C++ 层面直接返回)。
Java 层需要手动检查中断状态:if (Thread.interrupted()) { ... }
这是 AQS 的设计基础:park 被 unpark/interrupt 唤醒后,统一检查中断。
Q4:如何正确恢复中断标志?
java
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 捕获后中断标志被清除,如果需要传播中断:
Thread.currentThread().interrupt(); // 重新设置标志!
// 或者:
throw new RuntimeException(e); // 包装后抛出
}
Q5:Future.cancel(true) 是怎么中断线程的?
java
public boolean cancel(boolean mayInterruptIfRunning) {
if (state != NEW) return false;
if (mayInterruptIfRunning) {
// 中断执行任务的线程
runner.interrupt(); // ← 调用 Thread.interrupt()
}
finishCompletion(); // 唤醒等待 Future.get() 的线程
return true;
}
总结:中断机制的核心要点
┌─────────────────────────────────────────────────────────────┐
│ 线程中断机制全景图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Java 层:Thread.interrupt() │
│ ↓ │
│ JVM 层:Thread::interrupt() (C++) │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 设置 _osthread_interrupt_state = 1 │ │
│ │ 2. 调用 ParkEvent->unpark() 唤醒阻塞线程 │ │
│ │ 3. 被唤醒的线程检查中断状态,决定行为 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 阻塞方法响应: │
│ - sleep/wait/take → 抛 InterruptedException(清除标志) │
│ - park → 直接返回(不抛异常,不清除标志) │
│ - synchronized/普通 I/O → 不响应 │
│ ↓ │
│ 应用层处理模式: │
│ - 立即停止:catch 后 break/return │
│ - 延迟停止:完成当前工作后再退出 │
│ - 恢复标志:Thread.currentThread().interrupt() │
│ │
└─────────────────────────────────────────────────────────────┘
参考源码路径
src/hotspot/share/runtime/thread.hpp # Thread 类定义
src/hotspot/share/runtime/thread.cpp # interrupt() 实现
src/hotspot/share/runtime/park.cpp # ParkEvent 实现
src/hotspot/share/runtime/objectMonitor.cpp # wait() 中断处理
src/java.base/share/classes/java/lang/Thread.java # Java API
src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java # AQS