你好,我是一航同学,AI发展很快,快到我们已经忽略了很多基础知识的构建。俗话说,基础不牢地动山摇。在快速迭代的AI时代,请沉下心,耐心看完,查缺补漏。

Java初级并发知识【3】

线程生命周期

核心概念

概念 核心要点 记忆口诀
线程状态 6 种状态:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED “新建→就绪→运行→等待→阻塞→死亡”
sleep vs wait sleep 不释放锁 + 时间到自动醒;wait 释放锁 + 需被唤醒 “sleep 是’暂停’,wait 是’让位’”
wait/notify 必须在同步块中使用,配合 while 循环防虚假唤醒 “等条件→wait();改条件→notify()”

线程状态是"结果",不是"原因"。 我们通过调用方法(如 wait()sleep())让线程进入某状态,而不是直接"设置状态"。


生活类比:地铁安检 + 候车室

1. 6 种状态类比
状态 类比 说明
NEW(新建) 乘客买了票,还没进地铁站 new Thread() 后未 start()
RUNNABLE(可运行) 乘客在安检口排队(就绪)或正在过安检(执行) 包含"就绪"和"运行"两种子状态,由 OS 调度决定
BLOCKED(阻塞) 安检口被占用,乘客等待 专指等待 synchronized 锁,不是所有等待都叫 BLOCKED
WAITING(等待) 乘客在候车室等广播通知 无超时等待:wait() / join()
TIMED_WAITING 乘客设了 10 分钟闹钟小睡 有超时等待:sleep(ms) / wait(ms)
TERMINATED(终止) 乘客出站,旅程结束 run() 执行完或异常退出

WAITING vs TIMED_WAITING: 区别仅在于有没有设置超时时间。

2. wait/notify 角色类比
角色 类比 对应概念
乘客 A 线程 A 执行 wait() 进入候车室(释放锁 + 等待)
广播员 线程 B 执行 notify() 唤醒候车室乘客(但不释放当前锁)
候车条件 while(!条件) 防止"虚假唤醒":醒了但条件仍不满足,继续等

wait/notify = 候车室广播系统:

  • wait() 必须配合 while 循环,不能用 if(防止虚假唤醒)
  • notify() 随机唤醒一个等待线程,notifyAll() 唤醒所有(更安全)
  • 唤醒后线程要重新竞争锁,不是立即执行

Java 代码视角

1. 线程 6 大状态 + 触发方法总览
public enum State {
    NEW,                    // new Thread() 后,未 start()
    RUNNABLE,               // start() 后,或运行中/就绪中
    BLOCKED,                // 等待 synchronized 锁(进入同步块时锁被占用)
    WAITING,                // 无超时等待:Object.wait() / Thread.join() / LockSupport.park()
    TIMED_WAITING,          // 有超时等待:Thread.sleep(ms) / Object.wait(ms) / join(ms)
    TERMINATED              // run() 方法执行完,或异常退出
}
public class StateDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            // ① RUNNABLE:正在执行
            System.out.println("当前状态: " + Thread.currentThread().getState());

            // ② TIMED_WAITING:sleep
            try { Thread.sleep(1000); } catch (InterruptedException e) {}

            // ③ BLOCKED:等待 synchronized 锁
            synchronized (StateDemo.class) {
                // 临界区代码
            }

            // ④ WAITING:wait()
            synchronized (StateDemo.class) {
                try { StateDemo.class.wait(); } catch (InterruptedException e) {}
            }
        });

        // ⑤ NEW:刚创建
        System.out.println("NEW: " + thread.getState());

        thread.start();
        // ⑥ RUNNABLE:start() 后
        System.out.println("RUNNABLE: " + thread.getState());

        thread.join();
        // ⑦ TERMINATED:执行完
        System.out.println("TERMINATED: " + thread.getState());
    }
}

实际开发中很少直接判断线程状态,但需要能看懂 jstack 日志中的状态,用于排查死锁/卡顿。

2. sleep() vs wait()
对比项 Thread.sleep(ms) Object.wait(ms)
所属类 Thread 静态方法 Object 实例方法
锁的处理 不释放锁 释放当前持有的锁
唤醒方式 时间到自动唤醒 notify()/notifyAll() 或超时
使用范围 任意位置 必须在 synchronized 块/方法中
异常处理 需捕获 InterruptedException 需捕获 InterruptedException
典型场景 模拟延迟、轮询间隔 线程协作、生产者消费者
// sleep:不释放锁,其他线程进不来
synchronized (lock) {
    System.out.println("A: 持有锁,开始sleep");
    Thread.sleep(2000);  // 锁仍被 A 持有
    System.out.println("A: sleep结束");
}

// wait:释放锁,其他线程可以进入
synchronized (lock) {
    System.out.println("B: 持有锁,开始wait");
    lock.wait(2000);  // 释放锁,进入等待
    System.out.println("B: 被唤醒,重新竞争锁");
}

不要在循环外调用 wait() 防止"虚假唤醒"(线程被唤醒但条件仍不满足)。

