悲观锁 & 乐观锁
区别
核心思想差异
悲观锁:假设并发冲突必然发生,操作数据前必须先加锁(如数据库行锁、表锁),确保独占访问
乐观锁:假设并发冲突极少发生,仅在数据更新时检测是否被修改(通过版本号或 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:可中断、超时的可重入锁,替代synchronizedStampedLock:乐观读锁,减少读写锁竞争(如读多写少的场景优化)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:支持可重入锁(如递归调用)
评论区