并发

并发
pfa目录
- 一、什么是并发?
- 二、为什么需要并发?
- 三、并发三大特性(JMM)
- 四、as-if-serial 与 happens-before 规则
- 五、线程同步工具类
- 六、异步编程:CompletableFuture
- 七、并发面试高频问题
- 引用
并发
一、什么是并发?
一句话定义
并发是同一时间段内,系统中有多个独立任务交替执行的能力。
并发 vs 并行
| 维度 | 并发(Concurrent) | 并行(Parallel) |
|---|---|---|
| 时间维度 | 同一时间段 | 同一时刻 |
| CPU核心数 | 单核也能实现 | 需要多核硬件支持 |
| 执行方式 | 线程交替切换执行 | 多个线程真的同时执行 |
实现层级
- 多进程并发:操作系统中同时运行多个应用程序
- 多线程并发:同一进程中运行多个任务
- 优点:共享进程内存,上下文切换代价小
- 协程:单线程内多任务协作切换
二、为什么需要并发?
带来的收益
| 收益 | 说明 |
|---|---|
| 提高吞吐量 | 单位时间内处理更多请求 |
| 提高响应性 | 不会因为某一任务阻塞导致整个程序卡住 |
| 提高资源利用率 | 避免CPU在等待IO时空闲 |
带来的问题
| 问题 | 表现 | 解决方案思路 |
|---|---|---|
| 上下文切换代价大 | 线程切换消耗CPU | 无锁编程、CAS、协程、减少线程数 |
| 资源限制 | 带宽、连接数等瓶颈 | 增加资源(集群)、限制并发度 |
| 数据不一致 | 多线程同时修改共享数据 | synchronized、Lock、Atomic |
| 死锁 | 线程互相等待对方锁 | 正确使用锁顺序、超时机制 |
| 调试难度大 | 问题难复现,Bug不稳定 | 日志、压测、线程Dump分析 |
三、并发三大特性(JMM)
Java内存模型(JMM)定义了多线程环境下如何保证数据一致性。
可见性
问题:一个线程修改了主内存变量,另一个线程看不到最新值
1 | 主内存 |
保证手段:
volatile:修改后立即刷回主内存,读时直接读主内存synchronized/Lock:解锁前刷回主内存,加锁时清空工作内存重新读
原子性
问题:一个操作执行到一半被中断,导致数据不对
常见反例:
1 | count++; // 不是原子操作:1. 读count 2. 加1 3. 写回 |
保证手段:
synchronized/Lock:保证同一时刻只有一个线程执行Atomic*类:CAS + volatile 实现无锁原子操作
有序性
问题:编译器/CPU为了优化会重排指令,导致执行顺序和代码写的不一样
经典例子:
1 | // 线程 A |
重排后线程B可能先看到flag=true但a还没赋值。
保证手段:
synchronized/Lock:加锁区域内保证单线程有序执行volatile:禁止指令重排序(通过内存屏障实现)
四、as-if-serial 与 happens-before 规则
as-if-serial
编译器和处理器不会对存在数据依赖关系的操作做重排序。
核心:单线程下不管怎么重排,执行结果不能变。
1 | int a = 1; // 1 |
1 和 2 可以重排,但 3 必须在 1 和 2 之后。
happens-before
如果操作 A happens-before 操作 B,那么 A 的执行结果对 B 可见,且 A 的执行顺序排在 B 之前。
不是强制顺序:如果重排后结果一致,也可以重排。
happens-before 的 8 条规则
| 规则名 | 说明 |
|---|---|
| 程序次序规则 | 一个线程内,前面的操作 happens-before 后面的操作 |
| 监视器锁规则 | 解锁 happens-before 加锁 |
| volatile变量规则 | 对 volatile 的写 happens-before 后续对它的读 |
| 线程start规则 | Thread.start() happens-before 该线程的任何操作 |
| 线程join规则 | 线程A join 线程B,B的所有操作 happens-before A从join返回 |
| 线程中断规则 | interrupt() happens-before 被中断线程检测到中断 |
| 对象终结规则 | 对象初始化完成 happens-before finalizer() 开始 |
| 传递性 | A happens-before B,B happens-before C → A happens-before C |
五、线程同步工具类
synchronized + wait/notify
| 特性 | 说明 |
|---|---|
| 锁对象 | 可以是实例对象、Class对象、this |
| 可重入 | 同一个线程可以多次获取同一把锁 |
| 自动释放 | 方法/代码块执行完或抛异常自动释放锁 |
| wait/notify | 必须在 synchronized 块内调用,释放锁等待 |
ReentrantLock + Condition
| 特性 | 说明 |
|---|---|
| 和 synchronized 对比 | 显式加锁、解锁,需要在 finally 里释放 |
| 可重入 | 支持 |
| 公平锁 | 可选公平锁(先到先得),默认非公平 |
| Condition | 支持多个条件队列,替代 wait/notify |
1 | ReentrantLock lock = new ReentrantLock(); |
ReadWriteLock / StampedLock
| 特性 | 说明 |
|---|---|
| ReadWriteLock | 读锁共享、写锁独占;适合读多写少 |
| StampedLock | 1.8引入,性能更好;乐观读、读写锁模式切换 |
Semaphore
作用:控制同时访问某个资源的线程数量(令牌数)
1 | Semaphore semaphore = new Semaphore(3); // 3个许可 |
CountDownLatch / CyclicBarrier / Phaser
| 工具类 | 作用 | 可复用 |
|---|---|---|
| CountDownLatch | 等 N 个线程都完成,主线程再继续 | ❌ 只能用一次 |
| CyclicBarrier | N 个线程互相等待,到齐后一起继续 | ✅ 可循环用 |
| Phaser | 更灵活的阶段同步器,支持动态增减参与者 | ✅ |
Exchanger
作用:两个线程在同步点交换数据
六、异步编程:CompletableFuture
| 特性 | 说明 |
|---|---|
| 1. 异步执行 | supplyAsync() / runAsync() |
| 2. 链式回调 | thenApply() / thenAccept() / thenRun() |
| 3. 异常处理 | exceptionally() / handle() |
| 4. 任务组合 | thenCompose() / thenCombine() / allOf() / anyOf() |
| 5. 线程池支持 | 可传自定义 Executor |
七、并发面试高频问题
1. volatile 关键字作用?原理?
- 作用:保证可见性、禁止指令重排序
- 原理:通过内存屏障(LoadLoad/StoreStore/LoadStore/StoreLoad)实现
2. CAS 是什么?有什么问题?
- CAS:Compare And Swap,无锁原子操作
- 核心:期望值、内存值、更新值
- 问题:ABA问题、循环时间长CPU开销、只能保证一个变量
3. synchronized 锁升级过程?
- 无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- 1.6后默认开启偏向锁
4. synchronized 和 ReentrantLock 区别?
- 显式 vs 隐式、公平锁可选、Condition 支持、可中断
5. 线程池参数有哪些?拒绝策略?
- 核心线程数、最大线程数、空闲时间、阻塞队列、线程工厂、拒绝策略
- 4种拒绝策略:Abort、CallerRuns、DiscardOldest、Discard
评论
匿名评论隐私政策






