跳到主要内容

并发编程知识体系概览

什么是并发编程?

并发编程(Concurrent Programming) 是指让程序同时处理多个任务的编程方式。想象一个餐厅:

  • 串行:一个服务员接单 → 做菜 → 上菜 → 接下一桌(效率极低)
  • 并发:一个服务员在多桌之间切换(一桌等菜时去另一桌接单)
  • 并行:多个服务员同时服务不同的桌(真正的"同时")

在 Java 中,线程(Thread) 是并发的基本单位。一个 Java 进程可以创建多个线程,多个线程共享进程的内存空间(堆),同时又各自拥有独立的栈。


为什么并发编程在 Java 面试中如此重要?

原因说明
面试必考synchronized、volatile、线程池、CAS 几乎是 Java 面试的"标配"
服务端本质Java 后端处理的每个 HTTP 请求都是一个线程,天然就是多线程环境
Bug 温床线程安全问题(数据不一致、死锁、竞态条件)是线上事故的常见原因
性能优化合理使用线程池和并发工具可以充分利用多核 CPU

核心知识点

线程基础——并发的"起点"

Java 创建线程的方式:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口(有返回值)、线程池提交任务。

线程有 6 种状态

synchronized——Java 的内置锁

synchronized 是 Java 最基本的同步原语,可以修饰方法或代码块。底层依赖对象头的 Monitor(监视器锁)

JDK 6 对 synchronized 做了大幅优化,引入了锁升级机制:

无锁偏向锁轻量级锁重量级锁\text{无锁} \rightarrow \text{偏向锁} \rightarrow \text{轻量级锁} \rightarrow \text{重量级锁}
  • 偏向锁:只有一个线程访问时,在对象头记录线程 ID,几乎零开销
  • 轻量级锁:两个线程交替访问(无竞争),通过 CAS 自旋获取锁
  • 重量级锁:激烈竞争时,线程进入阻塞队列等待唤醒

volatile——轻量级的"可见性保证"

volatile 关键字保证两件事:

  1. 可见性:一个线程修改了 volatile 变量,其他线程立即可见(禁止 CPU 缓存)
  2. 有序性:禁止指令重排序(通过内存屏障实现)

但 volatile 不保证原子性——volatile int count; count++ 仍然不是线程安全的(读-改-写不是原子操作)。volatile 最经典的应用是双重检测锁(DCL)单例模式

CAS 与原子类——无锁并发

CAS(Compare And Swap) 是一种无锁的并发原语:"比较当前值是否等于预期值,如果是就更新为新值"。CAS 是 java.util.concurrent 包的基石,AtomicIntegerAtomicLong 等原子类底层都是 CAS。

CAS 的经典问题是 ABA 问题——值从 A 变成 B 再变回 A,CAS 认为"没变过"。解决方案:AtomicStampedReference(加版本号)。

线程池——线程的"资源池"

频繁创建/销毁线程开销很大,线程池通过复用线程来降低开销。ThreadPoolExecutor7 个核心参数

new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 非核心线程空闲存活时间
unit, // 时间单位
workQueue, // 任务队列
threadFactory, // 线程工厂
handler // 拒绝策略
);

任务提交流程:核心线程未满 → 创建核心线程执行;核心线程已满 → 放入队列;队列已满 → 创建非核心线程;全满 → 执行拒绝策略。

为什么不推荐使用 Executors 创建线程池?

Executors.newFixedThreadPool() 的队列是无界的 LinkedBlockingQueue,可能导致 OOM;Executors.newCachedThreadPool() 最大线程数是 Integer.MAX_VALUE,可能创建大量线程。阿里 Java 规范要求直接使用 ThreadPoolExecutor 手动指定参数。

AQS——并发工具的"基石"

AQS(AbstractQueuedSynchronizer)ReentrantLockCountDownLatchSemaphore 等并发工具的底层框架。它维护一个 state 变量和一个 CLH 等待队列——线程通过 CAS 修改 state 来获取/释放锁,获取不到就加入队列等待。

ThreadLocal——线程的"私有存储"

ThreadLocal 为每个线程提供一份独立的变量副本——线程之间互不干扰。常用于存储用户上下文(如登录用户信息)、数据库连接、日期格式化器等。

内存泄漏风险

ThreadLocal 的 Entry 的 key 是弱引用,GC 后 key 变为 null,但 value 仍然强引用存在,导致内存泄漏。使用完毕必须调用 remove()


学习建议

推荐学习路径
  1. 线程基础 → 生命周期、创建方式、中断机制
  2. synchronized → Monitor、锁升级
  3. volatile + CAS → JMM、内存可见性、原子操作
  4. 线程池 → 7 参数、执行流程、拒绝策略
  5. AQS + Lock → ReentrantLock、读写锁
  6. 并发工具类 → CountDownLatch、Semaphore、CyclicBarrier
  7. CompletableFuture → 异步编排

相关链接