7.1 线程基础
线程与进程
进程 = 资源分配的最小单位;线程 = CPU 调度的最小单位。
| 维度 | 进程 | 线程 |
|---|---|---|
| 关系 | 进程包含线程 | 线程属于进程 |
| 内存 | 不同进程不同内存空间 | 同进程下线程共享内存 |
| 切换成本 | 高 | 低 |
并行 vs 并发
| 概念 | 说明 |
|---|---|
| 并发 | 同一时间应对多件事的能力,多线程轮流使用 CPU |
| 并行 | 同一时间动手做多件事的能力,多核 CPU 同时执行 |
创建线程的方式
- 继承
Thread类 - 实现
Runnable接口 - 实现
Callable接口 - 线程池创建(项目中使用方式)
Runnable vs Callable:
| 特性 | Runnable | Callable |
|---|---|---|
| 返回值 | 无 | 有,泛型 |
| 异常 | 不能上抛 | 可以上抛 |
| 配合 | — | Future / FutureTask |
线程六种状态
public enum State {
NEW, // 新建
RUNNABLE, // 可运行
BLOCKED, // 阻塞(等待锁)
WAITING, // 等待(无限期)
TIMED_WAITING, // 计时等待
TERMINATED; // 终止
}
sleep() vs wait()
| 特性 | sleep() | wait() |
|---|---|---|
| 所属类 | Thread 类 | Object 类 |
| 是否释放锁 | ❌ 不释放 | ✅ 释放 |
| 使用范围 | 任何地方 | 必须在 synchronized 块/方法中 |
| 唤醒方式 | 时间到自动醒 | notify() / notifyAll() / 时间到 |
| 主要用途 | 暂停执行、模拟耗时 | 线程间通信、生产者消费者 |
7.2 synchronized 与锁升级
synchronized 基本概念
synchronized(对象锁):采用互斥方式让同一时刻至多只有一个线程能持有对象锁。
底层原理 — Monitor
- synchronized 底层由 Monitor 实现(JVM 级别,C++ 实现)
- Monitor 内部三个属性:
- owner:关联获得锁的线程(只能关联一个)
- entrylist:关联阻塞状态的线程
- waitset:关联 Waiting 状态的线程
⚠️ Monitor 实现的锁属于重量级锁,涉及用户态/内核态切换、上下文切换,成本高、性能低。
JDK 1.6 锁优化(偏向锁 / 轻量级锁)
三种锁对比:
| 锁类型 | 描述 |
|---|---|
| 重量级锁 | Monitor 实现,涉及用户态/内核态切换,性能低 |
| 轻量级锁 | 线程加锁时间错开时使用。修改对象头锁标志,CAS 保证原子性,性能提升很多 |
| 偏向锁 | 很长一段时间只被一个线程使用。第一次 CAS,之后只需判断 Mark Word 中是否为自己的线程 ID |
7.3 JMM / CAS / volatile
JMM(Java 内存模型)
JMM 定义了共享内存中多线程程序读写操作的行为规范。
- 每个线程拥有独立的工作内存(CPU 缓存)
- 公共数据存储在主内存中
- 线程间通过主内存来同步数据
CAS(Compare And Swap)
CAS 体现乐观锁思想,在无锁情况下保证线程操作共享数据的原子性。
缺点:ABA 问题、自旋消耗 CPU、只能保证单一变量。
乐观锁 vs 悲观锁
| 思想 | 实现 | 理念 |
|---|---|---|
| 乐观锁 | CAS | 不怕别人改,改了我就重试 |
| 悲观锁 | synchronized | 防着别人改,我上了锁你们都别想改 |
volatile
被 volatile 修饰的共享变量具备两层语义:
- 保证线程间的可见性
- 禁止指令重排序
7.4 AQS / ReentrantLock
AQS(Abstract Queued Synchronizer)
AQS = 多线程中的队列同步器。构建锁和同步器的底层框架。
常见实现类:ReentrantLock(阻塞式锁)、Semaphore(信号量)、CountDownLatch(倒计时锁)。
公平锁 vs 非公平锁:
- 新线程和队列中线程共同抢占 → 非公平锁
- 新线程到队列中等待,只让 head 获取锁 → 公平锁
AQS 总结:
- 内部维护一个先进先出的双向队列,存储排队的线程
- 内部属性 state = 资源标识,默认 0(无锁),修改为 1 = 获取资源
ReentrantLock
| 特性 | 说明 |
|---|---|
| 可中断 | lockInterruptibly() |
| 可超时 | tryLock(time, TimeUnit) |
| 公平锁 | 构造器传参控制 |
| 多条件变量 | 支持多个 Condition |
| 可重入 | 与 synchronized 一样 |
synchronized vs Lock
| 维度 | synchronized | Lock |
|---|---|---|
| 语法 | 关键字,JVM 实现 | 接口,JDK 实现 |
| 释放 | 自动释放 | 手动 unlock() |
| 公平锁 | 不支持 | 支持 |
| 可中断 | 不支持 | 支持 |
| 可超时 | 不支持 | 支持 |
| 多条件 | 不支持 | 支持 |
synchronized 胜在简单安全,Lock 胜在灵活强大。
7.5 线程池
核心参数
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数("正式员工")
int maximumPoolSize, // 最大线程数(并发上限)
long keepAliveTime, // 非核心线程空闲存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
执行原理
提交任务
→ 核心线程是否已满?
→ 否:创建核心线程执行
→ 是:阻塞队列是否已满?
→ 否:放入队列等待
→ 是:最大线程是否已满?
→ 否:创建非核心线程执行
→ 是:执行拒绝策略
四种拒绝策略:
| 策略 | 行为 |
|---|---|
AbortPolicy | 丢弃任务 + 抛异常(默认) |
CallerRunsPolicy | 由调用者线程处理该任务 |
DiscardOldestPolicy | 丢弃队列最前面的任务,重新提交 |
DiscardPolicy | 丢弃任务,不抛异常(有风险) |
核心线程数设置
| 任务类型 | 公式 | 说明 |
|---|---|---|
| CPU 密集型 | N + 1 | 减少上下文切换 |
| IO 密集型 | 2N + 1 | 线程等待 IO 时可让出 CPU |
⚠️
Executors创建的FixedThreadPool、SingleThreadPool队列无界 → OOM;CachedThreadPool线程无界 → OOM。生产环境推荐直接new ThreadPoolExecutor(...)。
7.6 ThreadLocal
ThreadLocal:让每个线程拥有该变量的一个独立拷贝,线程间互不影响。
底层结构:每个 Thread 内部维护一个 ThreadLocalMap,key 是 ThreadLocal 对象的弱引用,value 是线程私有的值。
⚠️ 内存泄漏风险:ThreadLocal 的 key 是弱引用,但 value 是强引用。线程不结束,value 无法被 GC。使用后务必调用
remove()。
7.7 ConcurrentHashMap
ConcurrentHashMap 是 Java 并发包中高效且线程安全的哈希表,通过细粒度锁 + CAS 在保证安全的同时大幅提升并发性能。
| 特性 | HashMap | ConcurrentHashMap(JDK 8+) |
|---|---|---|
| 线程安全 | ❌ | ✅ |
| 锁粒度 | 无 | 桶级别(Node 头节点) |
| 并发写 | 直接覆盖 | CAS(空桶)+ synchronized(非空桶) |
| 读操作 | 极快 | 极快(volatile,无锁) |
| 扩容 | 单线程重新哈希 | 多线程并发迁移 + 协助机制 |
| Null 键值 | 支持 | 不支持(避免并发歧义) |
7.8 并发编程三大特性
| 特性 | 说明 | 根本原因 |
|---|---|---|
| 原子性 | 操作不可分割,要么全执行,要么不执行 | i++ 实际分三步(读→算→写),中间可能被切换 |
| 可见性 | 一个线程对共享变量的修改对其他线程可见 | CPU 多级缓存,线程修改后未及时刷回主内存 |
| 有序性 | 代码按编写顺序执行 | 编译器/处理器指令重排序优化 |
解决方案:volatile(可见性 + 有序性)+ CAS(原子性)+ 锁机制(三者皆可)。