摘要:本文是 Java 并发编程系列的第一篇,覆盖**模块 1(基础层)**的全部内容——三种线程创建方式及其底层原理、线程六种生命周期状态与完整转换图Thread 核心方法(sleep/wait/yield/join/interrupt/守护线程)的深度解析。每个知识点均配有可运行代码、原理拆解与面试速记,文末附总结。

目录


一、三种方式创建线程

1.1 继承 Thread

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyThread extends Thread {
@Override
public void run() {
System.out.println("继承 Thread 创建线程,当前线程:" + getName());
}
}

public class Demo {
public static void main(String[] args) {
MyThread t = new MyThread(); // 创建线程对象
t.start(); // 启动线程(JVM 会调用 run())
}
}

原理Thread 类本身实现了 Runnable 接口。当你继承 Thread 并重写 run() 时,实际上是在覆写 Thread.run() 的默认行为(默认行为是调用构造时传入的 target.run())。

🔑 速记:继承 Thread = 直接把「任务」和「线程」绑在一起。简单但不够灵活。

1.2 实现 Runnable 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("实现 Runnable 创建线程");
}
}

public class Demo {
public static void main(String[] args) {
MyRunnable task = new MyRunnable(); // ① 只是一个任务(不是线程)
Thread thread = new Thread(task); // ② 把任务交给线程
thread.start(); // ③ 启动线程执行任务
}
}

Lambda 表达式写法(最常用、最简洁):

1
2
3
4
Thread t = new Thread(() -> {
System.out.println("Lambda 方式创建线程");
});
t.start();

原理Runnable 是一个函数式接口(@FunctionalInterface),只有一个抽象方法 run()。它只定义”要做什么”,不关心”怎么调度”。Thread 类的构造方法接受一个 Runnable 对象作为任务目标,start() 时由新线程去执行这个任务的 run()

🔑 速记Runnable = 纯任务,自己不能启动;必须包一层 Thread 或丢给线程池才能跑。

1.3 实现 Callable 接口(带返回值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable 返回的结果";
}
}

