少女祈祷中...
NEKO
知识库

java 线程面试要点

一、线程生命周期

Q1:Java 线程的生命周期有哪几种状态?

答: Java 线程有 6 种状态(Thread.State 枚举):

状态

说明

NEW

新建,尚未调用 start()

RUNNABLE

可运行,包括"就绪"和"运行中"

BLOCKED

阻塞,等待获取 synchronized 锁

WAITING

等待,调用 wait()、join()、park() 等

TIMED_WAITING

超时等待,sleep()、wait(timeout)、join(timeout)

TERMINATED

终止,run() 执行完毕或异常退出

Thread t = new Thread(() -> {});
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE

注意:Java 把操作系统的"就绪"和"运行"合并为 RUNNABLE。


Q2:线程状态之间如何转换?

答: 状态转换图:

NEW --start()--> RUNNABLE
RUNNABLE --获取锁失败--> BLOCKED --获取锁--> RUNNABLE
RUNNABLE --wait()--> WAITING --notify()--> BLOCKED/RUNNABLE
RUNNABLE --sleep()--> TIMED_WAITING --超时--> RUNNABLE
RUNNABLE --run()结束--> TERMINATED

关键点:

  • wait() 释放锁,进入 WAITING,被唤醒后进入 BLOCKED(重新抢锁)

  • sleep() 不释放锁,进入 TIMED_WAITING

  • join() 底层是 wait(),进入 WAITING


Q3:BLOCKED 和 WAITING 有什么区别?

答:

对比项

BLOCKED

WAITING

触发原因

等待 synchronized 锁

调用 wait/join/park

是否释放锁

未持有锁,等待获取

wait 会释放锁

唤醒方式

锁被释放后自动竞争

需 notify/notifyAll/unpark

是否可超时

否

WAITING 不可,TIMED_WAITING 可

核心区别:BLOCKED 是"抢锁失败",WAITING 是"主动等待通知"。


Q4:为什么线程不能重复调用 start()?

答: start() 内部会检查线程状态,只有 NEW 状态才能启动:

Thread t = new Thread(() -> {});
t.start();
t.start(); // IllegalThreadStateException

原因:线程一旦启动,threadStatus 不再为 0,重复启动会抛异常。

注意:调用 run() 不会启动新线程,只是普通方法调用。


Q5:sleep() 和 wait() 的区别?

答:

对比项

sleep()

wait()

所属类

Thread

Object

是否释放锁

不释放

释放

唤醒方式

超时自动醒

需 notify/notifyAll

使用位置

任意

必须在 synchronized 中

异常

InterruptedException

InterruptedException

synchronized (lock) {
    Thread.sleep(1000); // 持有锁睡觉
    lock.wait();        // 释放锁等待
}

经典考点:sleep 抱着锁睡,wait 放下锁等。


Q6:线程终止有哪几种方式?

答:

  1. 正常结束:run() 执行完毕

  2. 异常退出:run() 抛出未捕获异常

  3. 中断:interrupt() + 检查中断标志

  4. stop():已废弃,不安全

Thread t = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 工作...
    }
});
t.start();
t.interrupt(); // 优雅中断

推荐用 interrupt(),不要用 stop()(会强制释放锁,导致数据不一致)。


Q7:interrupt() 会立即停止线程吗?

答: 不会。interrupt() 只是设置中断标志:

  • 线程在 sleep()/wait()/join() 中:抛 InterruptedException,并清除中断标志

  • 线程在正常运行:只设置标志,需自行检查

Thread t = new Thread(() -> {
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        // 被中断,标志已被清除
        Thread.currentThread().interrupt(); // 恢复标志
    }
});

最佳实践:捕获 InterruptedException 后,要么向上抛,要么恢复中断标志。


二、线程创建方式

Q8:创建线程有哪几种方式?

答: 主要有 4 种:

  1. 继承 Thread

  2. 实现 Runnable

  3. 实现 Callable + FutureTask

  4. 线程池

// 1. 继承 Thread
class MyThread extends Thread {
    public void run() { System.out.println("Thread"); }
}
new MyThread().start();

// 2. 实现 Runnable
new Thread(() -> System.out.println("Runnable")).start();

// 3. Callable + FutureTask
FutureTask<String> task = new FutureTask<>(() -> "result");
new Thread(task).start();
String result = task.get();

// 4. 线程池
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.execute(() -> System.out.println("Pool"));

