java并发编程前置概念
并发编程学习路线
1. 前置概念
- 串行、并发、并行
- 进程与线程区别
- 单核 / 多核 CPU 与线程关系
- 上下文切换、时间片
2. Java 线程基础
- Thread、Runnable、Callable
- 线程 6 种生命周期
- start () 与 run () 区别
- sleep、yield、join、interrupt
- 守护线程与用户线程
3. Java 内存模型 JMM
- 原子性、可见性、有序性
- volatile 原理与使用场景
- 指令重排与内存屏障
- 堆、栈、方法区与线程关系
4. 线程安全与 synchronized
- 竞态条件与临界区
- synchronized 使用方式
- 锁特性:可重入、互斥、内存语义
- 锁升级流程:偏向锁→轻量级锁→重量级锁
- synchronized 底层 monitor 机制
5. CAS 与无锁编程
- CAS 原理:Compare And Swap
- CPU 原子指令与原子性保证
- CAS 存在的问题:ABA、循环开销、只能保证单个变量
- Atomic 原子类:AtomicInteger、AtomicReference 等
- ABA 解决方案:AtomicStampedReference 版本号机制
6. LockSupport 与线程阻塞唤醒
- park/unpark 工作机制
- 与 wait/notify 的区别
- AQS 底层依赖的阻塞工具
7. AQS 核心原理
- AQS 结构:state 同步状态 + CLH 等待队列
- 独占模式与共享模式
- 入队、出队、抢锁逻辑
- 可中断、超时等待机制
8. ReentrantLock 与显式锁
- ReentrantLock 使用与 API
- 公平锁与非公平锁实现
- 与 synchronized 的对比
- tryLock、锁中断、超时获取
9. 读写锁 StampedLock
- ReentrantReadWriteLock 读写分离
- 读锁共享、写锁独占
- StampedLock 乐观读机制
- 适用场景与性能对比
10. 线程间通信
- wait、notify、notifyAll
- Condition 精准唤醒
- 生产者 - 消费者模型实现
11. 并发安全问题
- 死锁的四个必要条件
- 死锁避免与检测
- 活锁、饥饿、优先级反转
12. AQS 同步工具类
- CountDownLatch 计数器
- CyclicBarrier 循环屏障
- Semaphore 信号量限流
- Exchanger 数据交换
13. ThreadLocal
- ThreadLocal 实现原理
- ThreadLocalMap 结构
- 内存泄漏原因与避免
- 使用场景与注意事项
14. 阻塞队列 BlockingQueue
- ArrayBlockingQueue、LinkedBlockingQueue
- SynchronousQueue、PriorityBlockingQueue
- 阻塞队列的线程安全实现
- 与线程池配合使用
15. 线程池 ThreadPoolExecutor
- 核心参数:corePoolSize、maximumPoolSize 等
- 线程池执行流程
- 拒绝策略
- 线程池状态转换
- 为什么禁止使用 Executors
16. 并发集合
- ConcurrentHashMap 1.7 与 1.8 原理
- CAS + synchronized 结合实现
- CopyOnWriteArrayList/CopyOnWriteArraySet
- 写时复制机制与适用场景
17. CompletableFuture 异步编程
- 异步任务创建与执行
- thenApply、thenAccept、thenCompose
- 多任务组合:allOf、anyOf
- 异常处理与线程池指定
18. 高并发问题与排查
- 锁竞争、上下文切换开销
- 伪共享、GC 压力
- jstack、jconsole、Arthas 排查死锁、线程阻塞
- 高并发性能优化思路
并发编程目的:
1. 提高 CPU 利用率,不浪费算力
单核 CPU 遇到 IO(读写文件、网络、数据库)会 idle 空转。
多线程可以让:
一个线程在等 IO
另一个线程继续计算
CPU 一直干活,不闲着。
2. 提高吞吐量,处理更多请求
服务器同一时间能处理更多用户请求:
单线程:1 个接 1 个处理
多线程:同时处理 N 个
单位时间处理更多任务,支撑更高并发。
3. 提高响应速度,让界面 / 服务不卡顿
GUI/APP:后台下载不阻塞界面操作
后端:异步发送短信、日志、通知
主线程快速返回,用户无等待感。
4. 任务模块化,逻辑更清晰
把不同职责的任务分开:
一个线程负责接收请求
一个线程负责计算
一个线程负责写库
职责分离,便于设计与维护。
一句话总结
并发编程的目的,是在有限硬件下,提高 CPU 利用率、提升吞吐量、降低响应延迟,同时让程序结构更合理。
串行、并发和并行的区别
1. 串行(Serial)
- 一个人,一件一件做事
- 任务排队执行,必须等上一个做完,才能做下一个
- 同一时间只做一件事
例子:
你先烧水 → 水开了再洗菜 → 洗完菜再炒菜
一件做完再做下一件。
2. 并发(Concurrent)
- 一个人,快速切换做多件事
- 看起来像同时做,其实是来回切换
- 同一时间本质还是只做一件事,只是切换很快
例子:
你烧水时,等水开的间隙去洗菜,水快开了又跑回去看水。
人还是一个,只是任务穿插执行。
3. 并行(Parallel)
- 多个人,同时做多件事
- 真正的同时执行
- 需要多核 CPU / 多线程硬件支持
例子:
你烧水,你老婆洗菜,你儿子炒菜
三个人同时干,互不干扰。
一句话总结
串行:排队做,一件一件来
并发:一个人快速切换,假装同时做
并行:多个人真・同时做
延伸
- 单核 CPU 只能:串行 + 并发
- 多核 CPU 才能:并行
- 并发解决 “等待浪费”,并行解决 “速度慢”
并发编程带来的问题
1. 线程安全问题(最常见)
多个线程同时读写同一个共享变量,结果不可预期。
- 竞态条件(Race Condition)
- 读 - 改 - 写 三步不是原子操作,结果错乱
典型例子:
1 | |
两个线程同时执行,结果可能少加 1。
2. 死锁(Deadlock)
两个或多个线程互相持有对方需要的锁,谁都不放,谁都不动。
满足四个条件就会死锁:
- 互斥
- 持有并等待
- 不可抢占
- 循环等待
3. 活锁(Livelock)
线程一直在 “动”,但没有任何进展。
比如互相谦让锁,你让我、我让你,永远拿不到。
4. 饥饿(Starvation)
某些线程一直抢不到资源,永远执行不了。
比如优先级低的线程一直被高优先级线程抢占。
5. 可见性问题(Visibility)
一个线程修改了变量,另一个线程看不到最新值。
因为 CPU 缓存、指令重排导致。
6. 有序性问题(Ordering)
编译器 / CPU 为了优化,打乱代码执行顺序,
单线程没事,多线程直接逻辑错乱。
7. 上下文切换开销
线程频繁切换,内核要保存 / 恢复现场。
切换太多,速度反而更慢。
CPU 被切换本身吃掉,内存被线程栈吃掉,缓存被频繁切换打废,整体性能雪崩。
8. 难以调试、难以复现
并发 bug 有三大特点:
- 不一定每次都出现
- 出现了也很难定位
- 测试环境没问题,线上突然炸
被称为 “Heisenbug”(测的时候就消失)。
9. 内存泄漏 & 资源释放问题
线程没正常退出、锁没释放、线程池任务堆积,
容易导致 OOM 或资源耗尽。
进程与线程的区别
1. 根本定义
- 进程:资源分配的基本单位,是运行中的程序。
- 线程:CPU 调度和执行的基本单位,是进程内的执行分支。
一句话:进程是容器,线程是干活的人。
2. 资源共享
进程:有独立的内存空间、文件句柄、地址空间,互不共享。
线程:共享所属进程的内存、数据、文件,只独有栈、程序计数器、寄存器。
CPU 调度的最小单位是线程,资源分配的最小单位是进程。
3. 开销
- 进程:创建、销毁、切换开销大。
- 线程:轻量级,切换快、开销小。
4. 通信方式
- 进程:通信麻烦,需要管道、消息队列、共享内存、Socket 等。
- 线程:直接读写共享变量即可通信(但要注意线程安全)。
5. 健壮性 / 稳定性
- 进程:一个进程崩了,不影响其他进程。
- 线程:一个线程崩了(比如段错误),整个进程跟着挂。