public class Demo {
public static void main(String[] args) {
// ① 创建 Callable 任务
MyCallable task = new MyCallable();

// ② 用 FutureTask 包装(FutureTask 同时实现了 Runnable 和 Future)
FutureTask<String> futureTask = new FutureTask<>(task);

// ③ 交给 Thread 执行
Thread thread = new Thread(futureTask);
thread.start();

// ④ get() 阻塞等待结果
try {
String result = futureTask.get(); // 阻塞直到 call() 执行完毕
System.out.println(result); // 输出:Callable 返回的结果
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
}

关键角色拆解

角色 职责
Callable<V> 定义有返回值的任务(类似 Runnablecall() 能抛异常、能返回结果)
FutureTask<V> 桥梁——同时实现了 RunnableFuture,既能被 Thread 执行,又能通过 get() 取结果
Future.get() 阻塞当前线程,等待异步任务完成并取回返回值

🔑 速记Callable 单独不能跑 → 包一层 FutureTask 变成 Runnable → 再给 Thread 或线程池。get() 是拿结果的入口,没算完就一直等。

1.4 三种方式的底层关系与选择指南

三者之间的关系(一张图看懂):

1
2
3
4
5
6
7
8
9
10
Thread (extends Object implements Runnable)
├── 方式一:直接继承 Thread,重写 run()
│ └─ 缺点:Java 单继承,无法再继承其他类

└── 方式二三:把任务传进去
├── 构造参数 Runnable → 重写 run()
│ └─ 方式二:直接实现 Runnable

└── 构造参数 Runnable → 用 FutureTask 包装 Callable
└─ 方式三:实现 Callable(有返回值+异常)

完整对比表

维度 继承 Thread 实现 Runnable 实现 Callable
是否有返回值 FutureTask.get()
能否抛出受检异常 ❌ 只能 try-catch ❌ 同左 ✅ 声明在 call()
继承限制 ⚠️ 占用单继承名额 ✅ 不影响继承 ✅ 不影响继承
如何启动 new X().start() new Thread(x).start() new Thread(new FutureTask(x)).start()
适用场景 简单演示 无返回值任务(最常用 需要返回值的任务
线程池兼容 ❌ 不方便 ✅ 直接提交 ✅ 提交后返回 Future

💡 面试提示:实际开发优先用 Runnable/Callable + 线程池,几乎不用继承 Thread。原因有三:

  1. 避免单继承限制——你的业务类很可能需要继承别的框架类;
  2. 任务与线程解耦——同一个 Runnable 可以交给不同线程或线程池复用;
  3. 线程池只认 Runnable/Callable——ExecutorService.submit() 不接受 Thread 子类。

二、线程生命周期与状态(核心)

这是模块 1 最重要的一节。面试必问,不理解后面所有同步手段都是在死记。

2.1 六种状态一览

Java 线程在任意时刻只能处于以下六种状态之一(定义在 Thread.State 枚举中):

状态 英文常量 含义 类比
新建 NEW 已创建但还没 start() 招了人但还没入职
可运行 RUNNABLE 就绪态(排队等 CPU)+ 运行态(正在用 CPU) 在工位上干活或等着轮到自己
阻塞 BLOCKED 等待获取监视器锁(synchronized) 在等同事释放会议室钥匙
等待 WAITING 无限期等别人唤醒(wait()/join()/park() 发了消息等对方回复,没有超时
限时等待 TIMED_WAITING 有时限地等(sleep()/wait(timeout)/join(timeout) 设了闹钟休息一会儿
终止 TERMINATED run() 执行完毕或异常退出 离职了

关键认知:Java 的 RUNNABLE 包含了操作系统层面的**就绪(Ready)+ 运行(Running)**两种状态,JVM 层面不做区分。所以一个线程处于 RUNNABLE 不代表它一定在消耗 CPU——它可能在等 I/O、等网络响应。

2.2 状态转换全景图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
                   start()
NEW ───────────────────────▶ RUNNABLE

┌─────────────┼──────────────┐
▼ ▼ ▼
synchronized sleep(ms) wait() / join()
未获锁 / / park()
│ ▼ ▼ ▼
▼ TIMED_WAITING WAITING WAITING
BLOCKED │ │ │
│ 到期/中断 notify() unpark()
│ / interrupt /interrupt join结束
│ ▼ ▼ ▼
└──────── RUNNABLE ◄──── RUNNABLE ◀┘

run() 结束
/ 异常

TERMINATED

记忆口诀新建启动变可运行;锁不到就 BLOCKED;睡/等有时限进 TIMED;无限等进 WAITING;跑完挂掉 TERMINATED;全部回来都靠唤醒/中断/到期。

2.3 每种状态的触发条件与代码演示

① NEW → RUNNABLE

1
2
3
4
5
Thread t = new Thread(() -> {});   // NEW
System.out.println(t.getState()); // 输出 NEW

t.start(); // NEW → RUNNABLE
System.out.println(t.getState()); // 输出 RUNNABLE(可能很快变成 TERMINATED)

② RUNNABLE → TIMED_WAITING(sleep)

1
2
3
4
5
6
7
8
9
10
Thread t = new Thread(() -> {
try {
Thread.sleep(5000); // RUNNABLE → TIMED_WAITING
} catch (InterruptedException e) {
// 收到中断信号后被唤醒
}
});
t.start();
Thread.sleep(100); // 等 t 进入 sleep
System.out.println(t.getState()); // 输出 TIMED_WAITING

③ RUNNABLE → BLOCKED(synchronized 抢锁失败)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Object lock = new Object();

Thread t1 = new Thread(() -> {
synchronized (lock) { // t1 先拿到锁
try { Thread.sleep(3000); } catch (Exception e) {}
}
});

Thread t2 = new Thread(() -> {
synchronized (lock) { // t2 进不来 → BLOCKED
System.out.println("t2 拿到锁了");
}
});

t1.start();
t2.start();
Thread.sleep(200); // 等 t1 拿到锁、t2 尝试抢锁
System.out.println(t2.getState()); // 输出 BLOCKED

④ RUNNABLE → WAITING(wait / join)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// --- wait() 场景 ---
Object lock = new Object();
Thread tw = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); // 释放锁 → WAITING
} catch (InterruptedException e) {}
}
});
tw.start();
Thread.sleep(100);
System.out.println(tw.getState()); // 输出 WAITING