Q9:Runnable 和 Callable 有什么区别?

答:

对比项

Runnable

Callable

方法

run()

call()

返回值

void

V(泛型)

异常

不能抛检查异常

可以抛 Exception

配合

Thread

FutureTask / 线程池

Runnable r = () -> System.out.println("no return");
Callable<String> c = () -> "return value";

FutureTask<String> ft = new FutureTask<>(c);
new Thread(ft).start();
System.out.println(ft.get()); // return value

Q10:继承 Thread 和实现 Runnable 哪个更好?

答: 实现 Runnable 更好,原因:

  1. 避免单继承限制:Java 单继承,继承 Thread 后不能再继承其他类

  2. 资源共享:多个线程可共享同一个 Runnable 对象

  3. 符合设计原则:任务与执行分离

// 共享资源示例
class Counter implements Runnable {
    private int count = 0;
    public void run() {
        for (int i = 0; i < 1000; i++) count++;
    }
}
Counter c = new Counter();
new Thread(c).start();
new Thread(c).start(); // 共享 count

Q11:FutureTask 的工作原理是什么?

答: FutureTask 实现了 Runnable 和 Future,内部用 state + 等待队列 管理:

FutureTask<String> ft = new FutureTask<>(() -> {
    Thread.sleep(1000);
    return "done";
});
new Thread(ft).start();
String r = ft.get(); // 阻塞直到完成

核心机制:

  • run() 执行 Callable,结果存入 outcome

  • get() 若未完成,则阻塞在等待队列

  • 完成后唤醒所有等待线程


Q12:线程池创建线程和 new Thread() 有什么区别?

答:

对比项

new Thread()

线程池

创建开销

每次新建

复用线程

管理

手动

自动管理

控制

无限制

可控制并发数

资源

易耗尽

可控

推荐

简单临时

生产环境

生产环境必须用线程池,避免线程爆炸。


Q13:start() 和 run() 的区别?

答:

  • start():启动新线程,JVM 调用 run()

  • run():普通方法调用,在当前线程执行

Thread t = new Thread(() -> 
    System.out.println(Thread.currentThread().getName()));

t.run();   // main,当前线程执行
t.start(); // Thread-0,新线程执行

只调用 run() 不会创建新线程。


Q14:如何让线程按顺序执行?

答: 用 join():

Thread t1 = new Thread(() -> System.out.println("T1"));
Thread t2 = new Thread(() -> System.out.println("T2"));
Thread t3 = new Thread(() -> System.out.println("T3"));

t1.start();
t1.join(); // 等 t1 结束
t2.start();
t2.join();
t3.start();

或用单线程池:

ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(() -> System.out.println("T1"));
pool.execute(() -> System.out.println("T2"));
pool.execute(() -> System.out.println("T3"));

三、线程通信:wait/notify

Q15:wait/notify 的作用是什么?

答: 用于线程间协调,基于对象的监视器锁:

  • wait():释放锁,进入等待队列

  • notify():随机唤醒一个等待线程

  • notifyAll():唤醒所有等待线程

synchronized (lock) {
    while (条件不满足) {
        lock.wait(); // 释放锁等待
    }
    // 条件满足,执行
    lock.notifyAll(); // 通知其他线程
}

Q16:为什么 wait/notify 必须在 synchronized 中?

答: 因为 wait/notify 依赖对象监视器锁:

  • wait() 释放锁,必须先持有锁

  • notify() 唤醒等待线程,需保证状态可见性

// 错误:IllegalMonitorStateException
lock.wait();

// 正确
synchronized (lock) {
    lock.wait();
}

本质:wait/notify 是"锁 + 条件队列"的机制。


Q17:为什么 wait 要用 while 而不是 if?

答: 防止虚假唤醒(spurious wakeup):

// 错误:可能被虚假唤醒
synchronized (lock) {
    if (queue.isEmpty()) {
        lock.wait();
    }
    // 可能队列仍为空
}

// 正确:醒来后重新检查
synchronized (lock) {
    while (queue.isEmpty()) {
        lock.wait();
    }
    // 确保条件满足
}

原因:

  1. 操作系统可能虚假唤醒

  2. notifyAll() 唤醒后,条件可能被其他线程改变

结论:wait 必须放在 while 循环中。


Q18:notify() 和 notifyAll() 有什么区别?

答:

  • notify():随机唤醒一个等待线程

  • notifyAll():唤醒所有等待线程,它们竞争锁

