线程中断与等待通知
1. 操作系统层面的线程中断机制
1.1 操作系统信号机制
在操作系统层面,进程间通信和异步事件处理主要通过信号机制实现。操作系统提供了多种信号来控制进程的行为,如SIGTERM、SIGKILL等。
sequenceDiagram
participant OS as 操作系统
participant P1 as 进程A
participant P2 as 进程B
P1->>OS: 发送信号SIGTERM
OS->>P2: 传递信号
P2->>P2: 信号处理器响应
alt 进程配合
P2->>P2: 清理资源
P2->>OS: 正常退出
else 进程不配合
P1->>OS: 发送SIGKILL
OS->>P2: 强制终止
end
1.2 线程级别的中断
与进程级别的信号机制不同,线程级别的中断更加复杂。在多线程环境中,线程之间需要协作来实现安全的中断机制。
flowchart TD
A[线程中断请求] --> B{线程当前状态}
B -->|运行状态| C[设置中断标志位]
B -->|阻塞状态| D[立即抛出异常]
C --> E[线程检查标志位]
E --> F[协作式响应]
D --> G[异常处理]
F --> H[安全退出]
G --> H
2. Java线程中断机制
2.1 设计理念
Java的线程中断机制基于以下核心理念:
graph TD
A[Java线程中断机制] --> B[设计原则]
A --> C[废弃方法]
A --> D[核心思想]
B --> B1[协作式机制]
B --> B2[线程自主决定]
B --> B3[安全优先]
C --> C1[Thread.stop]
C --> C2[Thread.suspend]
C --> C3[Thread.resume]
D --> D1[中断标识协商]
D --> D2[程序员自实现]
D --> D3[无强制语法]
核心原则:
- 一个线程不应该由其他线程来强制中断或停止
- 线程应该由自己决定自己的命运
- 中断只是一种协作协商机制
2.2 中断机制工作原理
sequenceDiagram
participant T1 as 线程A
participant T2 as 线程B
participant Flag as 中断标志位
T1->>T2: interrupt()调用
T2->>Flag: 设置标志位=true
Note over T2: 线程B需要主动检查
loop 线程执行循环
T2->>Flag: isInterrupted()检查
alt 标志位为true
T2->>T2: 执行中断处理逻辑
T2->>T2: 清理资源并退出
else 标志位为false
T2->>T2: 继续正常执行
end
end
2.3 中断状态的两种情况
flowchart TD
A[调用interrupt方法] --> B{目标线程状态}
B -->|正常活动状态| C[设置中断标志位=true]
B -->|阻塞状态| D[立即退出阻塞状态]
C --> E[线程继续正常运行]
C --> F[需要线程主动检查配合]
D --> G[抛出InterruptedException]
G --> H[中断标志位被清除]
3. Java中断核心方法详解
3.1 三大核心API
方法 | 类型 | 功能 | 是否清除标志位 | 详细说明 |
---|---|---|---|---|
interrupt() | 实例方法 | 设置目标线程的中断标志位为true | 否 | 仅设置标志位,发起协商,不会立刻停止线程 |
isInterrupted() | 实例方法 | 检查线程的中断状态 | 否 | 判断当前线程是否被中断,通过检查中断标志位 |
interrupted() | 静态方法 | 检查当前线程中断状态并清除 | 是 | 返回中断状态后将标志位重置为false |
3.2 interrupted()方法的特殊性
sequenceDiagram
participant T as 当前线程
participant Flag as 中断标志位
T->>Flag: Thread.interrupted()调用
Flag-->>T: 返回当前状态(true/false)
Flag->>Flag: 重置为false
Note over Flag: 状态被清除
T->>Flag: 再次调用interrupted()
Flag-->>T: 返回false
3.3 停止线程的三种方法对比
graph TD
subgraph "方法1: volatile变量"
A1[设置volatile boolean flag] --> A2[线程检查flag状态]
A2 --> A3[根据flag决定是否停止]
end
subgraph "方法2: AtomicBoolean"
B1[设置AtomicBoolean flag] --> B2[线程检查flag状态]
B2 --> B3[原子性操作保证线程安全]
end
subgraph "方法3: Thread中断API"
C1[调用interrupt方法] --> C2[检查isInterrupted]
C2 --> C3[处理InterruptedException]
end
推荐使用Thread中断API的原因:
- 标准化:Java官方提供的标准机制
- 异常处理:能够中断阻塞操作
- 语义清晰:明确表达中断意图
4. 线程等待通知机制
4.1 三种等待通知方式概览
graph LR
A[线程等待通知机制] --> B[Object方式]
A --> C[Condition方式]
A --> D[LockSupport方式]
B --> B1[wait]
B --> B2[notify]
B --> B3[notifyAll]
B --> B4[需要synchronized]
C --> C1[await]
C --> C2[signal]
C --> C3[signalAll]
C --> C4[需要Lock]
D --> D1[park]
D --> D2[unpark]
D --> D3[无需锁]
4.2 Object的wait/notify机制
sequenceDiagram
participant T1 as 线程1
participant T2 as 线程2
participant Monitor as 对象监视器
T1->>Monitor: synchronized获取锁
T1->>Monitor: wait()等待
Note over T1: 线程1进入等待状态
T2->>Monitor: synchronized获取锁
T2->>Monitor: notify()唤醒
Monitor->>T1: 唤醒线程1
T1->>Monitor: 重新竞争锁
Object方式的限制条件:
- 必须在synchronized代码块中使用
- 必须先wait后notify
- 顺序不能颠倒
4.2.1 源码实例:生产者消费者模式
public class ObjectWaitNotifyDemo {
private static final Object lock = new Object();
private static boolean flag = false;
public static void main(String[] args) {
// 等待线程
Thread waitThread = new Thread(() -> {
synchronized (lock) {
while (!flag) {
try {
System.out.println(Thread.currentThread().getName() + " 开始等待...");
lock.wait(); // 步骤1:释放锁并等待
System.out.println(Thread.currentThread().getName() + " 被唤醒,继续执行");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + " 执行完毕");
}
}, "WaitThread");
// 通知线程
Thread notifyThread = new Thread(() -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " 获取锁,准备通知");
flag = true; // 步骤2:修改条件
lock.notify(); // 步骤3:唤醒等待线程
System.out.println(Thread.currentThread().getName() + " 通知完成");
// 模拟一些工作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} // 步骤4:释放锁
}, "NotifyThread");
waitThread.start();
// 确保wait线程先执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
notifyThread.start();
}
}
4.2.2 执行步骤详解
- WaitThread获取锁:通过synchronized(lock)获取对象监视器锁
- 检查条件:while(!flag)检查等待条件
- 调用wait():释放锁并进入等待状态,线程被挂起
- NotifyThread获取锁:等待线程释放锁后,通知线程获取锁
- 修改条件:设置flag = true,改变等待条件
- 调用notify():唤醒一个等待线程(但不立即释放锁)
- 释放锁:synchronized代码块结束,释放锁
- 重新竞争锁:被唤醒的线程重新竞争锁
- 重新检查条件:获取锁后重新检查while条件
- 继续执行:条件满足,跳出循环继续执行
关键注意事项:
- wait()必须在while循环中,防止虚假唤醒
- notify()只是唤醒线程,不会立即释放锁
- 被唤醒的线程需要重新竞争锁才能继续执行
4.3 Condition的await/signal机制
sequenceDiagram
participant T1 as 线程1
participant T2 as 线程2
participant Lock as ReentrantLock
participant Cond as Condition
T1->>Lock: lock()获取锁
T1->>Cond: await()等待
Note over T1: 线程1进入等待状态
T2->>Lock: lock()获取锁
T2->>Cond: signal()唤醒
Cond->>T1: 唤醒线程1
T1->>Lock: 重新竞争锁
Condition方式的限制条件:
- 必须在Lock代码块中使用
- 必须先await后signal
- 顺序不能颠倒
4.3.1 源码实例:多条件等待通知
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionAwaitSignalDemo {
private static final ReentrantLock lock = new ReentrantLock();
private static final Condition condition = lock.newCondition();
private static boolean ready = false;
public static void main(String[] args) {
// 等待线程
Thread awaitThread = new Thread(() -> {
lock.lock(); // 步骤1:获取锁
try {
while (!ready) {
System.out.println(Thread.currentThread().getName() + " 开始等待条件...");
condition.await(); // 步骤2:释放锁并等待
System.out.println(Thread.currentThread().getName() + " 被唤醒,重新获取锁");
}
System.out.println(Thread.currentThread().getName() + " 条件满足,继续执行");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println(Thread.currentThread().getName() + " 被中断");
} finally {
lock.unlock(); // 步骤3:释放锁
}
}, "AwaitThread");
// 信号线程
Thread signalThread = new Thread(() -> {
lock.lock(); // 步骤4:获取锁
try {
System.out.println(Thread.currentThread().getName() + " 获取锁,准备发送信号");
ready = true; // 步骤5:修改条件
condition.signal(); // 步骤6:发送信号唤醒等待线程
System.out.println(Thread.currentThread().getName() + " 信号发送完成");
// 模拟一些工作
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " 工作完成,即将释放锁");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock(); // 步骤7:释放锁
}
}, "SignalThread");
awaitThread.start();
// 确保await线程先执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
signalThread.start();
}
}
4.3.2 多条件示例:生产者消费者
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumerWithCondition {
private static final int CAPACITY = 5;
private static final Queue<Integer> queue = new LinkedList<>();
private static final ReentrantLock lock = new ReentrantLock();
private static final Condition notFull = lock.newCondition();
private static final Condition notEmpty = lock.newCondition();
// 生产者
static class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock.lock();
try {
// 队列满时等待
while (queue.size() == CAPACITY) {
System.out.println("队列已满,生产者等待...");
notFull.await();
}
queue.offer(i);
System.out.println("生产者生产:" + i + ",队列大小:" + queue.size());
// 通知消费者
notEmpty.signal();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
// 消费者
static class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock.lock();
try {
// 队列空时等待
while (queue.isEmpty()) {
System.out.println("队列为空,消费者等待...");
notEmpty.await();
}
Integer item = queue.poll();
System.out.println("消费者消费:" + item + ",队列大小:" + queue.size());
// 通知生产者
notFull.signal();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
try {
Thread.sleep(150);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public static void main(String[] args) {
new Thread(new Producer(), "Producer").start();
new Thread(new Consumer(), "Consumer").start();
}
}
4.3.3 执行步骤详解
- 获取锁:调用lock.lock()获取ReentrantLock锁
- 检查条件:在while循环中检查等待条件
- 调用await():条件不满足时调用condition.await()释放锁并等待
- 线程挂起:当前线程进入Condition的等待队列
- 其他线程获取锁:等待线程释放锁后,其他线程可以获取锁
- 修改条件:获取锁的线程修改共享状态
- 发送信号:调用condition.signal()或signalAll()唤醒等待线程
- 释放锁:在finally块中调用unlock()释放锁
- 重新竞争锁:被唤醒的线程重新竞争锁
- 重新检查条件:获取锁后重新检查while条件
- 继续执行:条件满足时跳出循环继续执行
Condition相比Object.wait/notify的优势:
- 支持多个条件变量(如notFull、notEmpty)
- 提供更精确的线程唤醒控制
- 支持超时等待(awaitNanos、awaitUntil)
- 支持不响应中断的等待(awaitUninterruptibly)
- 更好的性能和灵活性
5. LockSupport详解
5.1 LockSupport概述
classDiagram
class LockSupport {
+park() void$
+park(Object blocker) void$
+unpark(Thread thread) void$
+parkNanos(long nanos) void$
+parkUntil(long deadline) void$
}
class Unsafe {
+park(boolean isAbsolute, long time) void
+unpark(Object thread) void
}
LockSupport --> Unsafe : 调用native方法
LockSupport特点:
- 基本线程阻塞原语
- 所有方法都是静态方法
- 可以在任意位置阻塞线程
- 底层调用Unsafe的native代码
5.1.1 基础使用示例
import java.util.concurrent.locks.LockSupport;
public class LockSupportBasicDemo {
public static void main(String[] args) {
Thread parkThread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始执行");
// 步骤1:调用park()阻塞当前线程
System.out.println(Thread.currentThread().getName() + " 准备park,等待许可");
LockSupport.park(); // 阻塞等待许可
// 步骤4:被unpark唤醒后继续执行
System.out.println(Thread.currentThread().getName() + " 被唤醒,继续执行");
}, "ParkThread");
parkThread.start();
// 主线程等待一段时间
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 步骤2:主线程调用unpark唤醒parkThread
System.out.println("主线程准备unpark");
LockSupport.unpark(parkThread); // 步骤3:给parkThread发放许可
System.out.println("主线程unpark完成");
}
}
5.1.2 先unpark后park示例
import java.util.concurrent.locks.LockSupport;
public class LockSupportUnparkFirstDemo {
public static void main(String[] args) {
Thread testThread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + " 准备park");
// 由于之前已经unpark,这里不会阻塞
LockSupport.park();
System.out.println(Thread.currentThread().getName() + " park结束,继续执行");
}, "TestThread");
testThread.start();
// 先unpark,给线程发放许可
System.out.println("主线程先unpark");
LockSupport.unpark(testThread);
try {
testThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
5.2 Permit许可机制
flowchart TD
A[线程调用park] --> B{是否有permit}
B -->|有permit| C[消耗permit]
B -->|无permit| D[阻塞等待]
C --> E[正常继续执行]
F[其他线程调用unpark] --> G[发放permit]
G --> H{permit数量}
H -->|已有1个| I[保持1个不累加]
H -->|没有| J[设置为1个]
D --> K[等待unpark唤醒]
G --> K
K --> L[获得permit继续执行]
Permit机制核心要点:
- 每个线程都有一个相关的permit
- permit最多只有1个
- 重复调用unpark不会累积凭证
- 可以先unpark后park(突破传统限制)
6. 总结与最佳实践
6.1 核心要点总结
graph TD
A[线程中断与等待通知] --> B[中断机制]
A --> C[等待通知]
B --> B1[协作式设计]
B --> B2[三大核心API]
B --> B3[异常处理要点]
B --> B4[状态管理]
C --> C1[Object方式]
C --> C2[Condition方式]
C --> C3[LockSupport方式]
C1 --> C11[需要synchronized]
C1 --> C12[顺序要求严格]
C2 --> C21[需要Lock]
C2 --> C22[功能更丰富]
C3 --> C31[无锁要求]
C3 --> C32[使用灵活]
C3 --> C33[permit机制]
6.2 最佳实践建议
6.2.1 中断处理最佳实践
// ✅ 正确的中断处理方式
public void interruptibleTask() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
doWork();
}
} catch (InterruptedException e) {
// 重新设置中断状态
Thread.currentThread().interrupt();
// 清理资源
cleanup();
}
}
6.2.2 等待通知方式选择
场景 | 推荐方式 | 理由 |
---|---|---|
简单的等待通知 | LockSupport | 使用简单,无锁要求 |
需要多个条件等待 | Condition | 支持多个条件变量 |
与synchronized配合 | Object.wait/notify | 保持一致性 |
需要超时控制 | LockSupport.parkNanos | 精确的时间控制 |
6.3 常见陷阱与避免方法
flowchart TD
A[常见陷阱] --> B[忽略InterruptedException]
A --> C[错误使用interrupted方法]
A --> D[permit机制理解错误]
A --> E[顺序依赖问题]
B --> B1[重新设置中断状态]
C --> C1[区分静态和实例方法]
D --> D1[理解permit上限为1]
E --> E1[使用LockSupport避免顺序问题]
style B fill:#ffcccc
style C fill:#ffcccc
style D fill:#ffcccc
style E fill:#ffcccc
style B1 fill:#ccffcc
style C1 fill:#ccffcc
style D1 fill:#ccffcc
style E1 fill:#ccffcc
6.4 关键记忆点
- 中断不是强制停止:只是设置标志位,需要线程协作
- 阻塞方法会抛异常:sleep、wait等方法会立即响应中断
- 异常会清除标志位:需要在catch块中重新设置
- interrupted()会清除状态:静态方法会重置标志位
- LockSupport更灵活:无锁要求,支持先唤醒后等待
- permit最多为1:多次unpark不会累积
- 及时响应是关键:定期检查中断状态,快速响应
结语
线程中断与等待通知机制是Java并发编程的基础,理解其设计理念和工作原理对于编写高质量的并发程序至关重要。从操作系统的信号机制到Java的协作式中断,从传统的wait/notify到现代的LockSupport,每种机制都有其适用场景和最佳实践。
掌握这些机制不仅能帮助我们编写更安全、更高效的并发代码,还能在面试和实际工作中游刃有余地处理各种并发问题。记住,并发编程的核心是协作与安全,而不是强制与暴力。