JUC并发编程 线程中断与等待通知

13 阅读8分钟

线程中断与等待通知

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的原因:

  1. 标准化:Java官方提供的标准机制
  2. 异常处理:能够中断阻塞操作
  3. 语义清晰:明确表达中断意图

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 执行步骤详解
  1. WaitThread获取锁:通过synchronized(lock)获取对象监视器锁
  2. 检查条件:while(!flag)检查等待条件
  3. 调用wait():释放锁并进入等待状态,线程被挂起
  4. NotifyThread获取锁:等待线程释放锁后,通知线程获取锁
  5. 修改条件:设置flag = true,改变等待条件
  6. 调用notify():唤醒一个等待线程(但不立即释放锁)
  7. 释放锁:synchronized代码块结束,释放锁
  8. 重新竞争锁:被唤醒的线程重新竞争锁
  9. 重新检查条件:获取锁后重新检查while条件
  10. 继续执行:条件满足,跳出循环继续执行

关键注意事项:

  • 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 执行步骤详解
  1. 获取锁:调用lock.lock()获取ReentrantLock锁
  2. 检查条件:在while循环中检查等待条件
  3. 调用await():条件不满足时调用condition.await()释放锁并等待
  4. 线程挂起:当前线程进入Condition的等待队列
  5. 其他线程获取锁:等待线程释放锁后,其他线程可以获取锁
  6. 修改条件:获取锁的线程修改共享状态
  7. 发送信号:调用condition.signal()或signalAll()唤醒等待线程
  8. 释放锁:在finally块中调用unlock()释放锁
  9. 重新竞争锁:被唤醒的线程重新竞争锁
  10. 重新检查条件:获取锁后重新检查while条件
  11. 继续执行:条件满足时跳出循环继续执行

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 关键记忆点

  1. 中断不是强制停止:只是设置标志位,需要线程协作
  2. 阻塞方法会抛异常:sleep、wait等方法会立即响应中断
  3. 异常会清除标志位:需要在catch块中重新设置
  4. interrupted()会清除状态:静态方法会重置标志位
  5. LockSupport更灵活:无锁要求,支持先唤醒后等待
  6. permit最多为1:多次unpark不会累积
  7. 及时响应是关键:定期检查中断状态,快速响应

结语

线程中断与等待通知机制是Java并发编程的基础,理解其设计理念和工作原理对于编写高质量的并发程序至关重要。从操作系统的信号机制到Java的协作式中断,从传统的wait/notify到现代的LockSupport,每种机制都有其适用场景和最佳实践。

掌握这些机制不仅能帮助我们编写更安全、更高效的并发代码,还能在面试和实际工作中游刃有余地处理各种并发问题。记住,并发编程的核心是协作与安全,而不是强制与暴力。