synchronized (lock) {
    lock.notify();    // 可能唤醒错误的线程,导致死锁
    lock.notifyAll(); // 更安全
}

推荐用 notifyAll(),除非确定只有一个等待者。

风险:notify() 可能唤醒不满足条件的线程,导致真正需要的线程一直等待。


Q19:wait() 被唤醒后,状态是什么?

答: 从 WAITING 变为 BLOCKED:

  1. wait() 释放锁,进入 WAITING

  2. 被 notify 后,进入 BLOCKED(等待重新获取锁)

  3. 获取锁后,从 wait() 返回,进入 RUNNABLE

synchronized (lock) {
    lock.wait(); // WAITING
    // 被唤醒后先变 BLOCKED,抢到锁才继续
}

关键:唤醒 ≠ 立即执行,还要重新抢锁。


Q20:wait() 和 sleep() 都能让线程暂停,为什么 wait 要释放锁?

答: 因为 wait 用于线程协作,sleep 用于单纯延时:

  • wait():等待条件满足,必须让其他线程能修改条件 → 释放锁

  • sleep():只是暂停,不涉及条件等待 → 不释放锁

// 生产者-消费者:必须释放锁
synchronized (queue) {
    while (queue.isEmpty()) {
        queue.wait(); // 让生产者能放入
    }
    queue.poll();
}

如果 wait 不释放锁,生产者永远无法进入,导致死锁。


Q21:手写一个生产者-消费者模型(wait/notify)

答:

class BlockingQueue<T> {
    private final Queue<T> queue = new LinkedList<>();
    private final int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void put(T item) 
            throws InterruptedException {
        while (queue.size() == capacity) {
            wait(); // 满了,等消费者
        }
        queue.offer(item);
        notifyAll(); // 通知消费者
    }

    public synchronized T take() 
            throws InterruptedException {
        while (queue.isEmpty()) {
            wait(); // 空了,等生产者
        }
        T item = queue.poll();
        notifyAll(); // 通知生产者
        return item;
    }
}

要点:while 循环 + notifyAll + synchronized。


四、线程通信:Condition

Q22:Condition 是什么?和 wait/notify 有什么关系?

答: Condition 是 Lock 提供的条件队列,替代 Object 的 wait/notify:

对比项

Object

Condition

配合

synchronized

Lock

等待

wait()

await()

唤醒一个

notify()

signal()

唤醒全部

notifyAll()

signalAll()

多条件

只有一个等待队列

可有多个 Condition

Lock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

Q23:Condition 相比 wait/notify 有什么优势?

答: 核心优势是支持多个条件队列:

class BoundedQueue<T> {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final Queue<T> queue = new LinkedList<>();
    private final int capacity;

    public void put(T item) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                notFull.await(); // 只等"不满"
            }
            queue.offer(item);
            notEmpty.signal(); // 只通知"非空"
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await();
            }
            T item = queue.poll();
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

优势:

  • 精确唤醒,避免 notifyAll 唤醒无关线程

  • 减少无效竞争,提升性能


Q24:Condition 的 await() 和 Object 的 wait() 有什么区别?

答:

对比项

wait()

await()

所属

Object

Condition

配合

synchronized

Lock

释放锁

释放监视器锁

释放 Lock

中断

InterruptedException

InterruptedException

超时

wait(timeout)

await(time, unit)

多条件

不支持

支持多个

用法相似,但 await 更灵活。


Q25:Condition 的底层原理是什么?

答: Condition 基于 AQS 的条件队列:

  • 每个 Condition 维护一个等待队列

  • await():释放锁,加入等待队列,阻塞

  • signal():把等待队列的头节点移到同步队列,等待重新获取锁

await: 同步队列 → 等待队列
signal: 等待队列 → 同步队列

核心:等待队列 + 同步队列的转移。


Q26:await() 被 signal() 后,线程状态是什么?

答: 和 wait/notify 类似:

  1. await():释放锁,进入 WAITING

  2. signal():从等待队列移到同步队列,状态变 BLOCKED

  3. 重新获取锁后,从 await() 返回

lock.lock();
try {
    condition.await(); // WAITING
    // signal 后变 BLOCKED,抢到锁才继续
} finally {
    lock.unlock();
}

Q27:Condition 可以替代 wait/notify 吗?

答: 可以,且更推荐:

  • 配合 ReentrantLock 使用

  • 支持多个条件队列,精确唤醒

  • 支持公平锁、可中断、超时

