08.1 — 线程基础与创建
定位: 并发编程的起点 — 理解线程的本质和四种创建方式 面试高频度: ⭐⭐⭐⭐ 考查方式: 线程创建方式对比、线程状态迁移、start() vs run()
一、这是什么?为什么需要它?
进程 vs 线程
┌─────────────────────────────────────────────────────────────┐
│ 进程 vs 线程 │
│ │
│ 进程 = 资源分配的基本单位 │
│ 线程 = CPU 调度的基本单位 │
│ │
│ ┌──────────────────────────┐ │
│ │ 进程 │ │
│ │ ┌──────┐ ┌──────┐ ┌──┐ │ │
│ │ │线程1 │ │线程2 │ │线程│ │ ← 共享堆、方法区、文件句柄 │
│ │ │(栈) │ │(栈) │ │(栈)│ │ 私有栈和程序计数器 │
│ │ └──────┘ └──────┘ └──┘ │ │
│ └──────────────────────────┘ │
│ │
│ 多线程的意义: │
│ 1. CPU 利用率 — 一个线程等待 IO 时,另一个线程使用 CPU │
│ 2. 响应性 — UI 不卡顿,后台任务不阻塞界面 │
│ 3. 资源节约 — 创建线程比创建进程轻量得多 │
└─────────────────────────────────────────────────────────────┘为什么需要多线程?
单线程的问题:
System.out.println("第一步");
Thread.sleep(5000); // 5 秒内什么都做不了!
System.out.println("第二步");
// CPU 空转 5 秒!
多线程:
Thread worker = new Thread(() -> {
doHeavyWork(); // 耗时操作在后台
});
worker.start();
System.out.println("主线程继续工作...");
// 充分利用 CPU 等待时间二、原理拆解
2.1 四种线程创建方式
java
// 方式 1:extends Thread(不推荐)
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread: " + getName());
}
}
new MyThread().start();
// 方式 2:implements Runnable(推荐)
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("Runnable task");
}
}
new Thread(new MyTask()).start();
// 或 Lambda
new Thread(() -> System.out.println("task")).start();
// 方式 3:Callable + FutureTask(有返回值)
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Task result";
}
}
FutureTask<String> ft = new FutureTask<>(new MyCallable());
new Thread(ft).start();
String result = ft.get(); // 阻塞等待结果
// 方式 4:线程池(工业首选)
ExecutorService pool = Executors.newFixedThreadPool(10);
Future<String> future = pool.submit(() -> "result");
pool.shutdown();为什么 Runnable/Callable 优于 Thread?
java
// Thread 占用继承位
class MyServlet extends Thread { ... } // 不能再继承其他类!
// 继承 Thread 成本高,每次 new 创建完整线程对象
// Runnable 只是任务,线程可复用
class MyTask implements Runnable { ... } // 还可以继承其他类
// 线程池复用了线程,只执行 run() 中的任务2.2 线程状态迁移
┌─────────────────────────────────────────────────────────────┐
│ Java 线程 6 种状态 │
│ │
│ ╔═════════╗ │
│ ┌──→║ NEW ║←───── new Thread() │
│ │ ╚═════════╝ │
│ │ │ │
│ │ start() │
│ │ ▼ │
│ │ ╔═════════╗ │
│ │ ║RUNNABLE ║ ←── 正在运行/等待 CPU │
│ │ ╚═════════╝ │
│ │ │ │ │ │
│ │ │ │ │ │
│ 获得锁◄────┼────┘ │ └──────→ ╔══════════╗ │
│ │ waiting()│ sleep()/IO ║TIMED_WAITING║ │
│ │ LockSup │ parkNanos() ╚══════════╝ │
│ │ park() │ ↑ │
│ │ │ sleep() 超时 │
│ │ ▼ notify()/unpark() │
│ ╔══════════╗ │ │
│ ║ BLOCKED ║───┘ │
│ ╚══════════╝ 等待获取锁 │
│ │ │
│ │ │
│ │ run() 完成 / 异常 │
│ │ ▼ │
│ │ ╔══════════╗ │
│ └───║TERMINATED║ │
│ ╚══════════╝ │
│ │
│ NEW → start() → RUNNABLE(一切就绪只等 CPU 调度) │
│ RUNNABLE → 未获取到锁 → BLOCKED │
│ RUNNABLE → wait/sleep/park → WAITING/TIMED_WAITING │
└─────────────────────────────────────────────────────────────┘2.3 start() vs run()
java
Thread t = new Thread(() -> System.out.println("running"));
t.run(); // ❌ 不会启动新线程!在当前线程执行 run()
t.start(); // ✅ 创建新线程,由新线程执行 run()run():普通方法调用,在当前线程执行,不会创建新线程
start():native 方法,底层 JVM 创建新线程,由新线程调用 run()
所以:run() 执行 → 不产生新线程 → 白费功夫
start() 执行 → 启动新线程 → 正确用法2.4 daemon 线程
java
Thread t = new Thread(() -> {
while (true) { /* 后台监控 */ }
});
t.setDaemon(true); // 设为守护线程(必须在 start() 之前)
t.start();
// 守护线程:当所有非守护线程结束时,JVM 自动退出
// 用途:垃圾回收线程、监控线程、心跳线程
// 注意:守护线程中的 finally 块不保证执行!三、图解全景
┌─────────────────────────────────────────────────────────────┐
│ 四种线程创建方式对比 │
│ │
│ ┌──────────┬──────────┬──────────┬──────────────────┐ │
│ │ Thread │ Runnable │ Callable│ ExecutorService │ │
│ ├──────────┼──────────┼──────────┼──────────────────┤ │
│ │ ❌占用 │ ✅不占用 │ ✅不占用 │ ✅最佳实践 │ │
│ │ 继承位 │ 继承位 │ 继承位 │ │ │
│ ├──────────┼──────────┼──────────┼──────────────────┤ │
│ │ 无返回值 │ 无返回值 │ 有返回值 │ Future 支持 │ │
│ ├──────────┼──────────┼──────────┼──────────────────┤ │
│ │ 不可复用 │ 可复用 │ 可复用 │ 线程池复用 │ │
│ ├──────────┼──────────┼──────────┼──────────────────┤ │
│ │ 简单场景 │ 替换Thread│ 需要结果│ 生产环境 │ │
│ └──────────┴──────────┴──────────┴──────────────────┘ │
│ │
│ 推荐度:Thread < Runnable < Callable < ExecutorService │
└─────────────────────────────────────────────────────────────┘四、实战验证
4.1 线程状态观察
java
Thread t = new Thread(() -> {
System.out.println("State in run: " + Thread.currentThread().getState());
});
System.out.println("State before start: " + t.getState()); // NEW
t.start();
System.out.println("State after start: " + t.getState()); // RUNNABLE
Thread.sleep(10);
System.out.println("State after completion: " + t.getState()); // TERMINATED4.2 线程中断机制
java
// 线程中断不会强制停止,而是设置中断标志
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000); // slepp 响应中断
} catch (InterruptedException e) {
System.out.println("被中断!");
Thread.currentThread().interrupt(); // 重新设置中断标志
break;
}
}
});
t.start();
Thread.sleep(300);
t.interrupt(); // 设置中断标志五、面试视角
| 追问 | 答案要点 |
|---|---|
| start() 和 run() 的区别? | run() 当前线程执行;start() 新建线程执行 |
| 线程状态有哪些? | NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED |
| sleep(0) 有什么作用? | 触发 CPU 重新调度,让出时间片,但不进入等待 |
| 线程中断是什么? | 设置中断标志位,需要线程自己检查并响应 |
| 守护线程的特点? | JVM 等待非守护线程结束;守护线程的 finally 不保证执行 |
📚 相关链接
- **线程安全与锁** — 线程安全的实现
- **Executor与线程池** — 工业级线程管理
- **JMM索引** — JMM 底层模型
- ← 返回 **并发索引**