一、基础概念

1.1 线程与进程

维度进程(Process)线程(Thread)
定义操作系统资源分配的最小单位CPU 调度执行的最小单位
资源拥有独立的内存地址空间、文件描述符、信号处理等共享进程的方法区,独有程序计数器
切换开销(切换页表、刷新 TLB、保存/恢复寄存器等)(共享进程地址空间,仅需切换栈和寄存器)
创建/销毁开销大(分配内存、创建 PCB 等内核数据结构)开销小(仅分配栈和 TCB)
通信方式IPC:管道(Pipe)、消息队列、共享内存、Socket、信号量共享内存(堆中的对象)+ 需同步控制(锁、volatile)
崩溃影响一个进程崩溃不影响其他进程(地址空间隔离)一个线程的未处理异常可能导致整个 JVM 进程退出
多核利用多进程可分布到多核多线程可分布到多核(Java 线程本质是 OS 线程)

操作系统层面的线程实现模型

┌─────────────────────────────────────────────────┐
│  1. 内核线程模型 (1:1)  — Java 采用               │
│    每个用户线程 → 一个内核线程(轻量级进程 LWP)     │
│    优点:一个线程阻塞不影响其他线程,充分利用多核     │
│    缺点:创建/切换开销依赖内核,数量受限于内核资源    │
├─────────────────────────────────────────────────┤
│  2. 用户线程模型 (N:1)  — 早期 JVM(Green Thread)│
│    多个用户线程 → 一个内核线程                      │
│    优点:无内核参与,切换极快                      │
│    缺点:一个线程阻塞,全部阻塞,无法利用多核        │
├─────────────────────────────────────────────────┤
│  3. 混合线程模型 (N:M)  — Go 协程、Project Loom    │
│    多个用户线程 ↔ 多个内核线程,动态映射            │
└─────────────────────────────────────────────────┘

Java 线程:HotSpot JVM 采用 1:1 内核线程模型,每个 java.lang.Thread 对象对应一个操作系统线程。JDK 19+ 的 Project Loom 引入了虚拟线程(Virtual Thread),采用 N:M 模型。

线程上下文切换的具体过程

  1. 挂起当前线程,保存 CPU 寄存器状态(PC、栈指针、通用寄存器)到 TCB
  2. 操作系统调度器选择下一个线程
  3. 恢复该线程的寄存器状态,更新 CPU 缓存
  4. 切回该线程代码执行

什么时候发生上下文切换

  • 时间片耗尽(抢占式调度)
  • 线程主动让出 CPU:sleep()wait()yield()LockSupport.park()
  • 高优先级线程就绪
  • 发生硬件中断

1.2 并发 vs 并行

并发 (Concurrency):多个任务在同一时间段内交替执行(单核 CPU 快速切换)
                    ──▲── ▲─ ▲──▲─ ▲ ──▲ → 时间轴
                      T1 T2 T1 T2 T1

并行 (Parallelism):多个任务在同一时刻真正同时执行(多核 CPU)
                   T1 ──────────────→ 核 1
                   T2 ──────────────→ 核 2
对比维度并发并行
核心含义逻辑上的同时发生物理上的真正同时
执行方式交替执行(单核或多核上快速切换)同时执行(必须多核)
必要条件代码结构支持(多线程编写)硬件支持(多核 CPU)
关注点任务间的调度与协调任务间的分解与加速
反义词串行(Sequential)串行(Sequential)
生活中的例子一个人同时聊多个微信多个人各聊各的
Java 体现单核上多个线程竞争时间片多核上多个线程真正同时运行

重要认知:并发是程序的结构特性(设计目标),并行是程序的执行特性(运行时行为)。多线程程序即使跑在单核 CPU 上也是并发的,但不一定是并行的。

1.3 线程的创建方式(5 种)

方式 1:继承 Thread 类
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is running");
    }
}
MyThread t = new MyThread();
t.start();  // ⚠️ 必须调用 start(),直接调用 run() 只是普通方法调用

为什么不推荐?

  • Java 单继承限制:继承了 Thread 就不能继承其他类
  • 任务和线程耦合:任务代码与线程控制混杂在一起
  • 无法使用线程池复用
方式 2:实现 Runnable 接口
class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("task running");
    }
}
Thread t = new Thread(new MyTask());
t.start();

// Lambda 简写
new Thread(() -> System.out.println("running")).start();

优点:任务与线程分离,可以传给线程池,可继承其他类。优先使用

方式 3:实现 Callable + FutureTask
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {  // 有返回值、可抛异常
        Thread.sleep(1000);
        return "done";
    }
}

FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();

// get() 阻塞等待结果
String result = futureTask.get();           // 阻塞直到任务完成
String result2 = futureTask.get(500, TimeUnit.MILLISECONDS); // 超时等待
futureTask.cancel(true);                     // 尝试取消任务
boolean isCancelled = futureTask.isCancelled();
boolean isDone = futureTask.isDone();

FutureTask 内部状态转换

 NEW → COMPLETING → NORMAL        (正常完成)
 NEW → COMPLETING → EXCEPTIONAL   (异常)
 NEW → CANCELLED                  (被取消)
 NEW → INTERRUPTING → INTERRUPTED (运行中被取消)

Runnable vs Callable 对比

对比点RunnableCallable
方法签名void run()V call() throws Exception
返回值
异常不能抛出受检异常,只能在内部 try-catch可以抛出受检异常
配合使用ThreadExecutor.execute()FutureTaskExecutorService.submit()
方式 4:通过 ExecutorService(线程池)
ExecutorService pool = Executors.newFixedThreadPool(10);

// 无返回值
pool.execute(() -> System.out.println("running"));

// 有返回值
Future<String> future = pool.submit(() -> {
    Thread.sleep(100);
    return "result";
});
String result = future.get();  // 阻塞获取

pool.shutdown();  // 优雅关闭,不接受新任务,执行完已有任务
pool.shutdownNow();  // 立即关闭,尝试中断正在执行的任务
方式 5:CompletableFuture(JDK8+,推荐异步编程)
// 不指定线程池,默认用 ForkJoinPool.commonPool()
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
    return "hello";
});

// 指定自定义线程池
ExecutorService myPool = Executors.newFixedThreadPool(4);
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
    return "world";
}, myPool);

// 链式组合
String result = cf.thenCombine(cf2, (a, b) -> a + " " + b).join();
创建方式返回值异常抛出配合线程池推荐度
继承 Thread
实现 Runnable⭐⭐
实现 Callable⭐⭐⭐
线程池直接提交通过 Future通过 Future⭐⭐⭐⭐
CompletableFuture,链式✅,exceptionally可选⭐⭐⭐⭐⭐
start() vs run() 对比(⭐ 高频面试)
维度start()run()
作用启动新线程,JVM 调用 native 方法创建 OS 线程普通 Java 方法调用
执行线程新线程中执行 run()当前线程(调用者线程)直接执行
调用次数一个线程对象只能调用一次,多次抛 IllegalThreadStateException可多次调用(普通方法)
并发真正的多线程并发串行执行,不产生新线程
Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName()));
t.run();    // 输出 "main"     — 当前线程执行!
t.start();  // 输出 "Thread-0" — 新线程执行!

底层原理start()native start0() → JVM 创建操作系统线程 → 新线程回调 run()

1.4 线程生命周期(6 种状态详解)

注意区分:操作系统层面通常 5 种状态(New→Ready→Running→Blocked→Terminated),但 Java 将 Running 和 Ready 合并为 RUNNABLE,并将等待细分为 BLOCKED/WAITING/TIMED_WAITING,共 6 种状态

                    ┌─────────┐
                    │   NEW   │  线程创建但未调用 start()
                    │         │  此时未分配 OS 资源
                    └────┬────┘
                         │ start() — 分配资源,等待 CPU 调度
                    ┌────▼──────────────┐
              ┌─────│    RUNNABLE       │  ←─┐
              │     │ ① 就绪态:等待CPU │    │
              │     │ ② 运行态:CPU执行 │    │
              │     └──┬──────┬────┬───┘    │
              │        │      │    │        │
     synchronized    │ Object.wait()  │      │
     等锁(block)     │ Thread.join()  │      │
              │      │ LockSupport    │      │
              │      │   .park()      │      │
              │      │      │         │      │
         ┌────▼────┐ │ ┌────▼───────┐ │      │
         │ BLOCKED │ │ │  WAITING   │ │      │
         │等待进入  │ │ │ 无限等待   │ │      │
         │synchro- │ │ │ 必须被显式 │ │      │
         │nized块  │ │ │ 唤醒       │ │      │
         └────┬────┘ │ └────┬───────┘ │      │
              │      │      │         │      │
              │ notify/notifyAll     │      │
              │ unpark/interrupt     │      │
              │      │      │         │      │
              拿到锁 └──────┼─────────┘      │
              │             │                │
              │      ┌──────▼──────────┐     │
              │      │  TIMED_WAITING  │     │
              │      │ 超时等待        │     │
              │      │ sleep(time),    │     │
              │      │ wait(time),     │     │
              │      │ join(time)      │     │
              │      └──────┬──────────┘     │
              │             │ timeout/       │
              │             │ notify/        │
              │             │ interrupt      │
              │             │                │
              └─────────────┼────────────────┘
                            │ run() 执行完毕
                            │ 或 未捕获异常
                    ┌───────▼──────┐
                    │  TERMINATED  │
                    │  线程终止    │
                    └──────────────┘

6 种状态详细说明

状态含义进入方式退出方式
NEW线程对象创建,start() 尚未调用new Thread()start() 进入 RUNNABLE
RUNNABLEJVM 层面的可运行态(含 OS 的 Running + Ready)start()、从 WAITING/TIMED_WAITING/BLOCKED 被唤醒yield() 让出 CPU(仍为 RUNNABLE)
BLOCKED等待进入 synchronized 代码块/方法时被阻塞尝试获取已被其他线程持有的 monitor 锁拿到锁,进入 RUNNABLE
WAITING无限期等待其他线程显式唤醒Object.wait()Thread.join()LockSupport.park()notify()/notifyAll()unpark()interrupt()
TIMED_WAITING带超时的等待Thread.sleep(ms)Object.wait(ms)Thread.join(ms)LockSupport.parkNanos(ms)超时到期 / notify() / unpark()
TERMINATED线程执行完毕run() 正常执行完成 / 未捕获异常退出不可恢复

关键区分

  • BLOCKED:被动等待,等别的线程释放锁。只针对 synchronized
  • WAITING:主动等待,等别的线程通知唤醒ReentrantLock.lock() 内部使用 LockSupport.park(),线程状态也是 WAITING。
  • TIMED_WAITING:WAITING 的超时版本,到时间会自动唤醒。

yield() 与线程状态

  • Thread.yield() 是一个暗示(hint),告诉调度器当前线程愿意让出 CPU
  • 不会释放锁
  • 状态不改变,从 RUNNABLE(Running)回到 RUNNABLE(Ready),可能立即被调度器再次选中
  • 实际效果依赖于操作系统调度器,不可靠

线程中断机制

// interrupt() — 设置中断标志位
thread.interrupt();

// 三种响应方式:
// 1. 当前线程检查自己的中断状态
while (!Thread.currentThread().isInterrupted()) {
    // 干活...
}

// 2. 阻塞方法(sleep/wait/join)抛出 InterruptedException
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // 中断标志已被清除,需要时重新设置
    Thread.currentThread().interrupt();  // 恢复中断标志
}

// 3. LockSupport.park() 被中断时立即返回(不清除中断标志)

中断方法对比

