悲观锁 & 乐观锁
区别
核心思想差异
悲观锁:假设并发冲突必然发生,操作数据前必须先加锁(如数据库行锁、表锁),确保独占访问
乐观锁:假设并发冲突极少发生,仅在数据更新时检测是否被修改(通过版本号或 CAS),允许无锁并发读取
实现机制对比
悲观锁
加锁时机:操作数据前加锁(如
SELECT FOR UPDATE
)典型实现:数据库行锁、Java
synchronized
冲突处理:通过阻塞其他线程避免冲突
数据一致性:强一致性(全程独占)
乐观锁
加锁时机:仅在更新时检测冲突(无前置锁)
典型实现:版本号机制、CAS 算法(如
AtomicInteger
)冲突处理:检测到冲突后回滚或重试
数据一致性:最终一致性(允许临时中间状态)
性能与适用场景
悲观锁适用场景
高并发写操作(如金融转账、库存扣减)
需严格避免脏读、不可重复读的场景
缺点:锁竞争导致线程阻塞,吞吐量低
乐观锁适用场景
高并发读操作(如新闻浏览、社交点赞)
冲突概率低的场景(如用户信息更新)
缺点:频繁冲突时重试开销大(需配合重试策略)
典型问题与解决方案
悲观锁死锁
解决方案:锁超时机制(如 MySQL 的
innodb_lock_wait_timeout
)、死锁检测算法
乐观锁 ABA 问题
解决方案:带版本号的 CAS(如
AtomicStampedReference
)
混合策略实践
分段锁(如
ConcurrentHashMap
):将全局锁拆分为多个细粒度锁,平衡性能与一致性自适应锁(如 Java 锁升级):根据竞争动态切换悲观/乐观策略
乐观锁
实现方式
基于版本号机制
数据对象增加版本号字段(如
version
),更新时校验当前版本号是否一致,一致则递增版本并更新,否则重试或回滚Java 实现:通过
AtomicInteger
管理版本号字段,结合业务逻辑校验
基于时间戳机制
使用时间戳字段替代版本号,更新时校验时间戳是否一致
注意点:需处理分布式系统时钟同步问题,避免因时间误差导致冲突
CAS 原子操作
使用
AtomicInteger
、AtomicLong
等原子类,依赖 CPU 的cmpxchg
指令实现原子性比较与交换扩展方案
解决 ABA 问题:使用
AtomicStampedReference
附加版本号或时间戳
JPA/Hibernate 的 @Version 注解
在实体类中添加
@Version
注解字段,框架自动管理版本号校验,简化数据库乐观锁实现
Redis 分布式乐观锁
利用 Redis 的
SETNX(setIfAbsent)
实现跨进程锁,结合超时机制避免死锁典型场景:分布式系统数据同步、秒杀库存控制
适用场景
低冲突场景:优先使用版本号或 CAS 减少锁开销
高并发写操作:结合数据库版本号机制确保强一致性
分布式系统:采用 Redis 锁实现跨服务同步
ORM 框架集成:直接使用
@Version
简化开发
注意事项
ABA 问题:优先使用
AtomicStampedReference
替代基础 CAS自旋开销:设置最大重试次数,避免 CPU 资源浪费
性能权衡:高冲突场景评估乐观锁重试成本与悲观锁阻塞开销
CAS
缺点
ABA 问题
问题描述:CAS 仅检测值是否变化,无法感知中间状态变化(如 A→B→A)
解决方案:引入版本号机制(如
AtomicStampedReference
类),同时比对值和版本号
自旋开销大
问题描述:高并发场景下,CAS 失败后循环重试导致 CPU 资源浪费
解决方案
设置最大自旋次数或退避策略(如
Thread.yield()
)高竞争场景改用锁机制(如
synchronized
)
仅支持单变量原子性
问题描述:无法原子操作多个共享变量,可能导致中间状态不一致
解决方案
封装多个变量为对象,通过
AtomicReference
管理使用锁机制或事务内存(如 JDK9+ 的
VarHandle
)
高竞争性能下降
问题描述:线程竞争激烈时,CAS 成功率低导致吞吐量下降
解决方案:改用分段计数机制(如
LongAdder
替代AtomicLong
)
其他潜在问题
优先级反转:高优先级线程自旋等待低优先级线程释放资源
硬件依赖:部分系统需依赖锁或其他同步机制
代码复杂性:无锁数据结构实现需处理边界条件,开发难度高
适用场景
为什么不能所有的锁都用 CAS?
高竞争环境性能瓶颈
自旋开销大:CAS 失败后持续重试导致 CPU 资源浪费,高并发场景(如秒杀系统)下传统锁的阻塞机制更高效
适应性不足:CAS 无法动态调整等待策略,传统锁(如
ReentrantLock
)可结合公平锁优化线程调度
功能局限性
仅支持单变量原子操作:无法处理多变量或复合操作(如转账需同时修改两个账户余额)
缺乏高级同步支持:无法实现条件变量(
Condition
)、读写锁分离等功能
ABA 问题与维护成本
值变化不可感知:A→B→A 的变化导致逻辑错误(如无锁栈顶元素多次弹出后重新压入)
解决方案复杂:需引入版本号(
AtomicStampedReference
)或时间戳,增加代码复杂度
线程公平性与资源分配
饥饿风险:CAS 不保证线程排队顺序,公平锁通过 CLH 队列强制 FIFO 避免线程饿死
优先级反转:高优先级线程可能因自旋等待低优先级线程被阻塞
硬件与场景依赖
硬件限制:部分嵌入式系统缺乏 CAS 指令支持,需依赖锁机制
适用场景局限:长耗时操作(如数据库事务)用锁更合适,CAS 适合短临界区、低冲突场景
替代方案与混合策略
优先 CAS 场景:简单原子操作(如计数器自增)、低冲突环境(状态标志位更新)
改用锁机制场景:复杂业务逻辑(多变量事务)、需公平性保障(任务队列调度)
混合策略:分段计数(
LongAdder
)、乐观读模式(StampedLock
)
问题 & 解决
CAS 有什么问题,Java 是怎么解决的?
ABA 问题
问题:无法感知中间状态变化(如 A→B→A)
Java 解决方案:通过
AtomicStampedReference
引入版本号,同时比对值和版本号
自旋开销大
问题:高并发下循环重试导致 CPU 浪费
Java 解决方案
分段 CAS 机制:如
LongAdder
拆分计数器减少竞争退避策略:结合
Thread.yield()
或限制自旋次数
仅支持单变量原子操作
问题:无法原子操作多变量导致中间状态不一致
Java 解决方案
封装复合状态:使用
AtomicReference
管理对象事务内存:JDK9+ 的
VarHandle
支持多变量原子操作
高竞争性能下降
问题:线程竞争激烈时吞吐量降低
Java 解决方案
切换锁机制:如
synchronized
或ReentrantLock
混合策略:AQS 框架结合 CAS 与队列机制
硬件依赖与代码复杂性
问题:部分系统缺乏 CAS 指令支持,无锁数据结构开发复杂
Java 解决方案
内置原子类:如
AtomicInteger
屏蔽底层差异工具类封装:提供
ConcurrentLinkedQueue
等线程安全结构
voliatle
作用
保证内存可见性
强制每次读写操作直接访问主内存,确保多线程间修改即时可见
示例:线程 A 修改
volatile
变量后,线程 B 能立即读取最新值,避免缓存不一致问题
禁止指令重排序
通过内存屏障防止编译器和处理器对指令进行重排序优化
应用:单例模式中避免获取未完全初始化的对象
适用场景
状态标志:标记线程状态(如任务完成、中断标志),确保状态变更可见
硬件寄存器访问:嵌入式开发中读取可能被硬件修改的寄存器值,防止读取旧值
轻量级同步:读多写少且无需原子性保证的场景(如计数器标记)
局限性
不保证原子性:单次读/写原子性,复合操作(如
i++
)需依赖synchronized
或原子类性能开销:频繁读写时强制刷新主内存会增加性能损耗
语言差异
Java:通过内存屏障实现可见性和有序性,与
synchronized
和原子类配合使用C/C++:仅禁止编译器优化,需结合锁或原子操作实现线程安全
线程安全
volatile 可以保证线程安全吗?
volatile 的线程安全保证范围
可见性保证:变量修改后立即刷新到主内存,其他线程读取时强制获取最新值(如状态标志变更避免死循环问题)
有序性保证:通过内存屏障禁止指令重排序,确保代码执行顺序与编写顺序一致(如单例模式避免获取未初始化对象)
volatile 的局限性
不保证原子性:复合操作(如
i++
)即使修饰为volatile
仍可能因线程切换导致数据不一致适用场景限制:仅适用于读多写少且操作简单的场景(如状态标志、硬件寄存器访问)
正确使用 volatile 的场景
状态标志控制:如
volatile boolean flag
控制任务启停,保证状态变更可见性双重检查锁定单例模式:结合
volatile
和synchronized
避免指令重排序引发的初始化问题轻量级同步:读多写少场景下减少锁竞争开销
线程安全的完整实现方案
原子类(如
AtomicInteger
):通过CAS + volatile
组合保证原子性和可见性(适用于计数器等场景)synchronized 或显式锁:对复合操作(如转账、库存扣减)提供原子性保证,但引入锁竞争开销
juc 包常用类
并发集合类
ConcurrentHashMap
:分段锁实现的线程安全哈希表,适用于高并发读写(如缓存)CopyOnWriteArrayList
:写时复制列表,读多写少场景高效(如监听器列表)
同步工具类
CountDownLatch
:倒计时门闩,主线程等待多个子任务完成(如系统初始化)CyclicBarrier
:可重用屏障,多线程同步至同一阶段(如分批次数据处理)Semaphore
:控制并发资源访问数(如连接池限流)
执行框架类
ThreadPoolExecutor
:灵活配置线程池参数(核心线程数、队列策略等)CompletableFuture
:异步编程工具,支持链式调用和组合任务(如并行计算编排)
原子操作类
AtomicInteger
:基于 CAS 的无锁计数器,高性能并发计数(如请求量统计)AtomicReference
:原子更新对象引用(如状态标志的无锁更新)
锁与条件类
ReentrantLock
:可中断、超时的可重入锁,替代synchronized
StampedLock
:乐观读锁,减少读写锁竞争(如读多写少的场景优化)Condition
:细粒度条件等待/唤醒,支持多条件队列(如生产者-消费者模型)
并发队列类
LinkedBlockingQueue
:无界或容量可选阻塞队列(如任务缓冲队列)SynchronousQueue
:直接传递任务的队列(如线程池的立即匹配策略)
锁分类
互斥锁
synchronized
特点:JVM 内置锁,自动管理锁的获取与释放,支持可重入
场景:简单临界区保护(如计数器操作)、单例模式的双重检查锁
ReentrantLock
特点:支持可中断锁、公平锁、条件变量,需手动释放锁
场景:复杂同步需求(如超时控制、多条件判断)
读写锁
ReentrantReadWriteLock
特点:读操作共享,写操作独占,提升读多写少场景性能
场景:缓存系统、配置文件读取、共享文档操作
无锁机制
原子类(
AtomicInteger
等)特点:基于 CAS 实现无锁线程安全操作
场景:高并发计数(如请求量统计、状态标志更新)
volatile
特点:保证变量可见性,需结合锁保证复合操作原子性
场景:简单状态标志(如线程启停控制)
乐观锁与悲观锁
乐观锁
实现:CAS、版本号机制(如数据库版本字段)
场景:读多写少场景(如商品库存、点赞计数)
悲观锁
实现:
synchronized
、ReentrantLock
、数据库SELECT FOR UPDATE
场景:写操作频繁场景(如金融交易、库存扣减)
公平锁与非公平锁
公平锁
特点:按请求顺序分配锁,避免线程饥饿
场景:任务调度需严格顺序(如转账操作、数据库连接池)
非公平锁
特点:允许线程插队,吞吐量更高
场景:高并发读操作(如缓存读取、秒杀系统)
分段锁与锁优化
ConcurrentHashMap
的分段锁特点:锁粒度细化到数据段,减少锁竞争
场景:高并发哈希表操作
锁粗化与锁消除
特点:JVM 自动优化减少锁开销
场景:循环内重复加锁、明显无竞争的同步代码
锁实践
互斥锁的实践要点
synchronized
关键字临界区保护:通过同步方法或代码块保护共享资源(如计数器操作)
锁对象选择:指定特定对象作为锁缩小粒度(如
synchronized (lockObject)
)单例模式:双重检查锁定需结合
volatile
避免指令重排序
ReentrantLock
高级控制可中断锁:
lockInterruptibly()
避免永久阻塞(如服务端请求处理)超时机制:
tryLock(1, TimeUnit.SECONDS)
防止死锁(分布式锁场景)条件变量:
newCondition()
实现线程协作(生产者-消费者模型)
读写分离场景实践
ReentrantReadWriteLock
缓存系统:读锁允许多线程并发读,写锁保证数据更新原子性
配置热更新:读锁读取配置,写锁更新配置实现动态加载
StampedLock
性能优化乐观读模式:
tryOptimisticRead()
提升读多写少场景吞吐量(如金融行情读取)三维空间计算:保护多个关联变量原子更新(如坐标 x,y,z)
线程安全工具类应用
原子类与无锁编程
计数器场景:
AtomicInteger
替代同步锁提升性能 5-10 倍状态标志:
volatile
修饰简单状态变量(需注意复合操作加锁)
阻塞队列实践
生产者-消费者模型:
BlockingQueue
自动处理阻塞与唤醒(如LinkedBlockingQueue
)
锁使用最佳实践
死锁预防
统一加锁顺序:按锁对象 hashCode 排序
资源限时获取:
tryLock
设置超时(推荐 100ms-1s)
性能优化技巧
锁粒度控制:拆分全局锁为分段锁(如
ConcurrentHashMap
桶锁)锁消除与粗化:依赖 JVM 自动优化循环内同步代码
资源管理规范
try-finally 范式:确保任何路径释放锁(如
finally { lock.unlock() }
)AutoCloseable 集成:支持 try-with-resources 自动释放锁
典型场景锁选型参考
简单临界区:
synchronized
(单例模式、计数器)超时/中断控制:
ReentrantLock
(分布式任务调度)读多写少(>10:1):
ReentrantReadWriteLock
(文档协作系统)极高读取并发:
StampedLock
(实时行情推送)无数据竞争:原子类(请求统计、ID 生成器)
线程协作:
Condition + ReentrantLock
(订单状态机)
线程同步
除了用 synchronized,还有什么方法可以实现线程同步?
显式锁(ReentrantLock)
手动控制锁的获取与释放(
lock()
/unlock()
),支持公平锁和非公平锁模式特性:
超时获取(
tryLock
)和可中断锁(lockInterruptibly
)多条件变量(
Condition
)实现精准线程唤醒
场景:高并发资源保护(如转账逻辑)、公平调度系统
原子操作类(Atomic 类)
使用
AtomicInteger
、AtomicReference
等通过 CAS 保证单变量原子性场景:简单计数器、状态标志(如
volatile + AtomicBoolean
)
信号量(Semaphore)
通过计数器控制资源并发访问数(
acquire()
/release()
)场景:数据库连接池限制、并发下载流量控制
读写锁(ReentrantReadWriteLock)
读锁共享,写锁独占。
场景:读多写少的配置管理、缓存系统热更新
线程本地存储(ThreadLocal)
为每个线程创建变量副本,避免共享变量同步问题
场景:数据库连接管理、线程不安全工具类(如日期格式化)
条件变量(Condition)
配合锁实现精准唤醒(
await()
/signal()
)场景:生产者-消费者模型中的满/空队列分离控制
volatile 关键字
保证变量可见性和禁止指令重排序
场景:状态标志(任务终止标记)、双重检查锁定单例模式
阻塞队列(BlockingQueue)
内置线程安全机制,自动处理阻塞与唤醒(
put()
/take()
)实现:
ArrayBlockingQueue
(任务调度)、LinkedBlockingQueue
(日志异步处理)
synchronized vs Reentrantlock
实现机制
synchronized:基于 JVM 的监视器锁(Monitor),通过对象头管理锁状态,自动处理加锁与释放
ReentrantLock:基于 JDK 的
Lock
接口,依赖 AQS 队列和 CAS 操作,需显式调用lock()
/unlock()
功能特性
锁类型
synchronized:仅非公平锁,线程获取顺序随机
ReentrantLock:支持公平锁(按请求顺序)和非公平锁(默认)
中断与超时
ReentrantLock:支持
lockInterruptibly()
响应中断,tryLock(timeout)
超时机制synchronized:线程阻塞后无法中断,可能导致死锁
条件变量
ReentrantLock:通过
newCondition()
创建多个Condition
实现精准唤醒(如生产者-消费者模型)synchronized:仅支持
wait()
/notify()
单路通知
性能与优化
低竞争场景:synchronized 因偏向锁、轻量级锁优化性能接近 ReentrantLock
高并发场景:ReentrantLock 非公平锁通过 CAS 减少线程切换,性能更优;synchronized 升级为重量级锁后开销大
使用方式
synchronized:语法简洁,自动释放锁,适用于方法或代码块同步
ReentrantLock:需显式调用
lock()
/unlock()
,支持tryLock()
和超时机制,结合try-finally
确保释放
适用场景
synchronized:简单同步需求(实例变量保护)、低竞争场景、快速开发
ReentrantLock:复杂同步逻辑(公平锁、中断响应)、高并发优化、多条件协作(任务调度优先级控制)
锁的释放与可重入性
synchronized:可重入且自动释放锁,JVM 在同步代码结束时解锁
ReentrantLock:可重入但需手动释放,未正确
unlock()
可能导致死锁
注意事项
锁粒度:synchronized 静态方法锁定类对象可能影响性能,ReentrantLock 可通过细粒度条件优化
死锁预防:ReentrantLock 的
tryLock()
和超时机制减少死锁概率,synchronized 需避免嵌套锁锁升级机制:synchronized 从偏向锁逐步升级为重量级锁,ReentrantLock 优化需开发者控制
CAS vs AQS
底层实现的依赖关系
CAS 是 AQS 的原子操作基础
AQS 通过
compareAndSetState()
方法实现state
的原子更新(如获取锁时从 0 改为 1)CLH 队列的节点插入与删除(如
enq()
方法)依赖 CAS 保证线程安全
协同应用的场景
锁的公平性与非公平性实现
非公平锁:直接通过 CAS 抢占
state
(如ReentrantLock
默认模式)公平锁:在
tryAcquire()
中先检查队列状态,再通过 CAS 修改state
资源释放与唤醒机制
AQS 的
release()
方法通过 CAS 重置state
,并唤醒队列中的下一个线程
功能定位的区别
CAS 的轻量级特性
单变量原子操作(如计数器增减),无需锁机制,适用于低竞争场景
AQS 的同步框架特性
管理多线程竞争资源的排队、阻塞与唤醒(如
Semaphore
控制并发数)支持复杂同步逻辑(如读写锁分离、条件变量
Condition
)
性能优化的互补性
CAS 的局限性
ABA 问题通过
AtomicStampedReference
解决高竞争下自旋开销由 AQS 队列阻塞机制缓解
AQS 的扩展性
结合 CAS 与队列机制平衡吞吐量(如非公平锁优先 CAS 抢占)
支持可重入性(通过
state
计数器记录锁持有次数)
Threadlocal
Threadlocal 作用,原理,具体里面存的 key value 是啥,会有什么问题,如何解决?
作用
线程数据隔离:为每个线程创建独立变量副本,避免多线程共享数据同步开销
上下文管理:存储线程级上下文信息(如用户会话、数据库连接),简化参数传递
原理
ThreadLocalMap 结构:每个线程维护一个
ThreadLocalMap
,以ThreadLocal
对象为键(弱引用),用户数据为值(强引用)操作机制:通过
set()
/get()
读写当前线程的ThreadLocalMap
,remove()
清理数据初始化:使用
withInitial()
设置默认值,首次get()
触发初始化
存储的 key-value
Key:
ThreadLocal
实例(弱引用),通过threadLocalHashCode
唯一标识Value:线程本地数据(强引用),如计数器、会话对象
潜在问题
内存泄漏
原因:
Entry
的 key 被 GC 回收后,value 仍占用内存(尤其线程池复用线程时)场景:未调用
remove()
导致残留数据长期存在
数据污染:线程池复用线程时,残留数据被后续任务误用
性能开销:哈希冲突使用线性探测法,高并发效率降低
解决方案
及时清理:在
try-finally
中调用remove()
确保资源释放弱引用优化:用
static final
修饰ThreadLocal
实例,减少 key 回收风险避免大对象:优先存储轻量级数据,大型对象改用外部缓存
数据隔离检查:线程池任务执行前显式清理
ThreadLocal
数据
指令重排序
指令重排序的原理是什么?
编译器优化重排序
消除冗余:删除重复计算或无效指令(如未使用的变量赋值)
指令调度:将无数据依赖的指令交错排列以填充 CPU 流水线
循环展开:复制循环体减少分支预测开销
处理器动态重排序
乱序执行(OoOE)
使用重排序缓冲区(ROB)存储未提交指令,通过保留站监控操作数可用性
内存系统优化
写缓冲区(Write Buffer)延迟内存写入,合并多次写操作
缓存 Bank 划分允许并行访问不同存储单元
重排序的约束条件
数据依赖性限制
真数据依赖(RAW)不可重排,反依赖(WAR)和输出依赖(WAW)可通过寄存器重命名解除
控制依赖性限制
分支预测允许提前执行推测路径指令(预测失败则丢弃结果)
重排序的动机与效果
性能提升机制
减少流水线停顿,隐藏内存延迟,提高缓存命中率
典型优化效果
提升流水线指令吞吐量,通过寄存器重命名实现多指令并行
重排序引发的问题与解决方案
多线程可见性问题
写缓冲区延迟导致读取旧值:使用内存屏障指令(如 x86 的
MFENCE
)强制刷新
有序性破坏
对象未初始化被使用:通过
volatile
关键字插入 LoadLoad/StoreStore 屏障
volatile vs sychronized
可见性保证
volatile:通过内存屏障强制将修改值刷新到主内存,禁止指令重排序(LoadLoad/StoreStore 屏障)
synchronized:锁机制强制同步块内变量从主内存读取/刷新
原子性差异
volatile:仅保证单次读/写原子性(如 boolean 赋值),不支持复合操作(如
i++
)synchronized:通过独占锁保证代码块内所有操作的原子性
作用范围与实现机制
volatile:仅修饰变量,基于 JVM 内存模型实现无锁同步
synchronized:可修饰方法或代码块,基于对象监视器锁(Monitor)实现互斥访问
性能与锁机制
volatile:无锁机制,无线程阻塞,适合高频读操作
synchronized:涉及锁获取/释放,可能触发线程阻塞,但 JVM 优化后性能提升(偏向锁、轻量级锁)
适用场景
volatile:单例模式 DCL、状态标志(如
isRunning
)、硬件寄存器访问synchronized:需要原子性的复合操作(如计数器递增)、临界区资源保护(如文件读写)
有序性处理
volatile:通过内存屏障禁止指令重排序,保证操作顺序与代码一致
synchronized:隐式通过锁保证有序性,无法主动禁止重排序
可重入性与灵活性
volatile:不支持重入,仅适用于简单变量同步
synchronized:支持可重入锁(如递归调用)
评论区