Java - 多线程篇

7.1 线程基础

线程与进程

进程 = 资源分配的最小单位;线程 = CPU 调度的最小单位。

维度进程线程
关系进程包含线程线程属于进程
内存不同进程不同内存空间同进程下线程共享内存
切换成本高低

并行 vs 并发

概念说明
并发同一时间应对多件事的能力,多线程轮流使用 CPU
并行同一时间动手做多件事的能力,多核 CPU 同时执行

创建线程的方式

  1. 继承 Thread 类
  2. 实现 Runnable 接口
  3. 实现 Callable 接口
  4. 线程池创建(项目中使用方式)

Runnable vs Callable:

特性RunnableCallable
返回值无有,泛型
异常不能上抛可以上抛
配合—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 修饰的共享变量具备两层语义:

  1. 保证线程间的可见性
  2. 禁止指令重排序

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

维度synchronizedLock
语法关键字,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 在保证安全的同时大幅提升并发性能。

特性HashMapConcurrentHashMap(JDK 8+)
线程安全❌✅
锁粒度无桶级别(Node 头节点)
并发写直接覆盖CAS(空桶)+ synchronized(非空桶)
读操作极快极快(volatile,无锁)
扩容单线程重新哈希多线程并发迁移 + 协助机制
Null 键值支持不支持(避免并发歧义)

7.8 并发编程三大特性

特性说明根本原因
原子性操作不可分割,要么全执行,要么不执行i++ 实际分三步(读→算→写),中间可能被切换
可见性一个线程对共享变量的修改对其他线程可见CPU 多级缓存,线程修改后未及时刷回主内存
有序性代码按编写顺序执行编译器/处理器指令重排序优化

解决方案:volatile(可见性 + 有序性)+ CAS(原子性)+ 锁机制(三者皆可)。