并发编程知识体系概览
什么是并发编程?
并发编程(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 做了大幅优化,引入了锁升级机制:
- 偏向锁:只有一个线程访问时,在对象头记录线程 ID,几乎零开销
- 轻量级锁:两个线程交替访问(无竞争),通过 CAS 自旋获取锁
- 重量级锁:激烈竞争时,线程进入阻塞队列等待唤醒
volatile——轻量级的"可见性保证"
volatile 关键字保证两件事:
- 可见性:一个线程修改了 volatile 变量,其他线程立即可见(禁止 CPU 缓存)
- 有序性:禁止指令重排序(通过内存屏障实现)
但 volatile 不保证原子性——volatile int count; count++ 仍然不是线程安全的(读-改-写不是原子操作)。volatile 最经典的应用是双重检测锁(DCL)单例模式。
CAS 与原子类——无锁并发
CAS(Compare And Swap) 是一种无锁的并发原语:"比较当前值是否等于预期值,如果是就更新为新值"。CAS 是 java.util.concurrent 包的基石,AtomicInteger、AtomicLong 等原子类底层都是 CAS。
CAS 的经典问题是 ABA 问题——值从 A 变成 B 再变回 A,CAS 认为"没变过"。解决方案:AtomicStampedReference(加版本号)。
线程池——线程的"资源池"
频繁创建/销毁线程开销很大,线程池通过复用线程来降低开销。ThreadPoolExecutor 有 7 个核心参数:
new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 非核心线程空闲存活时间
unit, // 时间单位
workQueue, // 任务队列
threadFactory, // 线程工厂
handler // 拒绝策略
);
任务提交流程:核心线程未满 → 创建核心线程执行;核心线程已满 → 放入队列;队列已满 → 创建非核心线程;全满 → 执行拒绝策略。
Executors.newFixedThreadPool() 的队列是无界的 LinkedBlockingQueue,可能导致 OOM;Executors.newCachedThreadPool() 最大线程数是 Integer.MAX_VALUE,可能创建大量线程。阿里 Java 规范要求直接使用 ThreadPoolExecutor 手动指定参数。
AQS——并发工具的"基石"
AQS(AbstractQueuedSynchronizer) 是 ReentrantLock、CountDownLatch、Semaphore 等并发工具的底层框架。它维护一个 state 变量和一个 CLH 等待队列——线程通过 CAS 修改 state 来获取/释放锁,获取不到就加入队列等待。
ThreadLocal——线程的"私有存储"
ThreadLocal 为每个线程提供一份独立的变量副本——线程之间互不干扰。常用于存储用户上下文(如登录用户信息)、数据库连接、日期格式化器等。
ThreadLocal 的 Entry 的 key 是弱引用,GC 后 key 变为 null,但 value 仍然强引用存在,导致内存泄漏。使用完毕必须调用 remove()。
学习建议
- 线程基础 → 生命周期、创建方式、中断机制
- synchronized → Monitor、锁升级
- volatile + CAS → JMM、内存可见性、原子操作
- 线程池 → 7 参数、执行流程、拒绝策略
- AQS + Lock → ReentrantLock、读写锁
- 并发工具类 → CountDownLatch、Semaphore、CyclicBarrier
- CompletableFuture → 异步编排