// --- join() 场景 ---
Thread tj = new Thread(() -> {
try { Thread.sleep(5000); } catch (Exception e) {}
});
tj.start();
tj.join(); // 主线程等 tj → 主线程进入 WAITING

⑤ → TERMINATED

1
2
3
4
Thread t = new Thread(() -> {});
t.start();
t.join(); // 等 t 跑完
System.out.println(t.getState()); // 输出 TERMINATED

2.4 面试高频:BLOCKED vs WAITING vs TIMED_WAITING

这三个状态最容易混淆,用一句话区分:

状态 为什么等 谁让你恢复 典型场景
BLOCKED 被动等锁——你想进入 synchronized 块但锁被别人占着 锁的持有者主动退出同步块释放锁 多个线程竞争同一把 synchronized 锁
WAITING 主动等通知——你调用了 wait()/join()/LockSupport.park() 放弃执行权 必须被显式唤醒notify()/join 结束/unpark() 生产者-消费者模式、线程间协调
TIMED_WAITING 主动等,但有闹钟——你带了超时参数 时间到了自动醒,或被提前中断/唤醒 sleep() 限流、wait(timeout) 带超时等待

💡 面试提示:面试官最爱问「sleep() 之后线程是什么状态?」答 TIMED_WAITING,不是 RUNNABLE 也不是 BLOCKED。再追问「synchronized 没拿到锁呢?」答 BLOCKED。记住:BLOCKED 是被动的(锁不给你),WAITING/TIMED_WAITING 是主动的(你自己放弃的)。


三、核心方法深度解析

3.1 currentThread() 与线程基本信息

Thread.currentThread() 是一个静态方法,返回正在执行这段代码的那个线程对象

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo {
public static void main(String[] args) {
// main 方法里调用 → 返回主线程
System.out.println(Thread.currentThread());
// 输出:Thread[main,5,main] → [线程名, 优先级, 线程组名]

new Thread(() -> {
// 在新线程里调用 → 返回新线程
System.out.println(Thread.currentThread());
// 输出:Thread[Thread-0,5,main]
}).start();
}
}

currentThread 打印

Thread.toString 输出

toString() 格式解读Thread[线程名, 优先级, 线程组名]

常用信息获取方法汇总:

方法 返回类型 说明
getName() / setName(name) String / void 线程名(默认 Thread-0Thread-1…)
getId() long 线程唯一 ID(全局递增,不可修改)
getPriority() / setPriority(p) int / void 优先级(1–10,默认 5)
getState() Thread.State 当前状态(六种枚举值之一)
getThreadGroup() ThreadGroup 所属线程组
isAlive() boolean 是否存活(start() 后且未终止)
isDaemon() boolean 是否守护线程

优先级细节

1
2
3
4
Thread t = new Thread(() -> {});
t.setPriority(Thread.MAX_PRIORITY); // 10(最高)
t.setPriority(Thread.MIN_PRIORITY); // 1(最低)
t.setPriority(Thread.NORM_PRIORITY); // 5(默认)

⚠️ 重要认知:优先级越高只是获得 CPU 调度的概率越大绝不保证先执行或不被抢占。且不同操作系统对 Java 优先级(1–10)的映射不同(Linux 只有 1:1 和 1:N 两种线程模型,Windows 映射更复杂)。生产环境永远不要依赖优先级来保证执行顺序。

优先级示例输出

🔑 速记currentThread() = “我是谁”;优先级 = “老板偏心但不保准”。

3.2 run()start() 的本质区别

这是初学者第一坑,也是面试送分题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("当前线程:" + Thread.currentThread().getName());
});

// ❌ 错误用法:直接调用 run()
t.run(); // 只是普通方法调用,在主线程里执行!
// 输出:当前线程:main

// ✅ 正确用法:调用 start()
t.start(); // JVM 创建新线程,在新线程里执行 run()
// 输出:当前线程:Thread-0
}
}