方法清除中断标志说明
isInterrupted()❌ 不清除普通检查
interrupted()✅ 清除静态方法,调用的是当前线程,检查后清除标志
interrupt()设置中断标志

二、Java 内存模型(JMM)

2.1 为什么需要 JMM?

在没有 JMM 的情况下,多线程程序的执行结果取决于:

  • CPU 缓存:每个核心有自己的 L1/L2 缓存,写操作不会立即同步到主内存
  • 指令重排序:编译器和 CPU 为了性能会打乱指令执行顺序

JMM 定义了一套规范,屏蔽了不同硬件/操作系统的内存访问差异,确保 Java 程序在各种平台上有一致的行为。

2.2 JMM 核心概念

┌───────────────────┐          ┌───────────────────┐
│      线程 A        │          │      线程 B        │
│                   │          │                   │
│  ┌─────────────┐  │          │  ┌─────────────┐  │
│  │ 本地内存 A   │  │          │  │ 本地内存 B   │  │
│  │ (CPU 寄存器  │  │          │  │ (CPU 寄存器  │  │
│  │  + L1/L2缓存)│  │          │  │ + L1/L2缓存)│  │
│  │             │  │          │  │             │  │
│  │  x = 1  ←┐  │  │          │  │  read x → 1 │  │
│  └──────┬────┘  │  │          │  └──────┬────┘  │  │
└─────────┼───────┘  │          └─────────┼───────┘  │
          │ flush    │                    │ load     │
          ▼          │                    │          │
┌──────────────────────────────────────────────────┐ │
│                  主内存 (Main Memory)              │ │
│        所有线程共享的变量,与堆内存对应            │ │
│                                                  │ │
│        x = 0  ————>  x = 1                       │ │
└──────────────────────────────────────────────────┘

JMM 规定的关键操作(8 个原子操作)

操作作用域含义
lock主内存将变量标识为某线程独占
unlock主内存释放独占的变量
read主内存从主内存读取到工作内存
load工作内存将 read 到的值放入工作内存变量副本
use工作内存将工作内存变量值传给执行引擎
assign工作内存将执行引擎收到的值赋给工作内存变量
store工作内存将工作内存变量值传送到主内存
write主内存将 store 过来的值写入主内存变量

必须遵守的规则

  • readloadstorewrite 必须成对出现
  • 不允许线程丢弃最近的 assign(赋值后必须刷回主内存)
  • 工作内存中的变量在没有 assign 的情况下不允许无原因刷回主内存

2.3 指令重排序(3 种类型)

// 源代码
int a = 1;         // (1)
int b = 2;         // (2)
int c = a + b;     // (3)
重排序类型发生阶段说明
编译器重排序编译期JIT 编译器在保证单线程语义的前提下重新安排语句顺序
指令级并行重排序CPU 执行期CPU 流水线优化,无数据依赖的指令可并行/提前执行
内存系统重排序写入/读取时由于缓存/写缓冲区的存在,写操作对不同核心可见的顺序可能不同

as-if-serial 语义:无论怎么重排序,单线程程序的执行结果不能被改变。编译器、CPU 都必须遵守。但多线程环境下,重排序的效果会对其他线程可见,导致并发问题。

经典重排序导致的问题 — DCL 单例

public class Singleton {
    private static Singleton instance;  // ⚠️ 如果不用 volatile

    public static Singleton getInstance() {
        if (instance == null) {                     // (1) 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {             // (2) 第二次检查
                    instance = new Singleton();     // (3) 关键行!
                    // new Singleton() 底层三步:
                    //   A. 分配内存空间
                    //   B. 执行构造函数初始化对象
                    //   C. 将引用指向分配的内存地址
                    // ⚠️ 重排序后可能变成 A→C→B!
                    // 如果线程1执行完 A→C 后被挂起,
                    // 线程2 在 (1) 处看到 instance != null,
                    // 但拿到的是一个未初始化完成的对象!
                }
            }
        }
        return instance;
    }
}
// 解决方案:instance 加 volatile 禁止指令重排序
private static volatile Singleton instance;

2.4 happens-before 规则详解(JMM 核心)

JMM 通过 happens-before 规则来规定两个操作之间的内存可见性:如果操作 A happens-before 操作 B,那么 A 的结果对 B 可见,且 A 的执行顺序在 B 之前。

8 条规则逐条解析

① 程序次序规则(Program Order Rule)
同一线程内,书写在前面的代码 happens-before 书写在后面的代码。

int a = 1;   // hb
int b = 2;   // 本句

注意:这里说的是"控制流顺序"而非"程序代码顺序",在有分支/循环时以实际执行为准。

② volatile 变量规则(Volatile Variable Rule)
对 volatile 变量的操作 happens-before 后续对这个 volatile 变量的操作。

线程 A: volatileVar = 1;          // write
线程 B: int x = volatileVar;      // read — 看到的一定是 1

③ 传递性(Transitivity)
若 A hb B,B hb C,则 A hb C。

线程 A: normalVar = 10;           // (A)
        volatileVar = 1;          // (B)
线程 B: if (volatileVar == 1) {   // (C) 看到 B
            // 这里一定看到 normalVar == 10  ← 通过 A→B→C 传递
        }

④ 锁规则(Monitor Lock Rule)
对同一个锁的解锁 happens-before 后续对这个锁的加锁

线程 A: synchronized(lock) { x = 10; }  // 解锁 hb
线程 B: synchronized(lock) { /* 一定看到 x == 10 */ }

⑤ 线程 start() 规则
thread.start() happens-before 该线程内的所有操作。

主线程: thread.start();  // hb
新线程: thread.run() 内的第一条语句

⑥ 线程 join() 规则
线程内的所有操作 happens-before thread.join() 成功返回。

线程 A: count = 100;  // hb
主线程: threadA.join();  // 之后一定看到 count == 100

⑦ 线程中断规则
对线程 interrupt() 的调用 happens-before 被中断线程检测到中断事件。

主线程: thread.interrupt();           // hb
线程 B: thread.isInterrupted() == true

⑧ 对象终结规则
构造函数的结束 happens-before finalize() 方法的开始。

2.5 并发编程三大特性

                 线程安全
                    │
       ┌────────────┼────────────┐
       ▼            ▼            ▼
   原子性        可见性        有序性
   (Atomicity)  (Visibility)  (Ordering)
原子性
操作是否原子
int a = 5; 基本类型赋值(除 long/double 在 32 位 JVM)
int b = a; 读 + 赋值❌ 两步
a++; 读 + 改 + 写❌ 三步
使用 AtomicInteger CAS 操作
synchronized 块内的操作
32 位 JVM 上 long/double 的读写❌ 可能分两次 32 位操作

保证原子性的手段synchronizedReentrantLockAtomicXXX(CAS)

可见性
// 无 volatile:线程 B 可能永远看不到线程 A 的修改
class VisibilityProblem {
    private boolean flag = false;  // ⚠️ 不加 volatile

    void writer() { flag = true; }

    void reader() {
        while (!flag) { /* JIT 可能将 flag 缓存在寄存器,死循环 */ }
        System.out.println("done");
    }
}

JIT 编译器导致的可见性问题

  • 逃逸分析:编译器认为 flag 在当前线程内不会被修改,将其缓存到寄存器
  • 循环提升:将 while (!flag) 优化为 if (!flag) { while(true) {} }

保证可见性的手段volatilesynchronizedLockfinal

synchronized 如何保证可见性? JMM 规定:

  • 线程加锁前,必须清空工作内存,从主内存重新加载
  • 线程解锁前,必须把工作内存中的修改刷新到主内存
有序性

保证有序性的手段volatilesynchronizedLock

volatile 如何禁止重排序? 内存屏障(Memory Barrier):

┌──────────────────────────────────────────────┐
│  volatile 写之前插入  StoreStore 屏障          │
│    确保之前所有普通写对 volatile 写可见        │
│  volatile 写之后插入  StoreLoad 屏障           │
│    确保 volatile 写不会被重排到后续读写之后    │
├──────────────────────────────────────────────┤
│  volatile 读之后插入  LoadLoad 屏障            │
│    确保后续读不会被重排到 volatile 读之前      │
│  volatile 读之后插入  LoadStore 屏障           │
│    确保后续写不会被重排到 volatile 读之前      │
└──────────────────────────────────────────────┘

四种屏障详解

屏障类型指令作用
LoadLoadLoad1; LoadLoad; Load2保证 Load1 的数据加载在 Load2 之前完成
StoreStoreStore1; StoreStore; Store2保证 Store1 的写入对后续 Store 可见
LoadStoreLoad1; LoadStore; Store2保证 Load1 在 Store2 的写入之前完成
StoreLoadStore1; StoreLoad; Load2保证 Store1 对所有处理器可见后才执行 Load2。最重,杀所有重排序

2.6 volatile 详解

volatile 三不保证 & 应用场景

volatile 保证:可见性 ✅、有序性 ✅
volatile 不保证:原子性 ❌

volatile int count = 0;
// 场景 1:原子的 volatile 操作 ✅
count = 1;           // 写是原子的
int x = count;       // 读是原子的

// 场景 2:非原子的复合操作 ❌
count++;             // 读-改-写,不是原子的
                      // 线程 A 读 count=0,线程 B 读 count=0
                      // 各自 +1 写回 =1,丢失一次更新

volatile 适用场景(必须同时满足)

条件说明
✅ 对变量的写不依赖当前值flag = true 而非 count++
✅ 变量不需要与其他状态变量共同参与不可分割的操作

典型适用

  • 状态标志量:volatile boolean initialized
  • DCL 单例模式的 instance 引用
  • 独立观察值(单次读就获取完全正确的值)

典型不适用

  • i++i = i + 1 等复合操作
  • 需要保证多个变量操作原子性的场景
