一、线程生命周期
Q1:Java 线程的生命周期有哪几种状态?
答: Java 线程有 6 种状态(Thread.State 枚举):
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_WAITINGjoin()底层是wait(),进入 WAITING
Q3:BLOCKED 和 WAITING 有什么区别?
答:
核心区别:BLOCKED 是"抢锁失败",WAITING 是"主动等待通知"。
Q4:为什么线程不能重复调用 start()?
答: start() 内部会检查线程状态,只有 NEW 状态才能启动:
Thread t = new Thread(() -> {});
t.start();
t.start(); // IllegalThreadStateException原因:线程一旦启动,threadStatus 不再为 0,重复启动会抛异常。
注意:调用 run() 不会启动新线程,只是普通方法调用。
Q5:sleep() 和 wait() 的区别?
答:
synchronized (lock) {
Thread.sleep(1000); // 持有锁睡觉
lock.wait(); // 释放锁等待
}经典考点:sleep 抱着锁睡,wait 放下锁等。
Q6:线程终止有哪几种方式?
答:
正常结束:run() 执行完毕
异常退出:run() 抛出未捕获异常
中断:
interrupt()+ 检查中断标志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 种:
继承 Thread
实现 Runnable
实现 Callable + FutureTask
线程池
// 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 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 valueQ10:继承 Thread 和实现 Runnable 哪个更好?
答: 实现 Runnable 更好,原因:
避免单继承限制:Java 单继承,继承 Thread 后不能再继承其他类
资源共享:多个线程可共享同一个 Runnable 对象
符合设计原则:任务与执行分离
// 共享资源示例
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(); // 共享 countQ11: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,结果存入outcomeget()若未完成,则阻塞在等待队列完成后唤醒所有等待线程
Q12:线程池创建线程和 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();
}
// 确保条件满足
}原因:
操作系统可能虚假唤醒
notifyAll()唤醒后,条件可能被其他线程改变
结论:wait 必须放在 while 循环中。
Q18:notify() 和 notifyAll() 有什么区别?
答:
notify():随机唤醒一个等待线程notifyAll():唤醒所有等待线程,它们竞争锁
synchronized (lock) {
lock.notify(); // 可能唤醒错误的线程,导致死锁
lock.notifyAll(); // 更安全
}推荐用 notifyAll(),除非确定只有一个等待者。
风险:notify() 可能唤醒不满足条件的线程,导致真正需要的线程一直等待。
Q19:wait() 被唤醒后,状态是什么?
答: 从 WAITING 变为 BLOCKED:
wait()释放锁,进入 WAITING被 notify 后,进入 BLOCKED(等待重新获取锁)
获取锁后,从 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:
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() 有什么区别?
答:
用法相似,但 await 更灵活。
Q25:Condition 的底层原理是什么?
答: Condition 基于 AQS 的条件队列:
每个 Condition 维护一个等待队列
await():释放锁,加入等待队列,阻塞signal():把等待队列的头节点移到同步队列,等待重新获取锁
await: 同步队列 → 等待队列
signal: 等待队列 → 同步队列核心:等待队列 + 同步队列的转移。
Q26:await() 被 signal() 后,线程状态是什么?
答: 和 wait/notify 类似:
await():释放锁,进入 WAITINGsignal():从等待队列移到同步队列,状态变 BLOCKED重新获取锁后,从 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。
核心要点:
lock.lock()加锁while循环检查条件notFull.await()/notEmpty.await()等待signal()精确通知finally中unlock()
五、综合与进阶
Q29:wait/notify 和 Condition 如何选择?
答:
核心:Condition 更灵活,但代码更复杂。
Q30:为什么 wait/notify 容易出 bug?
答: 常见问题:
用 if 而非 while:虚假唤醒
用 notify 而非 notifyAll:唤醒错误线程
不在 synchronized 中:IllegalMonitorStateException
唤醒后不重新检查条件:状态已变
多条件共用一个队列:无法精确唤醒
最佳实践:
synchronized (lock) {
while (!condition) {
lock.wait();
}
// 处理
lock.notifyAll();
}Q31:线程通信还有哪些方式?
答:
wait/notify:Object 监视器
Condition:Lock 条件队列
BlockingQueue:阻塞队列(最常用)
CountDownLatch:倒计数
CyclicBarrier:循环屏障
Semaphore:信号量
Exchanger:交换数据
volatile:共享变量可见性
管道流: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 无法区分"等任务"和"等空间"。
评论