Java并发编程核心机制:synchronized、volatile与CAS原理剖析
从字节码层面看synchronized
的实现,会发现它通过monitorenter
和monitorenter
指令实现同步控制。当线程执行到同步块时,会尝试获取对象的监视器锁(monitor),这个锁信息存储在对象头的Mark Word中。JDK6之后引入的偏向锁、轻量级锁优化,使得无竞争情况下的同步开销大幅降低。以32位JVM为例,对象头的Mark Word在不同锁状态下会有不同的结构:
// 对象头结构示例(32位)
// 无锁状态:25位哈希码|4位分代年龄|1位偏向模式|2位锁标志(01)
// 偏向锁:23位线程ID|2位epoch|4位分代年龄|1位偏向模式|2位锁标志(01)
// 轻量级锁:30位指向栈中锁记录的指针|2位锁标志(00)
// 重量级锁:30位指向监视器锁的指针|2位锁标志(10)
2.2 对象锁与类锁的实战差异
对象锁作用于实例方法时,锁的是当前对象实例;作用于代码块时,锁的是指定对象。类锁则是锁定Class对象,影响所有实例的访问。这种差异在分布式环境下尤其需要注意:
public class OrderService {
// 对象锁
public synchronized void createOrder() {
// 操作实例变量
}
// 类锁
public static synchronized void updateConfig() {
// 操作静态变量
}
public void process() {
Object lock = new Object();
synchronized(lock) { // 自定义对象锁
// 临界区代码
}
}
}
在集群环境中,类锁只能保证单个JVM内的同步,需要配合分布式锁实现全局控制。而对象锁的粒度更细,适合保护实例级别的资源。
2.3 锁升级的底层机制
当多个线程竞争锁时,JVM会经历锁升级过程:
- 偏向锁:第一个访问线程通过CAS设置线程ID
- 轻量级锁:出现竞争时转换为指向栈中锁记录的指针
- 重量级锁:自旋超过阈值(默认10次)后升级为操作系统级别的互斥量
使用jol-core
工具可以观察对象头变化:
// 添加Maven依赖
// <dependency>
// <groupId>org.openjdk.jol</groupId>
// <artifactId>jol-core</artifactId>
// <version>0.16</version>
// </dependency>
public static void main(String[] args) {
Object obj = new Object();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
synchronized(obj) {
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
}
三、volatile的内存语义深度解析
3.1 可见性原理与内存屏障
volatile变量的写操作会插入StoreStore屏障和StoreLoad屏障,保证:
- 写操作前的所有普通写对其它处理器可见
- 禁止与后续volatile写重排序
读操作会插入LoadLoad屏障和LoadStore屏障,确保:
- 每次读取都从主内存获取最新值
- 禁止与前面的volatile读重排序
3.2 双重检查锁定的正确实现
经典的单例模式实现需要volatile防止指令重排序:
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 非原子操作
}
}
}
return instance;
}
}
没有volatile时,new Singleton()
可能被重排序为:
- 分配内存空间
- 将引用指向内存空间(此时instance != null)
- 初始化对象
其他线程可能拿到未初始化的实例,导致NPE。
四、CAS机制的底层实现与ABA问题
4.1 Unsafe类的魔法操作
Java通过Unsafe类提供CAS原子操作,核心方法:
public final native boolean compareAndSwapObject(
Object o, long offset, Object expected, Object x);
以AtomicInteger为例:
public class AtomicInteger {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
4.2 ABA问题的解决方案
使用版本号戳记解决ABA问题:
AtomicStampedReference<Integer> atomicRef =
new AtomicStampedReference<>(100, 0);
int stamp = atomicRef.getStamp();
atomicRef.compareAndSet(100, 200, stamp, stamp+1);
五、三大机制的对比与选型策略
5.1 性能对比基准测试
使用JMH测试不同场景下的性能:
@BenchmarkMode(Mode.Throughput)
@State(Scope.Thread)
public class LockBenchmark {
private int counter = 0;
private volatile int volatileCounter = 0;
private AtomicInteger atomicCounter = new AtomicInteger(0);
@Benchmark
public void synchronizedIncrement() {
synchronized(this) {
counter++;
}
}
@Benchmark
public void atomicIncrement() {
atomicCounter.incrementAndGet();
}
}
测试结果可能显示:
- 低竞争场景:CAS > 偏向锁 > volatile
- 高竞争场景:锁升级后的重量级锁更稳定
5.2 选型决策树
- 需要原子性但无竞争 → volatile
- 简单同步且竞争不激烈 → synchronized
- 高并发计数 → AtomicLong+CAS
- 需要可中断锁 → ReentrantLock
- 状态标志 → volatile boolean
- 延迟初始化 → synchronized + volatile双检锁
六、现代并发模式下的演进
6.1 JDK8之后的改进
- StampedLock:乐观读锁提升读多写少场景性能
- LongAdder:分段CAS解决热点数据竞争
- VarHandle:替代Unsafe的安全内存访问
6.2 内存模型的最新发展
JEP 188提出的Java内存模型更新:
- 新的Fences API(LoadFence/StoreFence)
- 更精细的内存排序控制
- 与C++内存模型更好的互操作性
正文到此结束
相关文章
热门推荐
评论插件初始化中...