但注意:

  • 必须手动 lock() / unlock()

  • 忘记 unlock 会导致死锁

  • 简单场景用 synchronized 更简洁

选择建议:需要多条件或高级功能用 Condition,否则 synchronized 足够。


Q28:手写一个基于 Condition 的阻塞队列

答: 见 Q23 的 BoundedQueue。

核心要点:

  1. lock.lock() 加锁

  2. while 循环检查条件

  3. notFull.await() / notEmpty.await() 等待

  4. signal() 精确通知

  5. finally 中 unlock()


五、综合与进阶

Q29:wait/notify 和 Condition 如何选择?

答:

场景

推荐

简单同步

synchronized + wait/notify

多条件等待

ReentrantLock + Condition

需要公平锁

ReentrantLock + Condition

需要可中断/超时

Condition

高并发精细控制

Condition

核心:Condition 更灵活,但代码更复杂。


Q30:为什么 wait/notify 容易出 bug?

答: 常见问题:

  1. 用 if 而非 while:虚假唤醒

  2. 用 notify 而非 notifyAll:唤醒错误线程

  3. 不在 synchronized 中:IllegalMonitorStateException

  4. 唤醒后不重新检查条件:状态已变

  5. 多条件共用一个队列:无法精确唤醒

最佳实践:

synchronized (lock) {
    while (!condition) {
        lock.wait();
    }
    // 处理
    lock.notifyAll();
}

Q31:线程通信还有哪些方式?

答:

  1. wait/notify:Object 监视器

  2. Condition:Lock 条件队列

  3. BlockingQueue:阻塞队列(最常用)

  4. CountDownLatch:倒计数

  5. CyclicBarrier:循环屏障

  6. Semaphore:信号量

  7. Exchanger:交换数据

  8. volatile:共享变量可见性

  9. 管道流:PipedInputStream/PipedOutputStream

// BlockingQueue 最简单
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("data");  // 阻塞放入
String s = queue.take(); // 阻塞取出

Q32:volatile 能实现线程通信吗?

答: 可以,但仅限简单场景:

volatile boolean flag = false;

// 线程 A
flag = true;

// 线程 B
while (!flag) {} // 能看到变化

局限:

  • 只保证可见性,不保证原子性

  • 不能实现复杂条件等待

  • 忙等待浪费 CPU

复杂场景仍需 wait/notify 或 Lock。


Q33:Thread.join() 的底层原理是什么?

答: join() 底层用 wait/notify:

public final synchronized void join(long millis) 
        throws InterruptedException {
    while (isAlive()) {
        wait(millis);
    }
}
  • 线程结束时,JVM 调用 notifyAll()

  • 等待的线程被唤醒

本质:join 就是"等待线程结束"的封装。


Q34:如何实现两个线程交替打印奇偶数?

答:

方案一:wait/notify

class Printer {
    private int num = 1;
    private final Object lock = new Object();

    public void printOdd() {
        synchronized (lock) {
            while (num <= 100) {
                if (num % 2 == 1) {
                    System.out.println(Thread.currentThread().getName() 
                        + ": " + num++);
                    lock.notify();
                } else {
                    try { lock.wait(); } 
                    catch (InterruptedException e) {}
                }
            }
        }
    }

    public void printEven() {
        synchronized (lock) {
            while (num <= 100) {
                if (num % 2 == 0) {
                    System.out.println(Thread.currentThread().getName() 
                        + ": " + num++);
                    lock.notify();
                } else {
                    try { lock.wait(); } 
                    catch (InterruptedException e) {}
                }
            }
        }
    }
}

方案二:Condition

Lock lock = new ReentrantLock();
Condition oddCond = lock.newCondition();
Condition evenCond = lock.newCondition();

核心:条件判断 + 唤醒对方。


Q35:为什么线程池不用 wait/notify 而用 Condition?

答: 因为线程池需要多个条件:

  • 任务队列空:worker 等待

  • 任务队列满:提交者等待

  • 线程数达上限:等待

用 Condition 可以精确唤醒:

// ThreadPoolExecutor 简化
private final Condition notEmpty = ...;
private final Condition notFull = ...;

// 取任务
while (queue.isEmpty()) {
    notEmpty.await();
}
// 放任务
while (queue.size() == capacity) {
    notFull.await();
}

一个 Condition 无法区分"等任务"和"等空间"。

评论