volatile 与 DCL 单例完整解析
public class Singleton {
    // volatile 关键作用:
    // 1. 禁止指令重排(A→C→B 变为 A→B→C)
    // 2. 保证多线程间的可见性
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {                 // 第一次检查(无锁,性能优化)
            synchronized (Singleton.class) {
                if (instance == null) {         // 第二次检查(有锁,安全保证)
                    // 三步:分配内存 → 初始化 → 引用赋值
                    // volatile 禁止这三步重排序
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2.7 synchronized 详解

三种使用方式
// 方式 1:同步代码块 — 锁是括号内的对象
Object lock = new Object();
synchronized (lock) {
    // 临界区
}

// 方式 2:同步实例方法 — 锁是 this(当前实例对象)
public synchronized void method() {
    // 相当于 synchronized(this) { ... }
}

// 方式 3:同步静态方法 — 锁是类的 Class 对象
public static synchronized void staticMethod() {
    // 相当于 synchronized(MyClass.class) { ... }
}

实例方法同步 vs 静态方法同步:互不干扰!一个是锁 this 对象,一个是锁 Class 对象,两把不同的锁。

字节码层面原理
public void syncBlock() {
    synchronized (this) {
        System.out.println("Hello");
    }
}

对应字节码:

 0: aload_0
 1: dup
 2: astore_1
 3: monitorenter          ← 获取 monitor 锁
 4: getstatic ...
 7: ldc #...
 9: invokevirtual ...
12: aload_1
13: monitorexit           ← 正常释放 monitor 锁
14: goto 22
17: astore_2
18: aload_1
19: monitorexit           ← 异常路径也释放 monitor 锁
20: aload_2
21: athrow
22: return

关键细节:编译器会自动在异常处理表中加入 monitorexit,保证即使发生异常锁也能正常释放。这是 synchronized 比 Lock 更安全的一个体现。

Monitor(管程)原理
┌──────────────────────────────────────────────┐
│                 Object Monitor               │
│                                              │
│  ┌─────────┐   ┌──────────┐   ┌──────────┐  │
│  │ _owner  │   │ _EntryList│   │ _WaitSet │  │
│  │ null    │   │ (竞争队列) │   │ (等待队列) │  │
│  │         │   │ thread T2 │   │ thread T3 │  │
│  │         │   │ thread T4 │   │           │  │
│  └─────────┘   └──────────┘   └──────────┘  │
│                                              │
│  owner = null → 可以竞争                      │
│  owner = T1  → T2/T4 在 EntryList 中等待     │
│  wait()     → owner 进入 WaitSet 释放锁       │
│  notify()   → WaitSet 中的线程移到 EntryList  │
└──────────────────────────────────────────────┘

ObjectMonitor 核心字段(C++ 源码精简):

ObjectMonitor() {
    _header       = NULL;     // 对象头 Mark Word
    _count        = 0;       // 重入计数
    _waiters      = 0;       // 等待线程数
    _recursions   = 0;       // 重入次数
    _object       = NULL;    // 关联的 Java 对象
    _owner        = NULL;    // 持有锁的线程
    _EntryList    = NULL;    // 阻塞等待获取锁的线程链表
    _WaitSet      = NULL;    // 调用了 wait() 的线程链表
}
锁升级(膨胀)过程详解

锁升级方向不可逆:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。只能升级不能降级(偏向锁可撤销,重置为无锁)。

时间线
────────────────────────────────────────────────►

阶段 1: 无锁
[对象头 Mark Word 最后 3 位 = 001]

阶段 2: 偏向锁 (JDK6 默认开启,JDK15 默认关闭,JDK21 彻底废弃)
条件:同一线程反复获取同一把锁
实现:Mark Word 存储线程 ID
获取:
  检查 Mark Word 的线程 ID == 当前线程?
    → 是:直接获取(无需 CAS)
    → 否:CAS 尝试修改线程 ID
        → 成功:获取偏向锁
        → 失败:撤销偏向锁,升级轻量级锁
重偏向:偏向锁撤销达到阈值(-XX:BiasedLockingBulkRebiasThreshold=20)时
          触发批量重偏向

阶段 3: 轻量级锁
条件:多线程"交替"执行同步块,不存在实际竞争
实现:通过 CAS 自旋获取
过程:
  1. 当前线程栈帧中创建 Lock Record
  2. 将对象头 Mark Word 拷贝到 Lock Record
  3. CAS 尝试将对象头 Mark Word 替换为指向 Lock Record 的指针
     → 成功:获取轻量级锁,Mark Word 末 2 位 = 00
     → 失败:自旋一定次数
          → 一直失败(竞争激烈):膨胀为重量级锁

阶段 4: 重量级锁
条件:多线程同时竞争同一把锁
实现:操作系统 Mutex Lock
过程:
  1. 向 OS 申请互斥量
  2. 对象头 Mark Word 存储指向 ObjectMonitor(堆中)的指针
  3. 未获取锁的线程 → 进入 EntryList → BLOCKED 状态
  4. 锁释放时从 EntryList 中挑选一个线程唤醒

Mark Word 在不同锁状态下的存储结构(64 位 JVM)

锁状态存储内容锁标志位
无锁unused(25) | hashcode(31) | unused(1) | age(4) | biased_lock(1) | 01001
偏向锁threadID(54) | epoch(2) | unused(1) | age(4) | biased_lock(1) | 01101
轻量级锁ptr_to_lock_record(62) | 0000
重量级锁ptr_to_object_monitor(62) | 1010
GC 标记11
synchronized 优化(JDK6+)
优化技术说明
偏向锁单线程重复获取时,无 CAS 开销
轻量级锁无实际竞争时,CAS 自旋避免阻塞
自旋锁不挂起线程,空转等待,适合锁持有时间短的场景
自适应自旋JVM 根据前次自旋成功率自动调整自旋次数
锁消除JIT 分析无共享变量逃逸,直接去掉同步
锁粗化连续加锁解锁合并为一次更大的锁,减少加锁次数

锁消除示例

// 使用 StringBuffer 的方法,内部 append 都有 synchronized
// 但 sb 是局部变量,不会逃逸,JIT 会消除掉所有 synchronized
public String concat(String a, String b) {
    StringBuffer sb = new StringBuffer();
    sb.append(a);  // synchronized 会被消除
    sb.append(b);  // synchronized 会被消除
    return sb.toString();
}

锁粗化示例

// 循环内反复加锁解锁
for (int i = 0; i < 100; i++) {
    synchronized (lock) { doSomething(); }
}
// JIT 优化为(如果条件合适)
synchronized (lock) {
    for (int i = 0; i < 100; i++) { doSomething(); }
}

2.8 CAS 详解

CAS 原理
// CAS 由 CPU 指令 cmpxchg 保证原子性
// Unsafe 类提供 CAS 原语(只能被 JDK 内部类通过反射调用)
public final class Unsafe {
    public final native boolean compareAndSwapInt(
        Object o,      // 对象
        long offset,   // 字段在对象中的偏移量
        int expected,  // 期望值
        int x          // 新值
    );
}

CPU 层面cmpxchg 指令前加 lock 前缀(lock cmpxchg),锁定总线或缓存行,保证原子性。

自旋获取

// AtomicInteger.incrementAndGet() 的 CAS 自旋逻辑
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);          // 读当前值
    } while (!compareAndSwapInt(o, offset, v, v + delta));  // CAS 失败则重试
    return v;
}
ABA 问题详解
时间线:
T1: 读到值 A
T2: A → B → A (先改成B,又改回A)
T1: CAS(A, C) → 成功!但中间经历了 A→B→A 的变化,T1 不知道

示例代码

// 有 ABA 问题的情况
AtomicInteger ai = new AtomicInteger(100);
// 线程1: 预期 100,改为 50
// 线程2: 先改为 20,再改为 100 (ABA)
// 线程1 的 CAS(100, 50) 仍成功,但中间已发生过变化

// 解决方案1: AtomicStampedReference 带版本号
AtomicStampedReference<Integer> asr = 
    new AtomicStampedReference<>(100, 0);
int stamp = asr.getStamp();
asr.compareAndSet(100, 50, stamp, stamp + 1);

// 解决方案2: AtomicMarkableReference 带布尔标记
AtomicMarkableReference<Integer> amr = 
    new AtomicMarkableReference<>(100, false);
amr.compareAndSet(100, 50, false, true);
原子类体系
                    ┌─────────────────────┐
                    │     Atomic 家族      │
                    └─────────┬───────────┘
          ┌──────────────────┼──────────────────┐
          ▼                  ▼                  ▼
   标量原子类            原子数组类           原子引用类
   AtomicInteger        AtomicIntegerArray   AtomicReference
   AtomicLong           AtomicLongArray      AtomicStampedReference
   AtomicBoolean                               AtomicMarkableReference
          │
          ▼
   累加器 (JDK8+)
   LongAdder    ← 高并发下比 AtomicLong 吞吐更高
   DoubleAdder      原理:分散热点,Cell[] 分段累加
   LongAccumulator
   DoubleAccumulator
LongAdder vs AtomicLong
// AtomicLong: 单热点,高并发 CAS 自旋严重
// 线程1 ─┐
// 线程2 ─┼→ CAS(base, base+1) ← 所有线程竞争一个变量
// 线程3 ─┘

// LongAdder: 分散热点 (JDK8+)
// 线程1 → Cell[0].value++
// 线程2 → Cell[1].value++    ← 每个线程落到不同 Cell
// 线程3 → Cell[2].value++      最终 sum = base + ΣCell[i]
// Cell 数组由 @Contended 注解填充避免伪共享

LongAdder 适用场景

  • 高并发下统计计数(如 QPS 统计、访问计数)
  • 需要最终一致性但不需要实时精确值
  • sum() 方法不是原子的(求和时可能有并发更新)

三、锁机制深度解析

3.1 Lock 接口体系

Lock (接口)
  │
  ├── ReentrantLock         可重入互斥锁
  │
  ├── ReentrantReadWriteLock.ReadLock   读锁(共享)
  ├── ReentrantReadWriteLock.WriteLock  写锁(排他)
  │
  └── StampedLock (JDK8)
       ├── 悲观读锁
       ├── 悲观写锁
       └── 乐观读锁
ReentrantLock 特性
public class ReentrantLock implements Lock, Serializable {
    // 内部同步器,基于 AQS
    private final Sync sync;

    // 抽象基类,继承 AQS
    abstract static class Sync extends AbstractQueuedSynchronizer { ... }

    // 非公平实现(默认)
    static final class NonfairSync extends Sync { ... }

    // 公平实现
    static final class FairSync extends Sync { ... }
}

重入原理

// 简化版实现
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();               // 当前 AQS state
    if (c == 0) {                     // 无锁
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) {
        // ⭐ 重入的关键:同一个线程,state 累加
        int nextc = c + acquires;
        setState(nextc);
        return true;
    }
    return false;
}