run 与 start 区别

start() 内部发生了什么?

  1. 将线程状态从 NEWRUNNABLE
  2. 加入线程组,通知 JVM 该线程已就绪
  3. 由操作系统的线程调度器分配 CPU 时间片
  4. 当该线程获得 CPU 后,JVM 自动调用其 run() 方法
  5. run() 执行完毕后线程进入 TERMINATED 状态

💡 面试提示

  • start() 只能调用一次,第二次会抛 IllegalThreadStateException(因为状态已经不是 NEW 了)。
  • 直接调 run() 不会报错,但只是普通方法调用——不会创建新线程、不会有并发效果。
  • start()synchronized 方法(Thread 源码里加了锁),保证线程状态变更的原子性。

3.3 sleep() —— 抱着锁睡

1
2
3
4
5
6
7
8
// 基本用法
try {
Thread.sleep(1000); // 休眠 1 秒(毫秒)
Thread.sleep(1000, 500000); // 休眠 1.5 秒(毫秒 + 纳秒)
} catch (InterruptedException e) {
// sleep 期间如果被 interrupt(),会立即抛出此异常并清除中断标志
Thread.currentThread().interrupt(); // 最佳实践:重新设置中断标志
}

sleep() 的三个关键特性

特性 说明
不释放锁 如果当前线程持有 synchronized 锁,sleep 期间锁仍然被占用
静态方法 Thread.sleep() 作用在当前线程上,不是调用它的那个线程对象
状态变化 RUNNABLETIMED_WAITING → 时间到 / 被中断 → 回到 RUNNABLE

为什么 sleep() 要捕获 InterruptedException

因为 sleep() 是一种可中断的阻塞。当另一个线程对你调用了 interrupt(),JVM 会把你从睡眠中唤醒(抛异常),而不是让你继续傻睡到底。这是 Java 协作式中断机制的一部分——给了线程一个”优雅退出的机会”。

🔑 记忆钩子sleep 抱锁睡——抱着 synchronized 锁睡觉,不放手;醒来后继续持有。

3.4 wait() —— 放开锁等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Object lock = new Object();

Thread waiter = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("waiter: 我拿到锁了,现在开始等...");
lock.wait(); // ① 释放锁 → 进入 WAITING
System.out.println("waiter: 被唤醒了,重新拿到锁!");
} catch (InterruptedException e) {
System.out.println("waiter: 被中断了");
}
}
});

Thread notifier = new Thread(() -> {
try {
Thread.sleep(2000); // 先让 waiter 进入 wait
} catch (Exception e) {}
synchronized (lock) {
System.out.println("notifier: 我拿到锁了,唤醒 waiter");
lock.notify(); // ② 唤醒一个等待者(waiter 还需要重新抢锁)
}
// notifier 出了 synchronized 块才真正释放锁
// waiter 这时才有机会重新获取锁并从 wait() 返回
});

waiter.start();
notifier.start();

wait() 的五个铁律

规则 说明
必须在 synchronized 块内调用 否则抛 IllegalMonitorStateException
调用后立即释放锁 这是和 sleep() 最大的区别
释放的是当前持有的那把锁 不是所有锁,是你当前 synchronized(lock) 里的那个 lock 对象
被唤醒后重新竞争锁 notify() 只是把它从等待队列移到锁竞争队列,还得抢到锁才能真正执行
属于 Object 的方法 不是 Thread 的方法——任何对象都能 wait()/notify()

🔑 记忆钩子wait 放锁等——放开 synchronized 锁等别人叫;醒了还要重新抢锁。

3.5 sleep() vs wait() 完整对比

对比维度 sleep() wait()
所属类 Thread(静态方法) Object(实例方法)
锁行为 不释放锁 释放锁
使用位置 任何地方 必须在 synchronized 块内
唤醒方式 时间到 / interrupt() notify()/notifyAll() / interrupt()
用途 控制自身执行节奏 线程间通信/协作
作用对象 当前线程 调用它的那个锁对象上的等待者
典型场景 简单限流、模拟耗时 生产者-消费者、条件等待

