Skip to content

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()); // TERMINATED

4.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 底层模型
  • ← 返回 **并发索引**

Knowledge4J — Java 知识库