// 释放时同理,state 累减,减到 0 才真正释放
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {                     // 完全释放
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

公平锁 vs 非公平锁核心区别

// 非公平 NonfairSync.tryAcquire:
//   直接 CAS 抢锁,不管有没有人排队
final boolean tryAcquire(int acquires) {
    if (getState() == 0) {
        if (compareAndSetState(0, acquires)) {  // 直接抢
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
    }
    // 重入逻辑同上...
}

// 公平 FairSync.tryAcquire:
//   先看有没有人排队,有人排队就不抢
final boolean tryAcquire(int acquires) {
    if (getState() == 0) {
        if (!hasQueuedPredecessors() &&          // ⭐ 先检查是否有人排在前面
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
    }
    // 重入逻辑同上...
}

非公平锁吞吐量更高的原因:一个新线程来直接 CAS 抢锁,如果恰好锁刚释放,抢到后直接执行,避免了唤醒阻塞线程的上下文切换开销。

使用建议

  • 吞吐量优先 → 非公平锁(默认)
  • 公平性优先 → 公平锁(避免线程饥饿)

3.2 synchronized vs ReentrantLock 深度对比

对比维度synchronizedReentrantLock
实现层级JVM 层面,C++ 实现JDK 层面,Java 实现(基于 AQS)
获取与释放隐式,进入代码块获取,退出代码块释放显式,lock()unlock()
异常处理自动释放,不用担心忘记释放必须在 finally 中手动释放
可中断❌ 等待锁时不可中断lockInterruptibly()
超时获取❌ 不支持tryLock(timeout, unit)
非阻塞尝试❌ 不支持tryLock() 立即返回
公平锁❌ 只支持非公平✅ 可选公平/非公平
多条件等待❌ 单个隐式条件(wait/notify)✅ 多个 Condition 对象
持有线程查询❌ 无法知道哪个线程持有锁isHeldByCurrentThread() getOwner()
等待线程查询❌ 无法知道有多少线程在等待getQueueLength() hasQueuedThreads()
性能JDK6+ 优化后差距很小高竞争场景略优
锁绑定代码块级可跨方法(lock 和 unlock 在不同方法中调用)

什么时候选 Lock?

  1. 需要超时获取锁(如数据库连接池,等 3 秒拿不到就降级)
  2. 需要可中断地获取锁(如任务可以被取消)
  3. 需要多个条件变量(如生产者-消费者,需要用不同 Condition 区分"满了"和"空了")
  4. 需要非阻塞的 tryLock(如抢红包,抢不到就算了)

3.3 AQS 深度解析

AQS(AbstractQueuedSynchronizer)是 JUC 大多数同步器的基石

AQS 核心设计
public abstract class AbstractQueuedSynchronizer {
    // ═══════════ volatile + CAS 保证线程安全 ═══════════
    private volatile int state;

    protected final int getState()      { return state; }
    protected final void setState(int newState) { state = newState; }
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

    // ═══════════ CLH 变体队列 ═══════════
    // 双向链表,保存等待线程
    private transient volatile Node head;
    private transient volatile Node tail;

    static final class Node {
        volatile Node prev;          // 前驱节点
        volatile Node next;          // 后继节点
        volatile Thread thread;      // 等待的线程
        volatile int waitStatus;     // 当前节点状态
        Node nextWaiter;             // 条件队列的下一个节点
        // waitStatus 取值:
        // CANCELLED(1)  — 超时/中断已取消
        // SIGNAL(-1)    — 后继节点需要被唤醒
        // CONDITION(-2) — 节点在条件队列中
        // PROPAGATE(-3) — 共享模式下传播释放
        // 0             — 默认值
    }

    // ═══════════ 两种模式 ═══════════
    // 排他模式:同一时刻只有一个线程持有锁
    // 共享模式:多个线程可同时持有锁
    static final Node EXCLUSIVE = null;   // 排他模式标记
    static final Node SHARED = new Node(); // 共享模式标记(哨兵)
}
state 的两种语义
同步器state 含义
ReentrantLock0=未锁定, 1=锁定, N>1=重入 N 次
ReentrantReadWriteLock高 16 位=读锁持有数, 低 16 位=写锁重入次数
Semaphore剩余许可证数量
CountDownLatch还需 countDown 的次数,到 0 就释放
AQS 排他锁获取流程(acquire 源码分析)
// 排他锁获取入口
public final void acquire(int arg) {
    // 1. tryAcquire → 由子类实现,返回 true 表示成功
    // 2. 失败则 addWaiter → 封装成 Node 加入 CLH 队列尾部
    // 3. acquireQueued → 在队列中自旋等待
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();  // 如果因中断醒来,恢复中断标志
}

// 加入等待队列
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {  // 快速路径:CAS 入队尾
            pred.next = node;
            return node;
        }
    }
    enq(node);  // 慢路径:自旋 CAS 入队
    return node;
}

// 在队列中自旋等待
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            // 只有前驱是 head 才有资格 tryAcquire
            if (p == head && tryAcquire(arg)) {
                setHead(node);  // 获取成功,设为新 head
                p.next = null;  // 帮助 GC
                failed = false;
                return interrupted;
            }
            // 判断是否需要 park 当前线程
            // shouldParkAfterFailedAcquire: 将前驱 waitStatus 设为 SIGNAL
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())    // LockSupport.park(this)
                interrupted = true;
        }
    } finally {
        if (failed) cancelAcquire(node);
    }
}
AQS 排他锁释放流程
public final boolean release(int arg) {
    if (tryRelease(arg)) {              // 子类实现,state 归零
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);         // 唤醒 head 的下一个节点
        return true;
    }
    return false;
}
AQS 共享模式
// 共享获取(Semaphore、CountDownLatch、读写锁的读锁)
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)      // < 0 表示获取失败
        doAcquireShared(arg);           // 入队自旋等待
}

// 共享释放 — 关键不同:释放后会传播唤醒,连续唤醒多个等待的共享节点
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();              // 传播释放,逐级唤醒
        return true;
    }
    return false;
}
Condition 条件队列
// synchronized 只有单一条件队列(WaitSet)
// Lock 可以有多个 Condition,每个 Condition 维护自己的等待队列

class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    final Condition notFull  = lock.newCondition();   // "不满"条件
    final Condition notEmpty = lock.newCondition();   // "不空"条件
    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                notFull.await();          // 满了,在 notFull 条件上等待
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal();            // 放入后,通知 notEmpty 条件
        } finally { lock.unlock(); }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await();         // 空了,在 notEmpty 条件上等待
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal();             // 取出后,通知 notFull 条件
            return x;
        } finally { lock.unlock(); }
    }
}

Condition 与 Object monitor 方法的对应

Object MonitorCondition说明
wait()await()等待
wait(timeout)await(time, unit)超时等待
notify()signal()唤醒一个
notifyAll()signalAll()唤醒全部

Condition 底层原理:每个 Condition 内部维护一个单向链表的条件等待队列。await() 时,节点从 AQS 的 CLH 同步队列转移到 Condition 的条件队列;signal() 时,节点从条件队列转移回 CLH 同步队列的队尾,等待重新获取锁。

3.4 读写锁深度解析

ReentrantReadWriteLock 核心规则
读锁(共享)写锁(排他)
读锁✅ 多个读线程可同时持有读锁❌ 有写锁时不能获取读锁
写锁❌ 有读锁时不能获取写锁❌ 只能一个写线程持有写锁
state 的分割使用
// ReentrantReadWriteLock 将 state 高 16 位给读锁,低 16 位给写锁
// state = (sharedCount << 16) | exclusiveCount

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << 16);      // 65536
static final int EXCLUSIVE_MASK = (1 << 16) - 1;  // 65535

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }  // 读锁持有数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }  // 写锁重入次数
锁降级(允许)vs 锁升级(不允许)
// ✅ 锁降级:写锁 → 读锁(安全的,先获写锁再获读锁,再释放写锁)
public void cacheData() {
    rwLock.writeLock().lock();
    try {
        // 1. 修改数据
        data = fetchFromDB();
        // 2. 获取读锁(此时还持有写锁)
        rwLock.readLock().lock();
    } finally {
        // 3. 释放写锁
        rwLock.writeLock().unlock();
    }
    // 4. 此时只持有读锁,可用读锁读取数据
    try {
        use(data);  // 保证数据不被其他写锁修改
    } finally {
        rwLock.readLock().unlock();
    }
}
// 锁降级的意义:在写锁释放后继续持有读锁,
// 防止其他线程在"写完成→读之前"这个窗口期获取写锁修改数据

// ❌ 锁升级:读锁 → 写锁(不允许,会造成死锁)
// 如果两个读线程同时尝试升级,都会等对方释放读锁 → 死锁
StampedLock(JDK8+)
StampedLock sl = new StampedLock();

// 1. 乐观读(无锁,性能最高)
long stamp = sl.tryOptimisticRead();
// 读取共享变量
if (!sl.validate(stamp)) {       // 验证期间是否有写操作
    stamp = sl.readLock();       // 降级到悲观读锁
    try {
        // 重新读取
    } finally {
        sl.unlockRead(stamp);
    }
}

// 2. 悲观读锁
long stamp = sl.readLock();
try { /* 读 */ } finally { sl.unlockRead(stamp); }

// 3. 写锁
long stamp = sl.writeLock();
try { /* 写 */ } finally { sl.unlockWrite(stamp); }
对比ReentrantReadWriteLockStampedLock
可重入❌ 不可重入
锁模式读/写乐观读/悲观读/写
Condition
性能中等更高(乐观读无锁)
使用难度简单复杂(注意 stamp 校验)
读写锁的"写饥饿"问题

现象:大量读线程持续持有读锁,写线程永远拿不到写锁,导致写操作饿死。

原因:读锁是共享的,只要有一个读线程不释放读锁,即使有其他读线程在排队,写线程也必须等所有读锁释放

如何避免

  • 使用 公平模式new ReentrantReadWriteLock(true),按排队顺序,读等写、写等读
  • 使用 StampedLock 乐观读:乐观读不阻塞写(推荐)
  • 设置读锁超时,定期让路

3.5 死锁深度分析

死锁的四个必要条件
条件含义破坏方式
互斥条件资源一次只能被一个线程使用无法破坏(锁的本质)
持有并等待持有资源 A,同时在等待资源 B一次性申请所有资源,不阻塞在持有状态下等待
不可剥夺已获得的资源不能被强制夺取tryLock 超时释放已有资源
循环等待线程间形成头尾相连的等待环路固定加锁顺序(最常用)
死锁编码示例
public class DeadLockDemo {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) {
        // 线程1:先 lockA 后 lockB
        new Thread(() -> {
            synchronized (lockA) {
                System.out.println("T1 拿到 A");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (lockB) {
                    System.out.println("T1 拿到 B");
                }
            }
        }, "T1").start();

        // 线程2:先 lockB 后 lockA  ← 顺序相反,与 T1 形成环路
        new Thread(() -> {
            synchronized (lockB) {
                System.out.println("T2 拿到 B");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (lockA) {
                    System.out.println("T2 拿到 A");
                }
            }
        }, "T2").start();
    }
}
// 输出:T1 拿到 A / T2 拿到 B → 死锁,程序卡住
死锁排查
# 步骤1:找到 Java 进程
jps -l
# 输出:12345 DeadLockDemo

# 步骤2:打印线程堆栈
jstack 12345
# 输出末尾:
# Found one Java-level deadlock:
# =============================
# "T1":
#   waiting to lock monitor 0x00007f... (lockB)
#   - locked <0x00007f...> (lockA)
# "T2":
#   waiting to lock monitor 0x00007f... (lockA)
#   - locked <0x00007f...> (lockB)

# 步骤3(高级):用 jstack -m 混合模式查看,可以看到守护线程和 native 线程
jstack -m 12345

其他排查工具

  • JConsole → 线程 → 检测死锁
  • VisualVM → Thread Dump → 自动检测死锁
  • Arthasthread -b 一键找出阻塞其他线程的罪魁祸首
  • 代码中 ThreadMXBean.findDeadlockedThreads()
预防死锁的实践方法
// 方法1:固定加锁顺序(最推荐)
// 所有线程都按 lockA → lockB 的顺序获取

// 方法2:tryLock 超时放弃
public boolean transfer(Account from, Account to, int amount) {
    while (true) {
        if (from.lock.tryLock()) {
            try {
                if (to.lock.tryLock()) {
                    try {
                        from.debit(amount);
                        to.credit(amount);
                        return true;
                    } finally { to.lock.unlock(); }
                }
            } finally { from.lock.unlock(); }
        }
        // 两个锁没同时拿到时,休眠随机时间后重试
        Thread.sleep(random.nextInt(100));
    }
}

// 方法3:用锁的哈希值统一排序
if (System.identityHashCode(lockA) < System.identityHashCode(lockB)) {
    synchronized (lockA) {
        synchronized (lockB) { /* ... */ }
    }
} else if (System.identityHashCode(lockA) > System.identityHashCode(lockB)) {
    synchronized (lockB) {
        synchronized (lockA) { /* ... */ }
    }
} else {
    // 哈希冲突的处理:引入第三个 tie-breaking 锁
    synchronized (tieLock) {
        synchronized (lockA) {
            synchronized (lockB) { /* ... */ }
        }
    }
}

3.6 ThreadLocal 深度分析

数据结构全景
┌──────────────────────────────────────────────────────┐
│                    Thread 对象                        │
│  ┌──────────────────────────────────────────────┐   │
│  │ threadLocals (ThreadLocalMap)                │   │
│  │                                              │   │
│  │  ┌──────────────────────────────┐            │   │
│  │  │ Entry[] table (默认长度16)     │            │   │
│  │  │                              │            │   │
│  │  │ [0] → (TL@ref1 → value1)    │ key 是弱引用 │   │
│  │  │ [1] → null                  │            │   │
│  │  │ [2] → (TL@ref2 → value2)    │            │   │
│  │  │ [3] → null                  │            │   │
│  │  │ ...                         │            │   │
│  │  └──────────────────────────────┘            │   │
│  │                                              │   │
│  │  Entry extends WeakReference<ThreadLocal<?>> │   │
│  │  Key: 弱引用 → ThreadLocal 对象               │   │
│  │  Value: 强引用 → 业务数据                     │   │
│  └──────────────────────────────────────────────┘   │
│                                                      │
│  ┌──────────────────────────────────────────────┐   │
│  │ inheritableThreadLocals (可被子线程继承)       │   │
│  └──────────────────────────────────────────────┘   │
└──────────────────────────────────────────────────────┘
哈希冲突处理

