synchronized与volatile的区别
synchronized
- 保证读写操作的原子性和可见性,通过锁机制实现线程安全
- 可修饰方法(静态/非静态)或代码块
- 非静态方法锁为对象锁,静态方法锁为类锁
- 锁升级过程:无竞争时偏向锁 → 竞争时轻量级锁 → 持续竞争升级为重量级锁(不可逆)
volatile
- 仅保证可见性和禁止指令重排序,不保证原子性
- 只能修饰变量
- 底层通过Lock前缀指令强制缓存回写,并触发MESI协议使其他处理器缓存失效
内存模型与线程交互
JVM内存区域
- 方法区:存储类信息、静态变量(JDK 8后由元空间替代)
- 堆:存储对象实例,多线程共享
- 栈:存储方法调用帧与局部变量,线程私有
多线程数据访问
- 共享堆内存变量需通过同步机制(如synchronized)保证安全
- 每个线程拥有独立的工作内存(缓存),volatile强制数据刷新到主内存
volatile实现原理
硬件级机制
-
Lock前缀指令
- 触发处理器缓存回写到主内存
- 现代CPU通过缓存锁定(非总线锁定)实现高效独占
-
缓存一致性协议(MESI)
- 监听总线操作,发现共享变量被修改时使本地缓存失效
- 下次读取时强制从主内存重新加载
局限性
- 无法解决写后读的竞态条件(如
i++需配合synchronized或原子类)
锁的优化与问题
synchronized锁升级
- 偏向锁:单线程无竞争时直接获取
- 轻量级锁:竞争轻微时通过CAS自旋尝试
- 重量级锁:竞争激烈时线程阻塞,依赖操作系统互斥量
锁的副作用
- 重量级锁导致上下文切换开销
- 非静态方法锁影响同一实例的其他同步方法
- 静态方法锁影响整个类的同步方法
原子类与CAS问题
Java原子类
AtomicInteger、AtomicReference等基于CAS实现- 适用场景:计数器、状态标志等轻量级同步
CAS三大问题
- ABA问题
- 解决方案:使用版本号(如
AtomicStampedReference)
- 解决方案:使用版本号(如
- 循环时间长开销大
- 自旋CAS可能消耗CPU资源
- 只能保证单个变量原子性
- 复合操作需借助锁或
AtomicReference
- 复合操作需借助锁或
指令重排序与并行机制
流水线并行
- CPU将指令拆分为多个阶段(取指、译码、执行等)
- 不同指令的阶段可并行执行以提高吞吐量
重排序规则
- 编译器/处理器在不改变单线程语义的前提下优化指令顺序
- 数据依赖性(如写后读)会阻止重排序
内存屏障
- volatile通过插入屏障禁止特定重排序
- 确保修改对其他线程立即可见