【JUC】核心知识点归纳
JUC 核心知识点归纳
JUC(java.util.concurrent)是 Java 处理并发编程的核心包,涵盖线程创建、线程安全、锁机制、并发集合等关键内容,是面试和开发中的重点。本文基于核心知识点整理,旨在帮助快速回顾、巩固核心内容。
一、线程基础:创建方式与状态流转
1. 线程的创建方式
Java 中创建线程的 4 种核心方式,适用于不同场景:
- 继承 Thread 类:重写
run()
方法,直接调用start()
启动线程(单继承限制,灵活性低)。 - 实现 Runnable 接口:实现
run()
方法,通过new Thread(Runnable)
启动(无继承限制,推荐)。 - 实现 Callable 接口 + FutureTask:实现
call()
方法(支持返回值、可抛出异常),通过 FutureTask 封装后传入 Thread 启动,适用于需要任务结果的场景。 - 通过线程池创建:使用
Executors
或ThreadPoolExecutor
创建线程池,从池中复用线程(推荐生产环境,降低线程创建 / 销毁开销)。
2. 线程的 6 种状态及流转
Java 中 Thread 类的 State
枚举定义了 6 种线程状态,流转逻辑清晰:
- 新建状态(NEW):创建线程对象(如
new Thread()
),未调用start()
。 - 就绪状态(Runnable):调用
start()
后,线程等待 CPU 执行权(抢到 CPU 后实际运行,但状态仍为 Runnable)。 - 阻塞状态(Blocked):运行中线程未获取到锁对象(如进入 synchronized 块但锁被占用)。
- 等待状态(Waiting):运行中线程调用
wait()
、join()
等方法,需被其他线程唤醒。 - 计时等待状态(Timed_Waiting):运行中线程调用
sleep(long)
、wait(long)
等带超时时间的方法。 - 结束状态(Terminated):
run()
方法执行完毕或线程异常终止。
核心流转路径:NEW → Runnable → (Blocked/Waiting/Timed_Waiting)→ Runnable → Terminated
二、线程安全:核心定义与本质
1. 线程安全的定义
多线程并发访问共享资源(变量、对象、文件等)时,程序执行结果始终符合预期,无数据错乱、逻辑错误(如并发修改导致的数值异常)。
2. 线程安全的本质:3 大特性
线程安全问题的根源是共享资源的并发访问破坏了以下 3 个特性:
- 原子性:操作不可分割(如
i++
实际是i = i + 1
,分读取、计算、赋值 3 步,并发时易被打断)。 - 可见性:线程对共享变量的修改,其他线程能立即感知(CPU 缓存导致修改未及时同步到主存)。
- 有序性:代码执行顺序与编写顺序一致(编译器、CPU 为优化性能会重排序,并发时可能导致逻辑错误)。
三、线程池:核心优势与设计初衷
1. 为什么使用线程池?
线程的创建和销毁是高开销操作(需分配内存、切换内核态),频繁创建 / 销毁会消耗大量系统资源。线程池通过 “预先创建线程 + 复用线程” 解决该问题。
2. 线程池的核心好处
- 资源复用:避免频繁创建 / 销毁线程,降低开销。
- 资源管理:控制最大并发线程数,防止线程过多导致 CPU 过载、内存溢出。
- 提高响应速度:线程已预先创建,任务提交后可立即执行,无需等待线程创建。
- 便于监控与调优:支持统计任务执行情况、线程活跃度等。
四、ThreadLocal:线程隔离的核心工具
1. 什么是 ThreadLocal?
提供线程级别的数据存储机制,每个线程拥有独立的 ThreadLocal 变量副本,线程间互不干扰,实现线程隔离。
2. 核心作用与使用场景
(1)两大核心作用
- 线程内数据传递:避免多层方法通过参数传递数据(如请求链路中的用户信息)。
- 解决并发问题:为非线程安全的变量(如 SimpleDateFormat)创建线程独立实例,避免竞争。
(2)典型使用场景
- 用户身份信息存储:拦截器鉴权后,将用户 ID、权限存入 ThreadLocal,后续链路直接获取。
- 非线程安全变量隔离:如 SimpleDateFormat,通过 ThreadLocal 为每个线程创建独立实例。
- 日志 / 链路追踪上下文:存储日志中的用户 ID、分布式追踪的 traceId,方便调试。
- 数据库 Session 管理:MyBatis、Hibernate 用 ThreadLocal 存储会话,确保线程独立。
3. 实现原理
ThreadLocal 的线程隔离核心依赖 Thread
类中的 ThreadLocalMap
成员变量:
- ThreadLocalMap:本质是 ThreadLocal 内部的哈希表,维护一个
Entry
数组。 - Entry 对象:key 是 ThreadLocal 实例(弱引用),value 是存储的数据(强引用),通过 ThreadLocal 的
threadLocalHashCode
计算数组索引。 - 数据存取逻辑:
- 存数据:
Thread.currentThread().getThreadLocalMap().put(this, value)
(this 即当前 ThreadLocal 实例)。 - 取数据:
Thread.currentThread().getThreadLocalMap().get(this)
。
- 存数据:
4. 内存泄漏风险与解决方案
(1)为什么会内存泄漏?
- 线程池中的核心线程会被复用,线程对 ThreadLocalMap 是强引用,导致 ThreadLocalMap 无法被 GC。
- Entry 的 key(ThreadLocal)是弱引用,GC 时会被回收,但 value 是强引用,若未主动删除,会导致 value 无法回收,积累后内存泄漏。
(2)解决方案
使用完 ThreadLocal 后,必须在 finally
代码块中调用 remove()
方法,主动删除 value:
try {threadLocal.set(value);// 业务逻辑
} finally {threadLocal.remove(); // 关键:避免内存泄漏
}
五、锁机制:synchronized 与 ReentrantLock
1. synchronized:JVM 内置锁
(1)定义与原理
- 语言层面的关键字,基于 JVM 内置的 Monitor 监视器锁 实现。
- 字节码层面:通过
monitorenter
(进入临界区)和monitorexit
(退出临界区)指令控制锁。 - 锁状态存储:通过对象头的
Mark Word
记录锁状态(偏向锁、轻量级锁、重量级锁)。
(2)核心流程
- 加锁:执行
monitorenter
时,检查 Monitor 的 Owner 字段,若为空则当前线程占用锁;否则线程进入EntryList
阻塞。 - 解锁:执行
monitorexit
时,释放锁,唤醒 EntryList 中的线程竞争锁。 - 特性:天生支持可重入性(同一线程可多次加锁)和内存可见性(解锁时同步变量到主存)。
(3)锁升级过程(JDK 1.6 优化)
为适应不同竞争强度,synchronized 会自动升级锁状态,降低开销:
- 无锁:初始状态,无线程竞争。
- 偏向锁:单线程多次加锁时,通过 CAS 把线程 ID 写入 Mark Word,后续线程直接对比 ID 即可进入(无 CAS 开销)。
- 轻量级锁:有轻微竞争(如两个线程竞争),撤销偏向锁后,线程通过 CAS 自旋(用户态)竞争锁(避免内核态阻塞)。
- 重量级锁:竞争激烈(自旋超时或线程过多),锁膨胀为 Monitor 锁,未抢到锁的线程进入内核态阻塞(避免 CPU 空转)。
2. ReentrantLock:并发包可重入锁
(1)核心特性
- 实现
Lock
接口,是可重入锁,支持公平锁 / 非公平锁、超时获取锁、中断锁等高级特性(比 synchronized 灵活)。 - 默认是非公平锁,可通过构造函数
new ReentrantLock(true)
指定为公平锁。
(2)底层实现:AQS 框架
ReentrantLock 的核心依赖 AQS(AbstractQueuedSynchronizer) 框架,AQS 由两部分组成:
- state 变量:表示锁的状态(独占模式):
- state = 0:无锁状态。
- state = 1:已被加锁。
- state > 1:可重入(同一线程多次加锁)。
- FIFO 双向等待队列:未抢到锁的线程被封装为 Node 节点,加入队列,锁释放时唤醒队首节点。
(3)公平锁与非公平锁的实现
- 公平锁:线程竞争锁时,先检查等待队列是否有线程排队,若有则加入队尾,按顺序竞争。
- 非公平锁:线程竞争锁时,先尝试 CAS 抢占锁,抢占失败再加入队列(允许 “插队”,性能更高,默认选择)。
3. synchronized 与 ReentrantLock 对比
特性 | synchronized | ReentrantLock |
---|---|---|
锁实现 | JVM 内置 Monitor 锁 | AQS 框架(Java 代码实现) |
公平性 | 非公平锁(无法指定) | 支持公平 / 非公平(默认非公平) |
高级特性 | 无(不可中断、无超时) | 可中断、超时获取、尝试锁 |
可重入性 | 支持 | 支持 |
锁升级 | 自动升级(无锁→偏向→轻量→重量) | 无自动升级,需手动控制 |
性能 | 低竞争时与 ReentrantLock 接近 | 高竞争时性能更稳定 |
六、并发集合:线程安全的容器
1. 线程安全集合分类
(1)早期线程安全集合(不推荐)
Vector
:ArrayList 的线程安全版本,方法加 synchronized(全局锁,性能低)。Hashtable
:HashMap 的线程安全版本,方法加 synchronized(全局锁,性能低)。
(2)推荐的线程安全集合(java.util.concurrent 包)
ConcurrentHashMap
:高性能并发哈希表(重点)。CopyOnWriteArrayList
:读多写少场景的线程安全 List。ConcurrentSkipListMap
:有序并发 Map(基于跳表实现)。- 包装类:
Collections.synchronizedList(List)
、Collections.synchronizedMap(Map)
(对普通集合加锁包装,性能一般)。
2. 重点集合原理
(1)CopyOnWriteArrayList:写时复制
- 实现原理:
- 内部基于数组存储,写操作(add/remove)时复制新数组,在新数组上操作。
- 写操作加锁(避免并发写入覆盖),读操作无锁(读原数组)。
- 写操作完成后,原数组引用指向新数组。
- 优缺点:
- 优点:读性能极高,无锁竞争,适合读多写少场景。
- 缺点:内存占用高(复制数组),读不到实时数据(最终一致性)。
(2)ConcurrentHashMap:高效并发哈希表
核心优化:锁粒度细化,避免全局锁,Java 7 和 8 实现不同:
- Java 7:分段锁(Segment),将数组分为多个 Segment,每个 Segment 是独立的 ReentrantLock,锁粒度是 Segment。
- Java 8+:取消 Segment,采用
CAS + synchronized
:- CAS 用于初始化数组、头节点(短耗时操作)。
- synchronized 锁住链表 / 红黑树的头节点(锁粒度是单个桶)。
- 优势:并发度更高,冲突概率更低,性能优于 Hashtable。
3. 常见问题
(1)ArrayList 线程安全吗?如何解决?
- 不安全:高并发 add 时,会出现元素为 null、索引越界、size 与实际元素数不符。
- 解决方案:
- 用
Collections.synchronizedList(new ArrayList<>())
包装。 - 替换为
CopyOnWriteArrayList
。
- 用
六、CAS 与乐观锁 / 悲观锁
1. CAS:无锁并发的核心
(1)定义与原理
CAS(Compare And Swap):比较并交换,是乐观锁的核心实现,无锁机制,通过硬件指令保证原子性。
- 核心逻辑:修改共享变量前,先比较 “预期旧值” 与 “主存当前值”:
- 若一致:说明未被其他线程修改,将新值写入主存。
- 若不一致:说明被修改,自旋重试(重新读取旧值,再次尝试)。
- 流程:主存变量 V → 线程工作内存拷贝 V 为 A → 计算新值 B → CAS 比较 V 与 A,一致则 V=B,否则重试。
(2)应用场景与优缺点
- 应用:AQS 框架、AtomicInteger 等原子类(如
atomicInteger.incrementAndGet()
)。 - 优点:无锁,避免线程阻塞切换开销,并发性能高。
- 缺点:自旋会占用 CPU(高竞争场景下自旋频繁,CPU 负载高)。
2. 乐观锁与悲观锁
类型 | 核心思想 | 实现方式 | 适用场景 | 并发性能 |
---|---|---|---|---|
乐观锁 | 认为并发冲突概率低,提交时检查 | CAS、版本号法 | 读多写少、冲突少(如查询) | 高 |
悲观锁 | 认为并发冲突概率高,操作时加锁 | synchronized、ReentrantLock | 写多读少、冲突多(如支付) | 低 |
七、核心知识点总结
- 线程创建:4 种方式,线程池优先;线程状态:6 种,核心流转路径需牢记。
- 线程安全本质:原子性、可见性、有序性,需通过锁或无锁机制保证。
- ThreadLocal:线程隔离,核心是 ThreadLocalMap,必须手动 remove 避免内存泄漏。
- 锁机制:synchronized 是 JVM 内置锁(自动升级),ReentrantLock 是 API 锁(灵活高级)。
- 并发集合:CopyOnWriteArrayList 适合读多写少,ConcurrentHashMap 是高效并发哈希表。
- CAS:无锁核心,乐观锁实现,适用于短耗时操作;悲观锁适用于高冲突场景。
以上是 JUC 核心知识点的浓缩,复习时可结合代码案例强化理解,重点关注线程安全、锁机制、内存泄漏等高频考点。