Skip to content

多线程

Java 多线程的重点不是会创建 Thread,而是理解并发环境下的可见性、原子性、有序性、线程安全、线程池和锁。企业项目里尤其要重视线程池:JDK 7/8 项目大量使用 ExecutorServiceThreadPoolExecutorFuture,JDK 8 又进一步引入 CompletableFuture 做异步编排,但底层仍然离不开线程池。

学习路线

mermaid
flowchart TD
    A["线程创建和生命周期"] --> B["JMM 内存模型"]
    B --> C["volatile 与 Atomic"]
    C --> D["synchronized 和 Lock"]
    D --> E["线程池专题"]
    E --> F["CompletableFuture 异步编排"]
    F --> G["ThreadLocal 线程隔离"]
    G --> H["JUC 工具类"]
    H --> I["并发集合和阻塞队列"]
    I --> J["并发问题排查"]

目录

Thread 常用方法

Thread 基础题不要只背方法名,要知道每个方法改变的是“线程是否启动、当前线程是否让出 CPU、是否等待另一个线程、是否发出中断信号”。

方法作用容易误解
start()启动新线程,由 JVM 调用该线程的 run()不能重复调用同一个线程的 start()
run()普通方法,放线程要执行的逻辑直接调用不会启动新线程
sleep(ms)当前线程休眠指定时间,不释放已持有的锁它不是让别的指定线程睡眠
yield()当前线程提示调度器自己愿意让出 CPU只是提示,不保证让出成功
join()当前线程等待目标线程执行结束常用于主线程等待子任务完成
interrupt()给目标线程设置中断标记,或唤醒阻塞等待不是强制杀死线程
isInterrupted()判断线程中断标记不会清除标记
Thread.interrupted()判断当前线程中断标记并清除会清除标记,容易踩坑
currentThread()获取当前正在执行的线程对象常用于打印线程名和排查
setDaemon(true)设置守护线程必须在线程 start 前设置

start 和 run 的区别

java
Thread thread = new Thread(() -> {
    System.out.println(Thread.currentThread().getName());
});

thread.run();   // main 线程直接执行普通方法
thread.start(); // 新线程执行 run 逻辑
mermaid
flowchart TD
    A["调用 run()"] --> B["普通方法调用<br/>仍在当前线程执行"]
    C["调用 start()"] --> D["请求 JVM 创建新线程"]
    D --> E["新线程进入 RUNNABLE"]
    E --> F["由新线程执行 run()"]

interrupt 为什么不是强制停止

interrupt() 更像是给线程发一个“请停止或请醒来”的信号。线程是否结束,要看线程内部有没有正确响应。

java
Thread worker = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 执行任务
    }
    System.out.println("收到中断,准备退出");
});

worker.start();
worker.interrupt();

如果线程正在 sleepwaitjoin 中,被 interrupt 后通常会抛出 InterruptedException,并清除中断标记。正确做法通常是恢复中断标记或退出循环:

java
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

wait/notify 为什么不是 Thread 的方法

wait()notify()notifyAll()Object 的方法,因为它们操作的是对象监视器 monitor。线程必须先持有这个对象的锁,才能调用它们。

java
synchronized (lock) {
    lock.wait();
}

这解释了为什么 wait 会释放锁,而 sleep 不会释放锁:wait 是 monitor 协作机制的一部分,sleep 只是让当前线程暂停一段时间。

线程生命周期

mermaid
stateDiagram-v2
    [*] --> NEW
    NEW --> RUNNABLE: start
    RUNNABLE --> BLOCKED: 等待锁
    RUNNABLE --> WAITING: wait/join/park
    RUNNABLE --> TIMED_WAITING: sleep/带超时等待
    BLOCKED --> RUNNABLE: 获得锁
    WAITING --> RUNNABLE: notify/unpark
    TIMED_WAITING --> RUNNABLE: 超时或唤醒
    RUNNABLE --> TERMINATED: run结束

并发三大问题

问题说明解决思路
可见性一个线程修改后,其他线程看不到volatile、锁、线程安全容器
原子性多步操作被线程切换打断synchronized、Lock、CAS
有序性指令重排导致执行顺序不符合直觉volatile、happens-before

如果不理解这三个问题,就会误以为“加了 volatile 就线程安全”“用了线程池就一定更快”“ConcurrentHashMap 能保证所有复合操作安全”。

线程池执行流程

mermaid
flowchart TD
    A[提交任务] --> B{核心线程是否已满?}
    B -->|否| C[创建核心线程执行]
    B -->|是| D{队列是否已满?}
    D -->|否| E[放入阻塞队列]
    D -->|是| F{最大线程是否已满?}
    F -->|否| G[创建非核心线程执行]
    F -->|是| H[执行拒绝策略]

开发建议

  1. 不要直接无节制创建线程,优先使用线程池。
  2. 线程池参数要按业务设置,不要所有场景共用一个大线程池。
  3. 共享变量必须考虑线程安全。
  4. ThreadLocal 使用后要及时 remove
  5. 排查并发问题时保留线程名、traceId 和关键状态日志。

代码 Demo:创建线程池

java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    4,
    8,
    60,
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100),
    new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

executor.submit(() -> {
    System.out.println(Thread.currentThread().getName() + " handle task");
});

executor.shutdown();

真实项目不要随意使用 Executors.newFixedThreadPool,因为它默认队列可能很大,任务堆积时不容易暴露风险。