ThreadLocalMap 使用开放地址法(线性探测)处理哈希冲突,而不是 HashMap 的链地址法。

// 哈希计算
int i = key.threadLocalHashCode & (table.length - 1);

// 如果 i 位置被占,则 i+1, i+2, i+3... 线性探测找到下一个空位
// ThreadLocal 的 hashCode 是递增的魔法数 0x61C88647(黄金分割数),
// 确保哈希分布均匀,减少冲突
set/get/remove 源码分析
// set()
private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        if (k == key) {
            e.value = value;       // 找到,更新 value
            return;
        }
        if (k == null) {
            replaceStaleEntry(key, value, i);  // 清理过期的 Entry (key 被 GC 回收)
            return;
        }
    }
    tab[i] = new Entry(key, value);  // 新位置
    // ...
}

// get()
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);       // 获取当前线程的 ThreadLocalMap
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);  // 线性探测查找
        if (e != null) return (T) e.value;
    }
    return setInitialValue();  // 未找到,初始化默认值
}
内存泄漏详细分析

泄漏链路

Thread 对象(强引用)
  → ThreadLocalMap(强引用)
    → Entry[](强引用)
      → Entry.Value(强引用)
        → Entry.Key(弱引用 → ThreadLocal 对象)

GC 发生时:ThreadLocal 对象因为没有外部强引用了,被回收
  → Entry.Key 变成 null
  → 但 Value 通过 Thread → ThreadLocalMap → Entry → Value 这条强引用链可达
  → Value 永远不会被回收
  → 只要 Thread 不销毁,Value 就一直占用内存

两种泄漏情况

场景线程类型泄漏后果
线程池线程复用,几乎不销毁泄漏永久累积,OOM
普通线程任务完毕线程销毁线程销毁时 ThreadLocalMap 回收,无泄漏

ThreadLocal 自身的防护措施

  • get()/set() 时会探测 key==null 的过期 Entry 并清除(启发式清理)
  • remove() 主动清除指定 Entry
  • 扩容时全量清理过期 Entry

最佳实践

// 始终在 finally 中调用 remove()
ThreadLocal<MyData> tl = ThreadLocal.withInitial(MyData::new);
try {
    MyData data = tl.get();
    // ... 使用 data ...
} finally {
    tl.remove();  // ⚠️ 必须清理!
}
InheritableThreadLocal
// 父线程创建的 ThreadLocal 值可以传递给子线程
InheritableThreadLocal<String> itl = new InheritableThreadLocal<>();
itl.set("parent-value");

new Thread(() -> {
    System.out.println(itl.get());  // "parent-value"
}).start();

原理Thread 构造时,如果父线程的 inheritableThreadLocals 不为空,会复制一份给子线程。注意是浅拷贝,父线程和子线程指向同一个 Value 对象。


四、线程池深度解析

4.1 线程池核心设计

ThreadPoolExecutor 核心字段
public class ThreadPoolExecutor extends AbstractExecutorService {
    // ctl 是一个 AtomicInteger,高 3 位存线程池状态,低 29 位存线程数量
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;  // 29
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1; // 约 5 亿

    // 线程池 5 种状态(高 3 位)
    private static final int RUNNING    = -1 << COUNT_BITS;  // 111..., 接受新任务,处理队列
    private static final int SHUTDOWN   =  0 << COUNT_BITS;  // 000..., 不接新,处理队列
    private static final int STOP       =  1 << COUNT_BITS;  // 001..., 不接新,不处理,中断进行中
    private static final int TIDYING    =  2 << COUNT_BITS;  // 010..., 任务全空,线程为0,过渡态
    private static final int TERMINATED =  3 << COUNT_BITS;  // 011..., terminated() 执行完

    private final BlockingQueue<Runnable> workQueue;   // 任务队列
    private final HashSet<Worker> workers = new HashSet<>(); // 工作线程集合
    private int largestPoolSize;    // 历史峰值线程数
    private long completedTaskCount;// 已完成任务总数
}

一个原子变量 ctl 同时维护状态和数量的设计非常精妙,所有状态判断和数量修改都是原子的 CAS 操作。

线程池状态转换图
         ┌──────────┐
         │ RUNNING  │
         └────┬─────┘
              │ shutdown()
         ┌────▼─────┐
         │ SHUTDOWN │ ─────────┐
         └────┬─────┘          │ 队列为空
              │ shutdownNow()  │ 线程为 0
         ┌────▼─────┐          │
         │   STOP   │ ─────────┤
         └────┬─────┘          │ 线程为 0
              │                │
         ┌────▼─────┐          │
         │ TIDYING  │ ◄────────┘
         └────┬─────┘
              │ terminated() 执行完
         ┌────▼────────┐
         │ TERMINATED  │
         └─────────────┘
Worker 类 — 工作线程的封装
private final class Worker extends AbstractQueuedSynchronizer
                            implements Runnable {
    final Thread thread;           // 封装的工作线程
    Runnable firstTask;            // 构造时指定的首个任务,可为 null
    volatile long completedTasks;  // 该 Worker 完成的任务计数

    Worker(Runnable firstTask) {
        setState(-1);              // 初始 AQS state=-1,禁止中断直到 runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    public void run() {
        runWorker(this);           // 核心!循环从队列取任务执行
    }
    // Worker 的 AQS 实现了一个不可重入的排他锁,
    // locked 表示该 Worker 正在执行任务
    // unlocked 表示该 Worker 是空闲的
}
runWorker — 核心工作循环
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock();  // 允许中断
    boolean completedAbruptly = true;
    try {
        // ⭐ 核心循环:如果 firstTask 不为空则执行,否则从队列取
        while (task != null || (task = getTask()) != null) {
            w.lock();  // 执行任务前加锁
            // 检查线程池状态,如果已 STOP 则中断
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
                && !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);          // 钩子方法
                Throwable thrown = null;
                try {
                    task.run();                   // 执行任务
                } catch (Throwable x) {
                    thrown = x; throw x;
                } finally {
                    afterExecute(task, thrown);   // 钩子方法
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);  // Worker 退出处理
    }
}
getTask — 从队列获取任务

这是理解核心线程如何保持存活的关键

private Runnable getTask() {
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 检查:如果 STOP,或者 SHUTDOWN 且队列为空,减少 Worker 并返回 null
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
        // ⭐ 决定是否超时取任务
        // allowCoreThreadTimeOut=true 或 当前线程数 > corePoolSize → 超时取
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 如果应退出(超时拿到 null 或 线程太多或超时机制触发)
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;     // 返回 null → runWorker 循环退出 → Worker 销毁
            continue;
        }
        try {
            // ⭐ 关键区别:
            // timed=true  → poll(keepAliveTime) — 超时返回 null,Worker 可退出
            // timed=false → take() — 永久阻塞等待,Worker 常驻
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null) return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

核心线程如何保持存活?

  • 当前线程数 ≤ corePoolSize 时,timed = false,调用 workQueue.take() 永久阻塞等待
  • 当线程数 > corePoolSize 时,timed = true,调用 workQueue.poll(keepAliveTime) 超时等待
  • 如果希望核心线程也能回收,设置 allowCoreThreadTimeOut = true

4.2 线程池任务提交流程(完整版)

// execute() 源码分析
public void execute(Runnable command) {
    if (command == null) throw new NullPointerException();
    int c = ctl.get();

    // 步骤 1:当前线程数 < 核心线程数?→ 直接创建新 Worker(addWorker 第二个参数=true)
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))   // core=true, 计入核心线程
            return;
        c = ctl.get();                  // 创建失败(状态变化),重新获取
    }

    // 步骤 2:线程池 RUNNING 且入队成功
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 双重检查:入队后线程池可能已经 shutdown,需要回滚
        if (!isRunning(recheck) && remove(command))
            reject(command);            // 回滚后执行拒绝策略
        // 或者线程池还在运行,但所有线程都死了(极端情况),创建一个新 Worker
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 步骤 3:队列满了,尝试扩到最大线程数
    else if (!addWorker(command, false))  // core=false, 计入非核心线程
        reject(command);                  // 达到最大线程数也放不下,拒绝
}
                        提交任务
                           │
                           ▼
                   ┌──────────────┐
                   │ 核心线程满了?  │
                   └──────┬───────┘
                      N   │   Y
                      ▼   │   ▼
                  ┌───────┐│┌─────────────┐
                  │addWorker││ workQueue   │
                  │(core=true││ .offer()   │
                  │)        ││ 入队成功?   │
                  └───────┘│└──────┬──────┘
                           │   Y   │   N
                           │   ▼   │   ▼
                           │ ┌────┐│┌──────────┐
                           │ │双重 │││addWorker  │
                           │ │检查 │││(core=false│
                           │ └────┘││) 成功?   │
                           │       │└────┬─────┘
                           │       │  Y  │  N
                           │       │  ▼  │  ▼
                           │       │┌───┐│┌──────┐
                           │       ││执行│││拒绝  │
                           │       │└───┘││策略  │
                           │       │     │└──────┘
                           └───────┴─────┘

4.3 线程池大小配置

如何合理设置核心参数?
int cpuCores = Runtime.getRuntime().availableProcessors();

// ═══ CPU 密集型(计算密集型)═══
// 大量计算、很少阻塞
// 线程数 = CPU 核数 + 1(+1 是为了偶尔的缺页中断/其他暂停时有多余线程顶上去)
int cpuThreads = cpuCores + 1;

// ═══ IO 密集型 ═══
// 大量网络/磁盘 IO,大部分时间在等待
// 根据理论公式:线程数 = CPU核数 × (1 + 平均等待时间/平均计算时间)
// 等待时间/计算时间 的比例很难精确获得,通常取 2 或根据压测调整

// 常见两种公式:
// 公式1:线程数 = CPU核数 × 2
// 公式2:线程数 = CPU核数 / (1 - 阻塞系数)
//        阻塞系数通常取 0.8 ~ 0.9(IO 密集下大部分时间在等 IO)
int ioThreads = cpuCores * 2;

// ═══ 混合型 ═══
// 核心线程数 = (任务总耗时 / CPU计算时间) × CPU核数
// 或者拆分为两个线程池分开执行

最佳实践:公式只是起点,实际值应在压测中确定

Runtime.getRuntime().availableProcessors() 获取的值:
在 Docker/容器环境中,可能返回宿主机 CPU 核数而非容器限制的核数!
JDK 8u191+ 修复,或用 -XX:ActiveProcessorCount=N 指定
线程池参数动态化(美团方案)
// 核心思路:通过配置中心动态调整参数,无需重启
public class DynamicThreadPool extends ThreadPoolExecutor {
    public void setCorePoolSize(int corePoolSize) {
        super.setCorePoolSize(corePoolSize);
    }
    public void setMaximumPoolSize(int maximumPoolSize) {
        super.setMaximumPoolSize(maximumPoolSize);
    }
    // 结合配置中心(Nacos/Apollo),监听参数变化实时调整
}

4.4 拒绝策略详解