💡 面试提示:面试必问。记住核心区别只有两个字:sleep 不放、wait 放。其他区别都是从这个根区别衍生出来的(比如 wait 必须在 synchronized 里用——因为它要释放锁,没锁怎么释放?)。

3.6 如何停止线程——interrupt() 协作式中断

❌ 已废弃的方式stop()suspend()resume()——它们强制停止/挂起线程,可能导致:

  • 对象处于不一致的中间态(锁被突然释放)
  • finally 块来不及执行清理资源
  • 无法预测程序状态

✅ 正确方式:interrupt() 协作式中断

interrupt() 不是强制杀死线程,而是设置一个中断标志位boolean flag),线程自己决定何时检查这个标志并安全退出。

方式一:通过 isInterrupted() 自行检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) { // 不断检查标志位
System.out.println("工作中...");
try {
Thread.sleep(500); // sleep 也会检查标志位
} catch (InterruptedException e) {
System.out.println("收到中断信号,准备退出");
break; // 安全退出循环
}
}
System.out.println("线程安全停止");
});

t.start();

try { Thread.sleep(2000); } catch (Exception e) {}
t.interrupt(); // 设置中断标志

方式二:通过 volatile 标志位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class VolatileStop {
private static volatile boolean running = true; // volatile 保证可见性

public static void main(String[] args) throws Exception {
Thread t = new Thread(() -> {
while (running) {
System.out.println("工作中...");
try { Thread.sleep(500); } catch (Exception e) {}
}
System.out.println("线程安全停止");
});

t.start();
Thread.sleep(2000);
running = false; // 外部改标志位
}
}

两种方式的选择

方式 优点 缺点
interrupt() JDK 内置,能唤醒 sleep()/wait() 中的线程 需要在代码里主动检查
volatile flag 简单直观,逻辑清晰 无法唤醒阻塞中的 sleep()/wait()——线程卡在 sleep 里根本读不到 flag

最佳实践:两者结合——用 interrupt() 作为主要手段,volatile 作为补充。

interrupt 停止线程

💡 面试提示:为何不用 stop()?→ 强制释放锁导致数据不一致。正确姿势是 interrupt() + 线程自行检查退出。追问「sleep() 中被 interrupt 会怎样?」→ 抛 InterruptedException 并清除中断标志,应在 catch 里做清理或重新设置标志。

3.7 yield() —— 礼让 CPU

1
2
3
4
5
6
7
8
9
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
if (i % 3 == 0) {
Thread.yield(); // 礼让:回到就绪态重新排队
}
System.out.println("计数:" + i);
}
});
t.start();

yield() 的本质

  • 让当前线程从运行态回到就绪态(注意:不是阻塞态,是不拿 CPU 但仍可被调度)
  • 不释放任何锁
  • 只是”建议”——操作系统完全可以忽略这次礼让(大多数情况下确实会被忽略)
  • 状态变化:RUNNABLERUNNABLE(还是同一种状态,只是放弃了本次时间片)

实际价值很低。现代 JVM 调度器已经足够智能,手动 yield() 几乎不会带来性能提升。主要用于调试或特殊框架代码。

🔑 记忆钩子yield 礼让——“我不急,你们先”,但别人不一定领情。

3.8 join() —— 插队等待

1
2
3
4
5
6
7
8
9
10
Thread t = new Thread(() -> {
try {
Thread.sleep(3000); // 模拟耗时任务
} catch (Exception e) {}
System.out.println("子线程完成");
});

t.start();
// t.join(); // ← 加上这行:主线程等 t 跑完再继续
System.out.println("主线程继续"); // 不加 join:立刻打印;加了 join:等 3 秒后才打印

join() 内部其实就是 wait()(源码层面用 wait(0) 实现):

1
2
3
4
5
6
// Thread.join() 的简化版逻辑
public final void join() throws InterruptedException {
while (isAlive()) {
wait(0); // 调用 this.wait(0) → 进入 WAITING
}
}

所以调用 t.join() 的主线程会进入 WAITING 状态,直到 t 线程终止后 JVM 会自动调用 this.notifyAll() 唤醒所有等待者。

