前置理论
可重入锁
核心定义与特性
允许同一线程多次获取同一把锁而不会死锁
重入计数器:每次获取锁时计数器递增,释放时递减,归零时锁完全释放
线程标识绑定:锁内部记录当前持有锁的线程,确保只有持有者能重入
避免递归死锁:线程在递归或嵌套方法中调用同一锁保护的代码时无需重复等待
与不可重入锁的对比
不可重入锁:同一线程重复获取锁时会阻塞,导致死锁(如自旋锁在递归中无限等待)
可重入锁优势:通过计数器机制安全重复进入同步区域,提升代码灵活性
Java 中的实现与应用
ReentrantLock
显式锁,支持公平性选择(公平锁按请求顺序分配,非公平锁允许插队)
支持条件变量(
Condition
)实现精准线程唤醒(如生产者-消费者模型的队列分离)
synchronized
关键字隐式可重入锁,功能有限(不支持中断或超时)
典型应用场景
递归算法:递归函数多次获取同一锁保护共享资源
同步嵌套调用:类中多个方法互斥访问共享资源且存在调用链
复杂线程协作:通过
Condition
实现多等待队列(如任务调度优先级控制)
注意事项
锁释放匹配:每次获取锁必须对应释放,否则计数器无法归零导致死锁
性能权衡:公平锁减少线程饥饿但增加切换开销,非公平锁反之
避免过度嵌套:深层嵌套增加调试复杂度(需追踪计数器状态)
核心策略
多线程安全保障的核心策略
同步机制
互斥锁
synchronized
:自动管理锁,适用于简单临界区(如计数器操作)ReentrantLock
:支持可中断锁、公平锁及条件变量,适合复杂同步需求(如超时控制)
读写锁
ReentrantReadWriteLock
:读共享、写独占,提升读多写少场景性能(如缓存系统)
无锁编程
原子类
AtomicInteger
/AtomicReference
:基于 CAS 实现无锁线程安全操作(如高并发计数)
可见性控制
volatile
:保证变量修改的可见性,适用于简单状态标志(需结合锁保证原子性)
线程隔离与不可变设计
线程局部存储
ThreadLocal
:为每个线程提供独立变量副本(如会话管理),需调用remove()
防泄漏
不可变对象
对象状态不可修改(如
String
、不可变集合),天然线程安全
线程安全数据结构
并发集合
ConcurrentHashMap
:分段锁优化高并发读写,性能优于HashTable
CopyOnWriteArrayList
:写时复制机制,适合读多写少场景(如事件监听器列表)
阻塞队列
BlockingQueue
:自动阻塞管理(如LinkedBlockingQueue
),简化生产者-消费者模型
并发工具类
任务协调工具
CountDownLatch
:主线程等待多个子任务完成(如服务初始化)CyclicBarrier
:多线程同步至共同阶段后继续执行(如分批次数据处理)
资源控制工具
Semaphore
:控制资源并发访问量(如数据库连接池限流)
执行框架类
线程池管理
ThreadPoolExecutor
:灵活配置线程池参数(核心线程数、队列策略等)
异步任务管理
FutureTask
:封装异步计算结果,支持阻塞获取结果(如并行计算任务)
锁与条件类
高级锁机制
StampedLock
:乐观读锁,减少读写锁竞争(读多写少场景优化)
条件变量
Condition
:与ReentrantLock
配合,实现细粒度等待/唤醒(如生产者-消费者模型)
设计原则与优化
锁粒度控制
仅同步必要代码块(如共享变量操作),减少锁竞争时间
死锁预防
统一锁顺序、设置超时(
tryLock
)、使用检测工具(如jstack
)
性能优化策略
读多写少场景优先用无锁或乐观锁,高并发场景结合线程池管理
公平锁 & 非公平锁
公平锁
获取顺序:严格按线程请求顺序分配锁,遵循“先到先得”原则,释放锁时队列中等待最久的线程优先获取
实现机制
维护 FIFO 等待队列,通过
hasQueuedPredecessors()
检查队列状态示例:Java 的
ReentrantLock(true)
在获取锁前检查队列
特点
优点:避免线程饥饿
缺点:性能较低,因维护队列和线程切换开销增加
非公平锁
获取顺序:允许线程直接竞争锁,新请求线程可能插队抢占锁,无需检查队列
实现机制
直接尝试 CAS 获取锁,失败后才加入队列
示例:Java 的
ReentrantLock()
默认模式
特点
优点:吞吐量高,减少线程调度开销
缺点:可能导致线程饥饿
核心区别
锁分配顺序:公平锁严格按队列顺序,非公平锁允许插队
性能:非公平锁性能更高(减少线程切换),公平锁适合对公平性敏感的场景
实现复杂度:公平锁需维护队列状态,非公平锁实现更简单
Java 中的实现
ReentrantLock:通过构造函数参数
true
启用公平锁,默认或false
为非公平锁synchronized:底层固定为非公平锁,不支持配置
适用场景
公平锁:需严格顺序的场景(如银行转账、任务调度)
非公平锁:高并发且锁竞争不激烈(如缓存读取、短任务处理)
吞吐量
非公平锁吞吐量为什么比公平锁大?
减少上下文切换开销
非公平锁允许新线程直接抢占锁,避免公平锁强制唤醒队列头部线程的上下文切换(耗时 1-10 微秒)
刚释放锁的线程可能立即重新获取,减少线程状态切换次数
利用 CPU 缓存局部性
非公平锁允许刚释放锁的线程(缓存未失效)快速重入,避免公平锁切换线程导致的缓存重新加载(延迟 100-300 纳秒)
避免队列维护成本
非公平锁仅在竞争失败时加入队列,公平锁每次获取需检查队列状态(增加 20-50 纳秒/次开销)
实验数据:64 线程时非公平锁吞吐量下降幅度比公平锁低 60%-80%
减少线程挂起概率
锁持有时间短时,非公平锁有 30%-50% 概率让新线程直接获取锁,避免挂起和内核调度介入(耗时 1-5 微秒)
优化系统调用频率
非公平锁通过 CAS 自旋减少
park()
系统调用次数,相同并发量下系统调用比公平锁减少 40%-70%高并发测试(64 线程)中吞吐量可达公平锁的 5-8 倍
死锁问题
死锁产生的核心条件
互斥条件:资源被独占使用(如数据库写锁)
请求与保持:持有资源的同时请求新资源(如线程A持有锁1后请求锁2)
不可剥夺:资源只能由持有者主动释放
循环等待:形成等待链(如线程A→B→C→A)
典型死锁场景
多线程嵌套锁:线程以不同顺序获取多个锁(如线程1锁A后锁B,线程2锁B后锁A)
数据库事务冲突:事务交叉更新记录(如事务T1更新X后请求Y,T2更新Y后请求X)
资源分配不合理:短任务高频竞争不可剥夺资源(如内存不足时进程互相等待)
死锁解决方法
预防策略
统一资源申请顺序:按固定编号顺序获取锁(如先锁表A再锁表B)
一次性申请所有资源:启动前声明所需全部资源
动态避免
银行家算法:动态检测资源分配安全性
超时机制:设置锁获取超时(如
tryLock(5, TimeUnit.SECONDS)
)
检测与恢复
资源分配图检测:周期性扫描环路并终止部分进程
事务回滚:数据库自动回滚代价最小的事务
工程实践优化
减少锁粒度:使用读写锁(如
ReentrantReadWriteLock
)无锁编程:采用 CAS(如
AtomicInteger
)或线程本地存储
不同场景的解决方案选择
高并发服务:非公平锁+超时检测(如电商秒杀系统用
ReentrantLock
)金融交易系统:统一资源访问顺序+事务隔离级别(按账户ID升序更新)
实时操作系统:资源抢占机制(允许高优先级进程回收资源)
synchronized锁
定义
核心作用
通过内置锁机制确保多线程环境下共享资源的原子性和可见性
基于对象监视器(Monitor)控制线程对同步代码的访问,同一时刻仅允许一个线程执行锁定代码
三种使用方式
修饰实例方法
锁对象:当前实例(
this
)。场景:保护实例变量的线程安全操作,如多线程操作同一对象的计数器
修饰静态方法
锁对象:类的
Class
对象(ClassName.class
)场景:保护静态变量的线程安全操作,如全局计数器或单例初始化
修饰代码块
锁对象:可指定任意对象(如
this
、自定义锁)场景:缩小锁粒度提升性能,仅对关键代码段加锁
使用
典型应用场景
共享资源的原子操作
需保证复合操作完整性的场景,如计数器增减、转账操作、库存扣减
单例模式的双重检查锁定
延迟初始化且保证线程安全,如全局配置类、数据库连接池
线程安全的数据结构
保护集合类的并发修改,如
Collections.synchronizedList
包装的ArrayList
生产者-消费者模型
协调线程间的数据传递,如消息队列的任务生产和消费
注意事项
锁粒度控制
避免全方法加锁,优先同步关键代码段以减少竞争
死锁预防
统一多锁的获取顺序(如按对象哈希值排序),避免循环等待
性能优化
减少锁持有时间,利用 JVM 锁升级机制(偏向锁→轻量级锁→重量级锁)
异常处理
在
finally
块中释放锁,防止线程阻塞导致资源泄漏
非公平锁
synchronized 的公平性本质
底层实现默认且固定为非公平锁,不保证线程按请求顺序获取锁
新请求线程可能直接抢占锁,而非遵循队列顺序
非公平锁的核心表现
随机竞争机制:锁释放时 JVM 不检查队列顺序,新线程与队列线程随机竞争
潜在饥饿风险:高频短任务场景中可能出现线程长期无法获取锁
设计选择的原因
性能优先:减少线程唤醒和上下文切换(每次 1-10 微秒),吞吐量可达公平锁的 5-8 倍
实现简化:锁升级机制(偏向锁→轻量级锁→重量级锁)针对非公平场景优化
与 ReentrantLock 的对比
灵活性差异:ReentrantLock 可配置公平/非公平模式,synchronized 仅支持非公平
适用场景
synchronized:锁持有时间短、竞争不激烈(如计数器递增)
ReentrantLock 公平模式:需严格顺序的场景(如银行转账)
静态 & 普通
synchronized 锁,静态方法和普通方法区别?
锁对象不同
普通方法:锁定当前实例对象(
this
),同一实例的同步普通方法互斥,不同实例方法可并行执行静态方法:锁定类的
Class
对象,所有实例调用同步静态方法均互斥,全局仅一个线程可执行
作用范围差异
普通方法:仅对同一对象实例的同步方法互斥,不同实例的同名方法不受影响
静态方法:同一时间任何实例的同步静态方法均不可并行执行
锁的粒度
普通方法:锁粒度较细,影响单个实例操作,适合高频实例级资源保护
静态方法:锁粒度较粗,全局锁定类级别资源,可能成为性能瓶颈
适用场景
普通方法:保护实例变量(如对象属性修改),避免多线程数据竞争
静态方法:保护静态变量或实现类级同步逻辑(如单例模式双重检查锁定)
线程交互影响
普通方法:不同线程访问不同实例的同步普通方法可并发执行
静态方法:同一时间仅一个线程能执行静态同步方法(如全局配置更新)
锁类型独立性
互不干扰:类锁与对象锁可同时持有(如线程持有类锁后仍可获取对象锁)
锁升级机制:JVM 对两种锁均可能进行偏向锁、轻量级锁、重量级锁优化升级
支持重入
synchronized 支持重入
同一线程可多次获取同一对象的锁而不会阻塞,避免递归或嵌套调用导致的死锁
实现原理
锁计数器与监视器机制
对象关联锁计数器(
status
)和持有线程 ID,首次获取计数器置 1,重入时递增,释放时递减,归零后完全释放
字节码指令支持
通过
monitorenter
和monitorexit
指令控制代码块进入/退出时的锁状态检查与计数器更新
锁升级机制
无竞争时使用偏向锁记录线程 ID,竞争升级为轻量级锁(CAS 自旋),激烈竞争时转为重量级锁(操作系统互斥锁)
优势与局限
优势
自动管理锁释放,避免死锁风险
支持递归调用和嵌套同步代码块,简化编程逻辑
局限
高并发场景下频繁锁升级(如偏向锁→重量级锁)可能带来性能损耗
与显式锁的对比
synchronized 的可重入性是隐式自动管理,而
ReentrantLock
需手动管理计数器
锁升级
注意高版本因为偏向锁性能收益低和实现过于复杂,已将偏向锁移除
无锁状态
对象创建时无锁,Mark Word 无锁标记,所有线程可自由访问
偏向锁阶段
触发条件:第一个线程首次获取锁时,JVM 将对象头 Mark Word 设为偏向锁状态(锁标志位 01),记录线程 ID
优化目的:单线程重复获取锁时无需同步操作,直接通过线程 ID 比对进入同步代码块
撤销机制:其他线程尝试获取锁时,JVM 暂停持有线程,若锁已释放则升级为轻量级锁
轻量级锁阶段
触发条件:多线程低竞争时升级(锁标志位 00)
实现方式
线程栈帧创建锁记录(Lock Record),拷贝对象头 Mark Word
通过 CAS 替换对象头为指向锁记录的指针,成功则获取锁,失败则自旋等待
自旋优化:自适应策略动态调整自旋次数以减少 CPU 空转
重量级锁阶段
触发条件:自旋次数超限或竞争激烈时升级(锁标志位 10)
实现机制
依赖操作系统互斥量(Mutex)实现阻塞与唤醒,未获锁线程进入等待队列
涉及用户态到内核态切换,带来高上下文切换开销
不可逆性:升级后无法降级,即使竞争减弱仍保持该状态
锁升级的意义
性能优化:减少低竞争场景同步开销(如偏向锁单线程优化)
平衡机制:轻量级锁减少阻塞,重量级锁保障高并发有序执行
Mark Word 动态变化:对象头存储不同元数据(线程 ID、锁记录指针等)
JVM优化
锁升级机制
轻量级锁
通过 CAS 操作和线程栈的锁记录实现非阻塞同步,首次获取时拷贝对象头 Mark Word 至锁记录,CAS 替换对象头指针
自适应自旋策略动态调整自旋次数,减少 CPU 空转
重量级锁
高竞争时升级为操作系统互斥锁(Mutex),依赖等待队列管理线程阻塞与唤醒
用户态到内核态切换带来高开销,升级后不可逆
锁消除
JIT 编译器通过逃逸分析检测同步对象是否仅限当前线程使用,未逃逸时消除锁操作
典型场景:未逃逸的局部变量(如方法内的 StringBuffer)同步被优化为无锁形式
锁粗化
合并相邻同步代码块减少加锁/解锁开销,如循环体内多次同步合并为单次同步
优化原则:临界区执行时间需远小于线程调度时间片(通常控制在 1 毫秒内)
自适应锁优化
动态选择锁策略:低竞争时自旋等待,高竞争时直接阻塞线程
自旋次数阈值基于历史竞争数据动态调整,平衡 CPU 利用率与响应速度
偏向锁替代方案
现代 JVM(JDK 15+)移除偏向锁,优先采用锁消除和轻量级锁优化
遗留支持:通过
-XX:+UseBiasedLocking
强制启用(官方不推荐)
其他优化策略
Mark Word 复用:对象头动态存储线程 ID、锁记录指针等元数据
可重入性:通过锁计数器实现同一线程多次获取锁,避免递归死锁
锁粒度控制:低竞争用细粒度锁,高竞争改用显式锁(如 ReentrantLock)
ReentrantLock
定义
定义
ReentrantLock 的定义
特性
可重入性:同一线程可多次获取同一锁,避免递归调用或嵌套同步块导致的死锁
公平性选择:支持公平锁(按请求顺序分配)和非公平锁(默认模式),前者减少线程饥饿但吞吐量较低
条件变量:通过
newCondition()
创建多个等待队列,实现细粒度线程协作(如生产者-消费者模型的不同唤醒条件)锁超时与中断响应:提供
tryLock(timeout, unit)
和lockInterruptibly()
,支持超时获取锁和中断等待锁状态监控:通过
getHoldCount()
查询锁重入次数,isLocked()
判断锁是否被占用
使用
应用场景
高竞争环境下的共享资源保护
多线程操作计数器、转账逻辑等需要原子性更新的场景,相比
synchronized
在高并发下性能更优
需要公平调度的系统
任务调度需按提交顺序处理请求的场景(如金融交易系统)
复杂线程协作模型
生产者-消费者模型中结合
Condition
实现满队列和空队列的精准唤醒阻塞队列通过
tryLock
控制入队/出队的超时逻辑
框架级并发控制
保护线程池状态(如
ThreadPoolExecutor
的RUNNING
状态)和任务队列的线程安全ConcurrentHashMap
的分段锁机制中使用 ReentrantLock 控制哈希桶的并发访问
需要锁中断或超时响应的场景
分布式锁实现中通过
tryLock
避免线程因网络问题长期阻塞。实时系统要求任务在指定时间内完成并执行降级策略
注意事项
手动释放锁:必须在
finally
块中调用unlock()
,否则可能导致死锁或资源泄漏避免锁嵌套:支持可重入但过度嵌套会增加锁竞争复杂度
公平锁的权衡:仅在严格顺序需求时使用,避免增加线程切换开销
性能监控:通过
getQueueLength()
监控等待线程数,优化锁竞争热点
公平锁
公平锁的核心实现机制
FIFO 队列顺序:
通过
FairSync
类实现,线程调用lock()
时必须先检查 AQS 队列是否存在等待线程队列为空或无前驱节点时尝试 CAS 获取锁
队列存在等待线程时,当前线程加入队列尾部排队
关键方法
hasQueuedPredecessors()
在
tryAcquire()
中调用该方法检查队列状态确保只有队列头部线程能获取锁,阻止新线程插队
CLH 队列管理
AQS 维护 CLH 双向链表队列,公平锁通过
enq()
将竞争失败的线程封装为 Node 节点加入队列尾部锁释放时唤醒队列头部节点的线程,保证顺序执行
与非公平锁的差异
初始抢占逻辑
公平锁:必须检查队列状态后才尝试 CAS
非公平锁:直接 CAS 抢占,不检查队列
性能表现
公平锁吞吐量较低(约下降 5-8 倍),适合顺序敏感场景
非公平锁适合短任务竞争,吞吐量更高
线程饥饿风险
公平锁无饥饿问题,非公平锁可能引发线程长期等待
适用场景与代价
适用场景:支付系统、任务调度等需严格顺序的场景
实现代价
每次锁获取需队列检查(增加 20-50 纳秒开销)
频繁线程切换导致性能下降(对比非公平锁吞吐量降低 60%-80%)
CountDownLatch
同步工具
核心功能
允许一个或多个线程等待其他线程完成特定操作后再继续执行
内部维护计数器,初始化时指定等待的线程数量,
countDown()
减少计数,计数器归零时唤醒等待线程
工作原理
初始化计数器:
new CountDownLatch(n)
设置初始值n
(需等待的任务数)任务完成通知:子线程调用
countDown()
安全减少计数(基于 CAS 实现)阻塞与唤醒:主线程
await()
阻塞至计数器归零,支持超时await(timeout, unit)
典型应用场景
主线程等待子任务完成:服务启动时等待所有组件初始化完毕
CountDownLatch latch = new CountDownLatch(3); // 启动 3 个子线程初始化服务并调用 latch.countDown() latch.await(); // 阻塞至所有服务就绪
多线程结果汇总:并行计算后主线程汇总结果(如分片数据处理)
并发测试同步:确保所有并发任务执行完毕后再验证结果
资源协调:学生全部离场后通知家长,顾客到齐后服务员上菜
注意事项
一次性使用:计数器归零后无法重置,需重新创建
异常处理:子线程在
finally
中调用countDown()
避免主线程永久阻塞超时机制:建议设置
await()
超时防止系统僵死精确计数:初始值与实际任务数严格匹配(过多导致阻塞,过少提前唤醒)
AQS
定义
AQS 的基本概念与核心功能
定义:Java 并发包(JUC)的核心框架,用于构建锁和同步器(如 ReentrantLock、Semaphore、CountDownLatch)
核心功能:
通过
volatile int state
管理资源状态,支持原子操作(getState()
/setState()
/CAS)基于 CLH 队列实现线程排队与唤醒,通过 LockSupport 实现线程阻塞(
park()
)与唤醒(unpark()
)
底层实现原理
同步队列(CLH 队列)
结构:双向链表,节点(Node)包含线程、等待状态(
waitStatus
)及前后指针作用:存储未获取资源的线程,实现 FIFO 公平调度,头节点(
head
)表示当前持有资源的线程
条件队列
单向链表,用于
Condition
的等待/通知机制(如await()
和signal()
)
资源共享模式
独占模式
同一时刻仅一个线程访问资源(如 ReentrantLock),通过
tryAcquire()
/tryRelease()
实现可重入性:
state
记录同一线程多次获取锁的计数
共享模式
允许多线程同时访问资源(如 Semaphore、CountDownLatch),通过
tryAcquireShared()
/tryReleaseShared()
实现
典型应用场景
锁实现:ReentrantLock 重写
tryAcquire()
实现公平/非公平锁同步器:
CountDownLatch:
state
初始化为计数值,countDown()
递减,归零时唤醒所有等待线程Semaphore:通过
state
控制并发线程数,acquire()
/release()
操作许可
优势与设计特点
模板方法模式:子类仅需实现
tryAcquire()
/tryRelease()
等模板方法,AQS 自动处理队列管理与调度性能优化:
双向链表支持快速删除节点(如线程超时或中断时)
自旋优化减少上下文切换开销
可扩展性:通过组合 AQS 实现复杂同步策略(如读写锁分离)
可重入公平锁
核心实现原理
公平性保证
通过 AQS 的 CLH 队列实现线程 FIFO 排队,在
tryAcquire()
中调用hasQueuedPredecessors()
检查队列是否有等待线程
可重入性
通过
state
变量记录锁持有次数,同一线程多次获取时递增state
,释放时递减至归零
关键实现步骤
定义 Sync 类继承 AQS
创建
ReentrantFairLock
类,内部定义Sync
类继承AbstractQueuedSynchronizer
重写 tryAcquire 方法
检查
state == 0
:若无等待线程且 CAS 设置state
成功,获取锁并设置独占线程若当前线程是持有者,
state += acquires
实现可重入
重写 tryRelease 方法
递减
state
,归零时清空独占线程标识
公平锁与非公平锁区别
公平锁:强制检查队列状态,仅无等待线程时尝试获取锁
非公平锁:允许直接 CAS 抢占锁,无需检查队列
锁操作流程
加锁:
lock()
调用失败时,线程进入 CLH 队列并阻塞,等待前驱节点唤醒解锁:
unlock()
释放锁后,唤醒队列中下一个线程
性能与注意事项
公平性代价:获取锁需遍历队列,吞吐量低于非公平锁
重入限制:
lock()
与unlock()
次数需严格匹配,否则导致死锁或状态异常
评论区