策略行为适用场景
AbortPolicy(默认)抛出 RejectedExecutionException需要感知拒绝、由上层处理
CallerRunsPolicy由提交任务的主线程执行不能丢任务的场景,有"反压"效果
DiscardPolicy直接丢弃,无任何通知允许丢、不重要(埋点日志、监控采样)
DiscardOldestPolicy丢弃队首(最旧)任务,重新提交更关注最新数据(实时数据展示)

CallerRunsPolicy 的"反压"机制

当队列满时,由调用者线程直接执行任务
→ 调用者线程被占用,无法继续提交新任务
→ 相当于把压力反向传导给上游
→ 形成自然的流量控制(TCP 滑动窗口同理)

自定义拒绝策略

public class MyRejectedHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 1. 记录日志(包括任务信息、线程池状态)
        log.warn("Task rejected: {}, pool size: {}, queue size: {}",
                 r.toString(), executor.getPoolSize(), executor.getQueue().size());
        // 2. 降级处理:写入持久化存储(DB/Redis),后续补偿
        // 3. 或者重新尝试提交
        if (!executor.isShutdown()) {
            try {
                executor.getQueue().put(r);  // 阻塞式入队
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}
execute() vs submit() 异常处理差异(⭐ 重要坑点)
ThreadPoolExecutor pool = new ThreadPoolExecutor(...);

// ❌ 方法1:execute(Runnable) — 任务内未捕获异常 → 直接抛出
pool.execute(() -> {
    throw new RuntimeException("将会传播给线程的 UncaughtExceptionHandler");
});
// 线程的 UncaughtExceptionHandler 会收到异常,线程终止,线程池会创建新线程替补

// ⚠️ 方法2:submit(Runnable) — 异常被吞掉!
Future<?> future = pool.submit(() -> {
    throw new RuntimeException("静默吞掉,你必须调用 get() 才能拿到");
});
// future.get() 才会抛出 ExecutionException,包装了原始异常

// ✅ 正确做法:submit 后必须 get() 或 自行 try-catch
Future<String> future2 = pool.submit(() -> {
    return "result";
});
try {
    String result = future2.get();  // get() 才会将任务内的异常通过 ExecutionException 抛出
} catch (ExecutionException e) {
    Throwable cause = e.getCause(); // 原始异常
    log.error("Task failed", cause);
}
维度execute(Runnable)submit(Runnable/Callable)
异常传播直接抛给 UncaughtExceptionHandler被吞掉,只有 get() 才通过 ExecutionException 抛出
线程行为异常线程终止,线程池创建新线程替补线程正常返回线程池复用,异常被封装在 Future 中
返回值通过 Future.get() 获取返回值
建议自主 try-catch必须 get() 或 try-catch

最佳实践

// 线程池统一异常处理方式 1:自定义 ThreadFactory 设置 UncaughtExceptionHandler
ThreadFactory factory = r -> {
    Thread t = new Thread(r);
    t.setUncaughtExceptionHandler((thread, ex) ->
        log.error("Thread {} crashed: {}", thread.getName(), ex.getMessage(), ex)
    );
    return t;
};

// 线程池统一异常处理方式 2:重写 afterExecute 钩子
ThreadPoolExecutor pool = new ThreadPoolExecutor(...) {
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if (t == null && r instanceof Future<?>) {
            try {
                ((Future<?>) r).get();  // submit 的任务从 Future 拿出异常
            } catch (ExecutionException e) {
                t = e.getCause();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        if (t != null) {
            log.error("Task exception: {}", t.getMessage(), t);
        }
    }
};

4.5 Executors 四大线程池的风险

// ❌ FixedThreadPool — 无界队列导致 OOM
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>());  // ⚠️ 无界!无限堆积
}

// ❌ SingleThreadExecutor — 无界队列导致 OOM
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(
        1, 1, 0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>()));  // ⚠️ 无界!
}

// ❌ CachedThreadPool — 不限线程数导致 OOM
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  // ⚠️ 最大线程数为 Integer.MAX_VALUE
        60L, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>());
}

// ❌ ScheduledThreadPool — 最大线程数为 Integer.MAX_VALUE
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
    // maximumPoolSize = Integer.MAX_VALUE  // ⚠️
}

正确创建方式(阿里巴巴规约):

// 生产环境标准写法
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    10,                                    // 核心线程数(根据业务配置)
    20,                                    // 最大线程数
    60L, TimeUnit.SECONDS,                 // 非核心线程空闲存活时间
    new LinkedBlockingQueue<>(1000),       // ⚠️ 必须设置有界队列
    new ThreadFactoryBuilder()             // Guava 工具类
        .setNameFormat("biz-pool-%d")      // ⚠️ 命名线程,方便排查
        .setDaemon(false)
        .build(),
    new ThreadPoolExecutor.CallerRunsPolicy()  // 指定拒绝策略
);

// 使用完记得 shutdown
Runtime.getRuntime().addShutdownHook(new Thread(pool::shutdown));

4.6 线程池监控

// 定期打印线程池核心指标
public class ThreadPoolMonitor {
    public static void logPoolStatus(ThreadPoolExecutor pool) {
        log.info("ThreadPool [{}]: " +
                 "active={}, " +           // 正在执行任务的线程数
                 "pool={}, " +             // 当前线程数(含空闲)
                 "core={}, " +             // 核心线程数
                 "max={}, " +              // 最大线程数
                 "largest={}, " +          // 历史峰值线程数
                 "queue={}, " +            // 当前队列任务数
                 "completed={}, " +        // 已完成任务总数
                 "task={} ",               // 总任务数 ≈ completed + active + queue
                 poolName,
                 pool.getActiveCount(),
                 pool.getPoolSize(),
                 pool.getCorePoolSize(),
                 pool.getMaximumPoolSize(),
                 pool.getLargestPoolSize(),
                 pool.getQueue().size(),
                 pool.getCompletedTaskCount(),
                 pool.getTaskCount());
    }
}

五、JUC 工具类详解

5.1 CountDownLatch / CyclicBarrier / Semaphore

三者对比
维度CountDownLatchCyclicBarrierSemaphore
核心作用一个线程等待一组操作完成一组线程相互等待到齐控制并发访问数量
可复用❌ 减到 0 不可重置✅ barrier 可循环使用✅ acquire/release
计数方向递减(countDown -1)递增(等待数 +1 到 parties)递减/递增(许可的消耗与释放)
等待线程调用方 await 等待所有参与者 await 等待调用方 acquire 等待许可
底层实现AQS 共享模式,state = countReentrantLock + ConditionAQS 共享模式,state = permits
CountDownLatch 源码与原理
// 基于 AQS,state 初始化 = count
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

// countDown: 通过 CAS 将 state - 1,减到 0 时释放所有等待线程
public void countDown() {
    sync.releaseShared(1);  // tryReleaseShared 在 state == 0 时返回 true
}

// await: 如果 state != 0,则阻塞等待
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1); // tryAcquireShared 在 state == 0 时返回 1
}

使用场景

  • 主线程等待所有子线程初始化完成
  • 并行任务全部完成后汇总结果
  • 微服务启动时的依赖等待(等所有依赖服务就绪)
CyclicBarrier 源码与原理
// 与 CountDownLatch 不同,CyclicBarrier 内部用 ReentrantLock + Condition 实现
public CyclicBarrier(int parties, Runnable barrierAction) {
    // parties: 需要等待的线程数
    // barrierAction: 所有线程到齐后执行的回调(最后一个到达的线程执行)
}

// await 内部逻辑(简化):
// 1. 获取 ReentrantLock
// 2. count--(当前代剩余等待数)
// 3. 如果 count == 0:
//      a. 执行 barrierAction.run()
//      b. nextGeneration() → 唤醒所有等待线程 → count 重置为 parties
// 4. 如果 count > 0:
//      进入 Condition 等待,直到被唤醒或超时/中断
//      如果超时/中断 → breakBarrier() → 唤醒所有线程 → 抛出异常

与 CountDownLatch 的关键区别

// CountDownLatch: 外部线程等待多个子任务完成
主线程.await()  ←  子线程1.countDown()
               ←  子线程2.countDown()
               ←  子线程3.countDown()

// CyclicBarrier: 多个线程互相等待,同进同退
子线程1.await() ─┐
子线程2.await() ─┼→ 全部到达 → barrier 打开 → 一起继续执行
子线程3.await() ─┘                   → count 重置,可复用
Semaphore 源码与原理
// 基于 AQS,state = 许可证数量
Semaphore semaphore = new Semaphore(3);      // 3 个许可证,非公平
Semaphore fairSem = new Semaphore(3, true);  // 公平模式

// acquire: 获取 1 个许可证,state-1。如果 state=0,等待直到有许可证
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

// release: 释放 1 个许可证,state+1。可能唤醒等待线程
public void release() {
    sync.releaseShared(1);
}

典型场景

  • 数据库连接池限流(最多 N 个线程同时访问数据库)
  • 接口限流(并发度控制)— 实现漏桶/令牌桶的基础
  • 停车位管理(N 个车位,来一个车占一个,走一个车释放一个)

5.2 ConcurrentHashMap 深度解析

JDK7 → JDK8 架构演进
JDK 7: Segment 分段锁
┌─────────────────────────────────────────────┐
│              ConcurrentHashMap              │
│  ┌──────────┬──────────┬──────────┬───────┐ │
│  │Segment[0]│Segment[1]│Segment[2]│  ...  │ │ 每个 Segment 是一把 ReentrantLock
│  │  ┌────┐  │  ┌────┐  │  ┌────┐  │       │ │ 默认 16 个 Segment,并发度 16
│  │  │Hash│  │  │Hash│  │  │Hash│  │       │ │
│  │  │Table│  │  │Table│  │  │Table│  │       │ │ 要定位两次:先定位 Segment,再定位 HashEntry
│  │  └────┘  │  └────┘  │  └────┘  │       │ │
│  └──────────┴──────────┴──────────┴───────┘ │
└─────────────────────────────────────────────┘

JDK 8: CAS + synchronized 细粒度锁
┌─────────────────────────────────────────────┐
│              ConcurrentHashMap              │
│  ┌─────┬─────┬─────┬─────┬─────┬─────┬───┐ │
│  │ [0] │ [1] │ [2] │ [3] │ [4] │ [5] │...│ │ Node<K,V> 数组
│  │     │  ●  │     │     │     │     │   │ │ 每个桶:
│  └─────┴──┼──┴─────┴─────┴─────┴─────┴───┘ │  空桶 → CAS 直接 put
│           │ synchronized                    │  有值 → synchronized 锁桶头
│     ┌─────▼────┐                             │
│     │ Node     │ → Node → ...  (链表)       │  链表 > 8 → 红黑树
│     └──────────┘                             │
└─────────────────────────────────────────────┘
JDK8 ConcurrentHashMap 关键字段
public class ConcurrentHashMap<K,V> {
    transient volatile Node<K,V>[] table;           // 当前数组,volatile 保证可见性
    private transient volatile Node<K,V>[] nextTable; // 扩容时的目标数组
    private transient volatile int sizeCtl;          // 多义性字段
    // sizeCtl 含义:
    // -1:   正在初始化
    // -(1+N): 有 N 个线程正在协助扩容
    // >0:   初始化前的容量 或 扩容阈值 (0.75n)