带超时的版本

1
2
t.join(1000);                           // 最多等 1 秒,超时后不管 t 有没有跑完都继续
// 状态变化:RUNNABLE → TIMED_WAITING → 时间到/t结束 → RUNNABLE

🔑 记忆钩子join 插队——“你做完我再做”。内部就是 wait(0),所以主线程会进入 WAITING/TIMED_WAITING。

3.9 守护线程(Daemon)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Thread daemon = new Thread(() -> {
int count = 0;
while (true) { // 无限循环
try {
Thread.sleep(1000);
System.out.println("守护线程工作中... 第" + (++count) + "次");
} catch (InterruptedException e) {
break;
}
}
});
daemon.setDaemon(true); // ★ 必须在 start() 之前设置!
daemon.start();

Thread.sleep(3000);
// 主线程结束 → JVM 退出 → 守护线程也被强制终止(不管它在干什么)
System.out.println("主线程结束");

守护线程的关键规则

规则 说明
必须在 start() 前设置 运行中设 setDaemon() 会抛 IllegalThreadStateException
不阻止 JVM 退出 当所有非守护线程结束时,JVM 直接退出,守护线程被”斩立决”
finally 不保证执行 JVM 退出时守护线程可能正执行到一半,finally 来不及跑
子线程继承父线程属性 主线程创建的子线程默认是非守护线程;守护线程创建的子线程默认也是守护线程

典型应用场景

  • GC 线程(Finalizer
  • 日志异步刷盘线程
  • 心跳检测 / 监控指标采集线程
  • 各种后台清理任务

💡 面试提示:守护线程最大的坑是 finally 不一定执行。所以不要把”必须完成的资源清理”放在守护线程里。判断是否要用守护线程的标准:“这个线程被突然杀掉,会不会导致数据丢失或状态不一致?” 如果会,就用非守护线程。


四、总结

核心速记

创建线程三方式Thread 继承(简单但占继承名额)、Runnable 实现(最常用)、Callable+FutureTask(有返回值)。实际开发优先 Runnable/Callable + 线程池。

六种状态:NEW → RUNNABLE → (BLOCKED 抢锁 / WAITING 无限等 / TIMED_WAITING 限时等)→ TERMINATED。BLOCKED 是被动的(锁不给),WAITING 是主动的(自己放弃的)。

方法记忆链sleep 抱锁睡、wait 放锁等、yield 礼让、join 插队、interrupt 协作停、setDaemon 守护不阻退出。

start()run():前者创建新线程并自动调用后者;直接调 run() 只是普通方法调用,无并发效果。

最佳实践 Checklist

  • 创建线程优先 Runnable/Callable,不用继承 Thread
  • 启动线程用 start(),绝不直接调 run()
  • 停止线程用 interrupt() 协作式中断,不用废弃的 stop()
  • sleep() 用于简单限流,wait()/notify() 用于线程间协作
  • 守护线程只做”可中断”的后台任务,不放重要资源清理
  • 不要依赖线程优先级保证执行顺序
  • join() 用于等待结果,但要设超时避免永久阻塞

高频面试题速答

问题 一句话答案
start()run() 区别? start() 创建新线程并自动调用 run();直接调 run() 是普通方法调用,不开新线程
sleep()wait() 区别? sleep 不释放锁(Thread 静态方法);wait 释放锁(Object 方法,需在 synchronized 内)
怎么正确停止线程? interrupt() 设置标志位 + 线程内检查 isInterrupted() 或捕获 InterruptedException

| 线程有哪些状态? | 六种:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED |
| yield() 做什么? | 礼让 CPU,回到就绪态重新竞争(只是建议,不保证生效) |
| join() 原理? | 内部用 wait(0) 实现,调用线程进入 WAITING 直到目标线程终止 |
| 守护线程特点? | 不阻止 JVM 退出,finally 不保证执行,必须在 start() 前设置 |
| BLOCKEDWAITING 区别? | BLOCKED 是被动等锁(synchronized),WAITING 是主动放弃(wait/join/park) |