3. wait/notify 正确用法:生产者-消费者简化版
public class ProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int MAX_SIZE = 5;

    // 生产者
    public void produce(int value) throws InterruptedException {
        synchronized (this) {
            while (queue.size() >= MAX_SIZE) {  // 关键:用 while 判断条件,不是 if!
                System.out.println("队列满,生产者等待...");
                this.wait();  // 释放锁,等待消费者通知
            }
            queue.offer(value);
            System.out.println("生产: " + value);
            this.notifyAll();  // 通知所有等待的线程(消费者)
        }
    }

    // 消费者
    public int consume() throws InterruptedException {
        synchronized (this) {
            while (queue.isEmpty()) {
                System.out.println("队列空,消费者等待...");
                this.wait();  // 释放锁,等待生产者通知
            }
            int value = queue.poll();
            System.out.println("消费: " + value);
            this.notifyAll();  // 通知所有等待的线程(生产者)
            return value;
        }
    }
}

为什么用 while 而不是 if?

// 错误:用 if 可能"虚假唤醒"
if (queue.isEmpty()) { wait(); }
// 场景:线程A被 notify() 唤醒,但线程B抢先消费了数据,队列仍空 → A继续执行会报错!

// 正确:用 while 循环检查
while (queue.isEmpty()) { wait(); }
// 即使被唤醒,也会重新检查条件,不满足继续等

实际项目中优先用 BlockingQueue 替代手写 wait/notify,更安全简洁!

4. 状态转换图
        start()
    ┌─────────────┐
    ▼             │
  NEW ──► RUNNABLE ◄──┐
              │       │
     获得synchronized锁 │
              ▼       │
          BLOCKED ────┘
              │
     调用wait()/join() │
              ▼
         WAITING ──► notify()/notifyAll()
              │           │
     sleep(ms)/wait(ms)   │
              ▼           │
     TIMED_WAITING ──────┘
              │
        run()结束/异常
              ▼
        TERMINATED

对比表:常见方法触发状态速查

方法 触发状态 是否释放锁 初级使用建议
Thread.sleep(ms) RUNNABLE → TIMED_WAITING 不释放 模拟延迟、限流
Object.wait() RUNNABLE → WAITING 释放 线程协作(配合 notify)
Object.wait(ms) RUNNABLE → TIMED_WAITING 释放 带超时的等待
Thread.join() RUNNABLE → WAITING 不释放(等待其他线程结束) 主线程等待子任务完成
synchronized 竞争失败 RUNNABLE → BLOCKED - 自动触发,无需手动调用
LockSupport.park() RUNNABLE → WAITING 不释放 高级并发工具底层使用

高频面试题

Q1:sleep() 和 wait() 有什么区别?(必考!)

  1. 所属类不同: sleep 是 Thread 的静态方法;wait 是 Object 的实例方法
  2. 锁的处理不同(核心区别): sleep 不释放锁;wait 释放锁,允许其他线程竞争
  3. 使用范围不同: sleep 可在任何地方调用;wait 必须在 synchronized 块/方法中调用
  4. 唤醒方式不同: sleep 时间到自动唤醒;wait 需 notify/notifyAll 或超时
  5. 异常处理: 两者都需捕获 InterruptedException

Q2:为什么 wait() 要放在 while 循环里,而不是 if?

因为存在虚假唤醒(Spurious Wakeup):线程可能在没有被 notify() 的情况下被唤醒(JVM/OS 底层机制)。

// 错误:if 写法,可能被虚假唤醒,条件仍不满足就继续执行
if (queue.isEmpty()) { wait(); }
// 唤醒后直接执行 poll(),但队列可能还是空 → 报错!

// 正确:while 写法,唤醒后重新检查条件
while (queue.isEmpty()) { wait(); }
// 即使被唤醒,条件不满足会继续等待,保证安全

Q3:线程被 notify() 唤醒后,能立即执行吗?

不能!notify() 只是把线程从等待池移到锁池,线程还需要重新竞争 synchronized 锁,竞争成功才能继续执行。

类比:候车室广播叫号(notify)→ 乘客起身(被唤醒)→ 还要排队过安检(竞争锁)→ 才能继续旅程(执行代码)。


Q4:Thread.yield() 是干嘛的?

  • 作用: 提示调度器"我愿意让出 CPU",但调度器不一定采纳(依赖操作系统实现)
  • 状态变化: RUNNABLE → RUNNABLE(仍在就绪队列,可能立即又被调度)
  • 建议: 知道有这个概念即可,实际开发极少用,线程调度交给 JVM/OS 更可靠

自测练习

  1. 【判断】 线程调用 sleep() 时会释放锁吗?wait() 呢?
  2. 【选择】 以下哪种情况线程会进入 BLOCKED 状态?A. 调用 Thread.sleep(1000) B. 竞争 synchronized 锁失败 C. 调用 Object.wait()
  3. 【简答】 为什么 notifyAll()notify() 更安全?什么场景必须用 notifyAll()
  4. 【代码】 修复下面代码的潜在问题:
synchronized (lock) {
    if (!condition) {
        lock.wait();
    }
    // 执行后续逻辑
}

答案

  1. sleep() 不释放锁;wait() 释放锁。 这是两者最核心区别!
  2. B。 BLOCKED 专指等待 synchronized 锁;A 是 TIMED_WAITING,C 是 WAITING。
  3. notifyAll() 唤醒所有等待线程,避免"漏唤醒"。 场景:多个条件变量共用一个锁时(如生产者-消费者),必须用 notifyAll(),否则可能唤醒错误条件的线程导致死锁。
  4. 修复:if 改为 while,防止虚假唤醒:
synchronized (lock) {
    while (!condition) {  // 关键!
        lock.wait();
    }
    // 执行后续逻辑
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