    static class Node<K,V> {
        final int hash;
        final K key;
        volatile V val;           // volatile 保证可见性
        volatile Node<K,V> next;  // volatile 保证可见性
    }
}
put 方法完整流程
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException(); // ⚠️ 不支持 null
    int hash = spread(key.hashCode());  // 扰动函数,降低冲突
    int binCount = 0;

    for (Node<K,V>[] tab = table;;) {  // 自旋循环
        Node<K,V> f; int n, i, fh;
        // ① 如果 table 为空或长度为 0 → 初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        // ② 桶为空 → CAS 直接放入,无锁
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break;  // CAS 成功,直接返回
        }
        // ③ 遇到 ForwardingNode (hash == MOVED(-1)) → 帮助扩容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        // ④ 桶不为空 → synchronized 锁住桶头节点
        else {
            V oldVal = null;
            synchronized (f) {  // ⭐ 只锁当前桶的头节点
                if (tabAt(tab, i) == f) {     // 双重检查
                    if (fh >= 0) {            // 普通链表节点
                        binCount = 1;
                        // 遍历链表,找到相同的 key 则更新,否则追加到尾部
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent) e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key, value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {  // 红黑树节点
                        // ... 树操作 ...
                    }
                }
            }
            if (binCount != 0) {
                // 链表长度 >= 8 → 转为红黑树
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                // ...
                break;
            }
        }
    }
    // ⑤ 计数 + 1,并检查是否需要扩容
    addCount(1L, binCount);
    return null;
}

关键设计点

  1. 空桶 CAS:没有锁竞争,高效
  2. synchronized 只锁桶头:锁粒度最细
  3. 遇到 ForwardingNode 帮助扩容:多线程协同迁移
  4. initTable 也用 CAS:保证只有一个线程初始化
get 方法 — 完全无锁
public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    int h = spread(key.hashCode());
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        // 桶头恰好命中
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        // hash < 0: 可能是树节点或 ForwardingNode
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        // 普通链表遍历
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}
// 全程无锁,靠 volatile 保证 Node.val 和 Node.next 的可见性
扩容机制 — 多线程协同迁移
// 扩容的两个核心方法
// 1. transfer:将原 table 的数据迁移到新 table
// 2. helpTransfer:发现正在扩容时,当前线程帮忙迁移

// 扩容过程(简化为 5 步):
// ① 一个线程触发扩容(put 后 addCount 发现超过阈值)
// ② 创建新的 2 倍大小的数组
// ③ 将原数组按步长(如每个线程负责16个桶)分段
// ④ 每个参与线程处理自己负责的桶区间:
//      - 链表:构造高位链和低位链(根据 hash & n 是否为 0 分流)
//      - 树:类似处理
// ⑤ 迁移完成后,在老数组桶位放置 ForwardingNode 标记"已迁移"

// ForwardingNode: hash = MOVED(-1),作用有3个:
// 1. 标记该桶已经迁移完成
// 2. find() 方法转发到新数组查找
// 3. 其他线程读到 ForwardingNode 知道正在扩容,参与协助
size 计数 — 分段计数避免竞争
// JDK8 不使用 JDK7 的 segment.modCount 累加方式
// 而是使用类似 LongAdder 的分段计数:

// 无竞争时直接 CAS baseCount
// 有竞争时使用 CounterCell[] 分散热点,最终 sum = baseCount + ΣCounterCell[].value

// addCount 简化逻辑:
// 1. 先 CAS baseCount+1,成功则返回
// 2. 失败 → 线程生成随机数,落到 CounterCell[x]
// 3. CAS CounterCell[x].value+1
// 4. 检查是否需要扩容

// size() / mappingCount() 求和:
// long sum = baseCount
// for (CounterCell cell : counterCells)
//     sum += cell.value
// 注意:size() 返回 int 可能溢出,推荐用 mappingCount() 返回 long
JDK7 vs JDK8 差异总结
对比维度JDK 7JDK 8
数据结构Segment 数组 + HashEntry 链表Node 数组 + 链表/红黑树
锁机制Segment(继承 ReentrantLock)CAS + synchronized(桶头)
锁粒度Segment 级别(默认 16 段)桶级别(最细)
get 操作需要加锁(volatile 读)完全无锁 + volatile 保证可见性
并发度固定 16(构造函数指定,不可改)桶数量级(table 长度,可动态扩容)
扩容单线程迁移 Segment 内部多线程协同迁移(transfer)
链表→红黑树❌ 全是链表✅ 链表 > 8 且 table >= 64 时转红黑树
size 计算先不加锁算 3 次,不一致再加锁分段累加(baseCount + CounterCell[])
null 键值❌ 不支持❌ 不支持
HashMap 并发问题详解(⭐ 重要背景)

HashMap 为什么线程不安全?

// JDK7:并发 resize 导致链表死循环(环形链表)
// 头插法:resize 扩容时将旧链表元素迁移到新数组,使用头插法
// 线程 A resize 时形成反向链表,线程 B 继续遍历 → 环形链表 → CPU 100% 死循环

// JDK8:不再死循环(改用尾插法),但仍有数据丢失问题
// 两个线程同时 put 时,一个线程的数据可能被另一个覆盖
// counter 累加非原子操作,导致 size 不准确

对比三种 Map

维度HashMapHashtableConcurrentHashMap
线程安全❌ 不安全synchronized 全表锁✅ CAS + synchronized 桶锁
性能(全表锁,串行化)(细粒度锁)
null 键值✅ 允许 1 个 null key❌ 不允许❌ 不允许(NullPointerException)
JDK8 结构数组 + 链表/红黑树数组 + 链表数组 + 链表/红黑树 + 多线程协同扩容
迭代器fail-fastEnumeration/Iterator弱一致性(不抛 CME)
推荐度单线程❌ 已废弃⭐⭐⭐⭐⭐

Hashtable 已废弃,所有方法加 synchronized,同一时刻只能一个线程操作,高并发下退化为串行。

⚠️ CHM 复合操作仍需额外同步
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// ❌ 错误:单个 put/get 是线程安全的,但复合操作不是原子的!
Integer count = map.get("key");
if (count == null) {
    map.put("key", 1);           // 两个线程可能同时到达这里
} else {
    map.put("key", count + 1);   // 读-改-写三步,不是原子的
}

// ✅ 正确方式1:使用 CHM 提供的原子复合操作
map.putIfAbsent("key", 1);                              // 不存在才 put
map.compute("key", (k, v) -> v == null ? 1 : v + 1);   // 原子计算
map.computeIfAbsent("key", k -> 1);                      // 不存在才计算
map.merge("key", 1, Integer::sum);                      // 原子合并

// ✅ 正确方式2:外置加锁
synchronized (map) {
    Integer c = map.get("key");
    map.put("key", c == null ? 1 : c + 1);
}

CHM 原子复合操作速查

方法含义
putIfAbsent(K, V)Key 不存在时才插入
remove(K, V)Key 和 Value 都匹配时才删除
replace(K, V1, V2)旧值匹配才替换
compute(K, BiFunction)原子计算(整个过程桶级加锁)
computeIfAbsent/IfPresent按条件计算
merge(K, V, BiFunction)原子合并

5.3 CopyOnWriteArrayList

核心思想:读操作完全无锁,写操作复制新数组后在副本上修改,然后原子替换引用。

public class CopyOnWriteArrayList<E> {
    // ⭐ volatile 数组引用,保证写操作的可见性
    private transient volatile Object[] array;

    // 读操作:直接读原数组,无锁
    public E get(int index) {
        return elementAt(array, index);
    }

    // 写操作:加 ReentrantLock,复制新数组,修改后原子替换
    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);  // 复制新数组(内存拷贝)
            es[len] = e;
            setArray(es);                     // 原子替换引用(volatile 写)
            return true;
        }
    }

    // 迭代器:快照(Snapshot)思想
    // 迭代器创建时保存了当时 array 的引用,后续的写操作都在新数组上进行
    // 所以迭代器完全不受并发写影响,也不抛 ConcurrentModificationException
}

优缺点对比

优点缺点
读操作完全无锁,高并发读性能极好写操作复制整个数组,内存开销 O(n)
读写分离,写不影响读数据弱一致性(读可能读到旧数据)
迭代器是快照,不会抛 CMEGC 压力大(旧数组变垃圾)

适用场景读多写极少 — 黑名单、白名单、监听器列表、配置信息

不适用场景:写操作频繁(每次写都复制整个数组)、需要强一致性的场景

5.4 BlockingQueue 详解

实现类底层结构有界/无界锁机制特点
ArrayBlockingQueue数组(环形队列)必须有界1 把锁 put+take 共用结构简单,内存预分配,公平锁可选
LinkedBlockingQueue单向链表可选有界(默认 Integer.MAX_VALUE)2 把锁 putLock + takeLock读写分离,吞吐量更高
SynchronousQueue无存储空间零容量生产者直接传递给消费者,一对一交接
PriorityBlockingQueue数组实现的堆无界1 把锁按优先级出队,无界需注意 OOM
DelayQueuePriorityQueue无界1 把锁延迟到期才能出队(ScheduledThreadPool 内部使用)
LinkedTransferQueue单向链表无界CAS + 自旋JDK7+, 结合了 SynchronousQueue 和 LinkedBlockingQueue

生产者-消费者模式(经典写法)

// ArrayBlockingQueue 实现
BlockingQueue<Task> queue = new ArrayBlockingQueue<>(100);

// 生产者
queue.put(task);      // 阻塞直到有空间
queue.offer(task);    // 不阻塞,满了返回 false(推荐业务代码用这个,有限时间)

// 消费者
Task task = queue.take();      // 阻塞直到有元素
Task task = queue.poll(1, TimeUnit.SECONDS);  // 超时等待

5.5 CompletableFuture 深度解析

核心 API 分类
// ═══════ 1. 创建 CompletableFuture ═══════
CompletableFuture<Void> cf1 = CompletableFuture.runAsync(() -> {});
CompletableFuture<T>    cf2 = CompletableFuture.supplyAsync(() -> result);

// ═══════ 2. 转换(Transform): 输入→处理→新输出 ═══════
cf.thenApply(fn)         // T → U,同步
   .thenApplyAsync(fn)   // T → U,异步

// ═══════ 3. 消费(Consume): 消费结果,无返回 ═══════
cf.thenAccept(consumer)     // T → Void
   .thenRun(runnable)       // 无入参,执行一段逻辑

// ═══════ 4. 组合(Combine): 两个都完成 ═══════
cf1.thenCombine(cf2, bifn)       // (T, U) → V
cf1.thenCompose(fn)              // T → CompletableFuture<U>,展平

// ═══════ 5. 选择(Either): 任一完成 ═══════
cf1.applyToEither(cf2, fn)       // 取先完成的那个
cf1.acceptEither(cf2, consumer)

// ═══════ 6. 多任务 ═══════
CompletableFuture.allOf(cf1, cf2, cf3)  // 全部完成
CompletableFuture.anyOf(cf1, cf2, cf3)  // 任一完成

// ═══════ 7. 异常处理 ═══════
cf.exceptionally(ex -> fallback)            // 异常兜底
  .handle((result, ex) -> ...)              // 正常/异常都处理
  .whenComplete((result, ex) -> ...)        // 正常/异常都执行(无返回值,观察者模式)
典型使用场景
// 场景1:异步调用链
CompletableFuture.supplyAsync(() -> rpcService.queryUser(userId))
    .thenApplyAsync(user -> rpcService.queryOrders(user))
    .thenAcceptAsync(orders -> saveToCache(orders))
    .exceptionally(ex -> {
        log.error("query failed", ex);
        return null;
    });

// 场景2:并行调用,合并结果
CompletableFuture<User> userFuture =
    CompletableFuture.supplyAsync(() -> userService.getUser(id));
CompletableFuture<Order> orderFuture =
    CompletableFuture.supplyAsync(() -> orderService.getOrders(id));

String result = userFuture
    .thenCombine(orderFuture, (user, orders) -> user.getName() + orders.size())
    .get(3, TimeUnit.SECONDS);

