Java 线程中断机制深度解析:从 API 到底层 C++ 实现

Java 线程中断机制深度解析:从 API 到底层 C++ 实现

日期 :2026-05-13
标签 :Java并发、线程中断、Interrupt、park/unpark、HotSpot源码、AQS
阅读建议:配合 OpenJDK 源码理解中断标志与阻塞唤醒的协作机制


目录

  1. 中断的本质:不是停止,而是"协商"
  2. [三个核心 API 的深入理解](#三个核心 API 的深入理解)
  3. 中断与阻塞方法的协作
  4. [从 HotSpot C++ 源码看中断实现](#从 HotSpot C++ 源码看中断实现)
  5. [中断在 AQS 中的应用](#中断在 AQS 中的应用)
  6. 中断的三种响应模式
  7. 实战:如何正确停止线程
  8. 常见误区与面试题

一、中断的本质:不是停止,而是"协商"

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
  // ...
}

核心逻辑

  1. 设置 _osthread_interrupt_state = 1
  2. 调用 unpark() 唤醒正在阻塞的线程
  3. 被唤醒的线程检查中断标志,抛出 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

相关推荐
Brilliantwxx1 小时前
【C++】priority_queue以及 仿函数 的学习
开发语言·c++·笔记·学习·算法
风味蘑菇干1 小时前
斗地主案例
java·数据结构·算法
码农学院1 小时前
itextsharp .net中如何设置两个表格的间距设为0,取网站的域名,协议、端口、当前站点目录的地址
开发语言·c#·.net
宠..1 小时前
VS Code 修改 C++ 标准同时修改错误检测标准
java·linux·开发语言·javascript·c++·python·qt
WL_Aurora1 小时前
Java Scanner输入陷阱深度解析
java·开发语言
一只大袋鼠1 小时前
SpringMVC 框架中的拦截器
java·springmvc·javaweb·拦截器
Han_han9191 小时前
斗地主案例:
java·开发语言
阿丰资源1 小时前
基于SpringBoot的电影评论网站(含源码)
java·spring boot·后端
小码哥0681 小时前
2026版基于springboot的家政服务预约系统
java·spring boot·后端