// 场景3:双数据源竞速
CompletableFuture<String> fromDB =
    CompletableFuture.supplyAsync(() -> db.query(sql));
CompletableFuture<String> fromRedis =
    CompletableFuture.supplyAsync(() -> redis.get(key));

fromDB.applyToEither(fromRedis, result -> result)
      .orTimeout(500, TimeUnit.MILLISECONDS)   // JDK9+
      .exceptionally(ex -> "fallback");

5.6 ForkJoinPool

// ForkJoinPool 是 JDK7 引入的分治任务执行框架
// CompletableFuture.supplyAsync() 默认用的就是 ForkJoinPool.commonPool()

// 核心思想:工作窃取(Work Stealing)
// 每个工作线程有自己的双端队列
// 线程执行完自己的任务后,从其他繁忙线程的队列尾部"偷"任务来执行

工作窃取示意图

线程 A (忙):  [task1 ← task2 ← task3] ← pop/push 操作这端
线程 B (闲):  自己的队列空了 → 从 A 的队列尾部 steal task1
线程 A:       继续处理 task2, task3,两个线程并行工作

ForkJoinTask 的两个子类

  • RecursiveTask<V>:有返回值的任务
  • RecursiveAction:无返回值的任务
// 经典示例:并行计算 1+2+...+N
class SumTask extends RecursiveTask<Long> {
    static final int THRESHOLD = 10000;
    long[] array; int low; int high;

    @Override
    protected Long compute() {
        if (high - low <= THRESHOLD) {
            long sum = 0;
            for (int i = low; i < high; i++) sum += array[i];
            return sum;
        }
        int mid = (low + high) >>> 1;
        SumTask left = new SumTask(array, low, mid);
        SumTask right = new SumTask(array, mid, high);
        left.fork();                       // 异步执行左半部分
        Long rightResult = right.compute(); // 同步执行右半部分
        Long leftResult = left.join();      // 等待左半部分
        return leftResult + rightResult;
    }
}

ForkJoinPool pool = new ForkJoinPool();
long result = pool.invoke(new SumTask(array, 0, array.length));

ForkJoinPool vs ThreadPoolExecutor

对比ThreadPoolExecutorForkJoinPool
任务队列一个共享 BlockingQueue每个线程有自己的双端队列
线程空闲时阻塞等待新任务窃取其他线程的任务
适合场景独立、无依赖的任务可分解、有依赖的分治任务
CPU 利用可能有线程空闲更高,因为工作窃取

六、面试高频问题速答

Q1:synchronized 和 Lock 的区别?什么时候用哪个?

维度synchronizedLock(ReentrantLock)
实现JVM 关键字,monitorJDK API,AQS
释放自动(代码块退出/异常)手动 finally{unlock()}
中断❌ 等锁时不可中断lockInterruptibly()
超时tryLock(timeout)
公平❌ 非公平✅ 可选
条件❌ 单条件 wait/notify✅ 多 Condition
性能JDK6+ 优化后差距不大高竞争场景略优

选择原则

  • 能用 synchronized 就用(代码更安全、更简洁)
  • 需要超时可中断多条件tryLock 时 → Lock

Q2:volatile 的底层实现和为什么不能保证原子性?

底层:内存屏障(Memory Barrier)

  • volatile 写 → 前插 StoreStore,后插 StoreLoad → 强制刷回主内存
  • volatile 读 → 后插 LoadLoad、LoadStore → 强制从主内存读

不能保证原子性i++ 包含读→改→写三步,volatile 只保证每一步的值是最新的,但无法保证三步之间不被其他线程插入。

Q3:ThreadLocal 内存泄漏原因和解决方案?

原因:ThreadLocalMap 的 Entry 的 Key 是弱引用(ThreadLocal 对象),Value 是强引用。ThreadLocal 被 GC → Key 变 null → Value 无法访问但通过线程引用链可达 → 只要线程不销毁就永久占用。

解决方案用完必须 remove(),特别是线程池场景。

Q4:线程池提交任务的执行流程?

  1. < 核心线程数 → 创建核心线程
  2. 核心线程满 → 入阻塞队列
  3. 队列满 → 创建线程直到达到最大线程数
  4. 达到最大且队列满 → 拒绝策略

Q5:ConcurrentHashMap 如何保证线程安全?

JDK7:Segment + ReentrantLock,默认 16 段,每段独立加锁。
JDK8:CAS + synchronized + volatile。

  • put:空桶 CAS 直插,有冲突 synchronized 锁桶头
  • get:完全无锁,volatile 保证可见性
  • 扩容:多线程协同迁移(helpTransfer)
  • size:分段累加(baseCount + CounterCell[])

Q6:如何排查死锁?

jps -l           # 找 PID
jstack <pid>     # 末尾自动检测死锁,打印死锁信息
# 或 Arthas: thread -b  # 一键找阻塞源头

Q7:wait() 和 sleep() 的区别?

wait()sleep()
所属类ObjectThread
锁释放释放锁不释放锁
调用条件必须在 synchronized 中任意位置
唤醒方式notify()/notifyAll()超时或 interrupt()
状态WAITINGTIMED_WAITING

Q8:什么是伪共享(False Sharing)?如何解决?

CPU 缓存以缓存行(Cache Line,通常 64 字节)为单位,多个线程修改不同变量但变量在同一缓存行时,导致缓存行反复失效。

CPU1 更新 X → 使 CPU2 的缓存行失效
CPU2 更新 Y → 使 CPU1 的缓存行失效
即使 X 和 Y 没有任何关系,性能也大幅下降

解决方案

  • JDK8 @Contended 注解(需 -XX:-RestrictContended
  • 手动填充(padding)把变量撑开,占满一个缓存行

Q9:为什么 wait()/notify() 要定义在 Object 中,而不是 Thread 中?

每个对象都可以作为锁,锁的作用范围是任意对象。如果定义在 Thread 中,那每个线程只能有一个等待队列,无法区分不同锁的等待条件。放在 Object 中,每个对象都可以有自己的等待队列。

Q10:notify() 和 notifyAll() 的区别?

  • notify():随机唤醒一个在 WaitSet 中等待的线程
  • notifyAll():唤醒所有等待线程,但它们还要重新竞争锁

建议:始终使用 notifyAll(),除非你非常确定只有一个线程在等待且永远不会增加。因为只用 notify() 可能导致信号丢失(唤醒了错误的线程)。

Q11:线程池中,核心线程会被回收吗?

默认不会。核心线程调用 workQueue.take() 永久阻塞等待。
设置 pool.allowCoreThreadTimeOut(true) 后,核心线程在空闲超过 keepAliveTime 后也会被回收。

Q12:synchronized 锁升级过程?

无锁 → 偏向锁 → 轻量级锁 → 重量级锁。只能升级,不能降级(偏向锁可撤销为无锁)。

  • 偏向锁:同一线程反复获取,Mark Word 存线程 ID
  • 轻量级锁:交替获取无竞争,CAS 自旋
  • 重量级锁:并发竞争,OS Mutex,线程阻塞

Q13:HashMap 和 ConcurrentHashMap 的区别?

维度HashMapConcurrentHashMap
线程安全❌ 线程不安全✅ 线程安全
null 键值✅ 允许 null❌ 不允许 null
并发控制CAS + synchronized
迭代器fail-fast弱一致性(不抛 CME)
JDK8 结构数组+链表+红黑树同,且扩容多线程协同

七、并发工具速查表

工具核心机制一句话用途
AtomicIntegerCAS 自旋无锁原子整数
LongAdder分段累加 Cell[]高并发累加(统计 QPS)
AtomicStampedReferenceCAS + 版本号解决 ABA 问题
CountDownLatchAQS 共享模式等人齐/等任务完
CyclicBarrierLock + Condition大家都到齐,一起出发
SemaphoreAQS 共享模式限流、控制并发数
ExchangerCAS + park/unpark两个线程交换数据
Phaser多阶段同步增强版 CyclicBarrier + CountDownLatch
CompletableFuture异步任务编排回调地狱的终结者
ForkJoinPool工作窃取分治任务并行计算
ConcurrentLinkedQueueCAS + 自旋无界并发队列
LinkedBlockingQueue双锁 putLock + takeLock生产者-消费者队列
SynchronousQueue无缓冲直接传递线程一对一直接交接
DelayQueuePriorityQueue + 时间判定定时任务队列

八、JVM 层面对并发的支持

8.1 锁优化

优化触发条件原理
偏向锁单线程反复获取对象头记录线程 ID,无 CAS
轻量级锁交替获取无竞争栈中 Lock Record + CAS
重量级锁并发竞争OS Mutex
自旋锁锁持有时间短忙等不挂起
自适应自旋JMV 统计根据前次成功率动态调整
锁消除逃逸分析无逃逸直接删除 synchronized
锁粗化连续加解锁合并为一次

8.2 指令重排序与内存屏障

源代码
    │  ① 编译器重排序
    ▼
编译后指令
    │  ② 指令级并行重排序
    ▼
CPU 执行序列
    │  ③ 内存系统重排序
    ▼
最终对其他处理器可见的内存访问顺序

8.3 逃逸分析(Escape Analysis)

JIT 的分析手段,判断对象是否逃逸出方法/线程:

  • 无逃逸 → 栈上分配 → 无需 GC,方法结束自动回收
  • 无逃逸 → 锁消除 → 去掉不必要的同步
  • 无逃逸且不改变字段 → 标量替换 → 拆成基本类型直接放栈上

九、坑点小结(⭐ 面试/生产必知)

序号坑点一句话正确做法
1volatile 不保证原子性volatile int i++; 线程不安全AtomicInteger / synchronized
2wait 必须在 synchronized 内否则抛 IllegalMonitorStateException先拿到锁,再 wait()
3线程池 submit 吞异常get() 永远不知道任务异常get() 或重写 afterExecute
4ThreadLocal 必须 remove线程池复用导致数据污染和内存泄漏finally { tl.remove(); }
5CHM 复合操作非原子get + put 两步,中间可被插入compute/merge/外部加锁
6Executors 创建线程池无界队列或无限线程 → OOM手动 new ThreadPoolExecutor
7DCL 单例不加 volatile指令重排导致半初始化对象private static volatile Singleton instance;
8锁释放顺序要对称外层锁先获取后释放加锁顺序 = 解锁逆序(或一致也可以,但要统一)
9notify 而非 notifyAll可能唤醒错误的等待线程,信号丢失默认用 notifyAll()
10HashMap 并发 putJDK7 死循环,JDK8 数据丢失ConcurrentHashMap
11interrupt 不清除中断标志isInterrupted() 检查后标志仍在interrupted() 检查并清除(静态方法)
12主线程不感知子线程异常子线程未捕获异常,主线程继续走Future.get() / UncaughtExceptionHandler

📌 记忆口诀

  • 线程池用 ThreadPoolExecutor 手动创建,七大参数要记牢
  • volatile 可见有序不原子,DCL 单例必须加
  • synchronized 自动释放会升级,Lock 手动释放高级多
  • CAS 无锁自旋看 ABA,LongAdder 分段高并发
  • CHM JDK7 分段变 JDK8 CAS+锁,get 无锁扩容多线程帮
  • ThreadLocal 用完必须 remove,弱引用 Key 强引用 Value 是祸根
  • AQS 是并发基石,CLH 队列 + volatile state
  • CompletableFuture 编排异步,allOf/anyOf/thenCombine 三板斧
  • 坑点记牢:volatile 不原子 / wait 里放锁 / submit 要 get / ThreadLocal 要 remove / Executors 别用 / CHM 复合操作要同步
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