Java锁机制深度解析:从synchronized到StampedLock

在并发编程中,锁是保障线程安全的核心机制。本文将系统剖析Java中各类锁的实现原理、适用场景及性能差异,助你精准选择并发控制方案。


一、锁的本质与核心作用

在多线程环境中,锁用于解决两类问题:

  1. 互斥(Mutual Exclusion)​​:保证同一时刻只有一个线程访问共享资源
  2. 可见性(Visibility)​​:确保共享变量的修改对其他线程立即可见

Java内存模型(JMM)通过happens-before原则保证锁的可见性:

解锁操作 happens-before 后续的加锁操作

二、内置锁:synchronized 关键字

1. 实现原理

public synchronized void method() { 
    // 临界区 
}

// 等价于
public void method() {
    this.intrinsicLock.lock();
    try {
        // 临界区
    } finally {
        this.intrinsicLock.unlock();
    }
}
  • 对象头Mark Word​:锁状态存储在对象头中(无锁/偏向锁/轻量级锁/重量级锁)
  • Monitor监视器​:每个对象关联一个Monitor(C++实现)
  • 锁升级路径​:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

2. 锁升级过程(JDK 6+优化)

锁状态特点适用场景
偏向锁仅第一次CAS设置线程ID单线程访问场景
轻量级锁自旋尝试获取锁(避免OS阻塞)低竞争、短临界区
重量级锁通过OS互斥量阻塞线程高竞争、长临界区

⚠️ 锁降级在HotSpot中不会发生


三、显式锁:Lock接口及其实现

1. ReentrantLock(可重入锁)

Lock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区
} finally {
    lock.unlock(); // 必须放在finally块
}

核心特性​:

  • 可重入​:同线程可多次获取锁(记录重入次数)
  • 公平性​:支持公平/非公平模式(默认非公平)
  • 可中断​:lockInterruptibly()支持中断等待
  • 超时机制​:tryLock(long time, TimeUnit unit)

2. ReentrantReadWriteLock(读写锁)

ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();   // 共享锁
Lock writeLock = rwLock.writeLock(); // 排他锁

锁降级示例​:

writeLock.lock();
try {
    // 写操作...
    readLock.lock(); // 锁降级开始
} finally {
    writeLock.unlock(); 
}
try {
    // 读操作(仍受读锁保护)
} finally {
    readLock.unlock();
}

⚠️ ​不支持锁升级​(读锁→写锁会死锁)


四、AQS(AbstractQueuedSynchronizer)

所有显式锁的底层实现基础(JDK 15中60%的并发类依赖AQS)

AQS核心结构

public abstract class AbstractQueuedSynchronizer {
    // 同步状态(volatile int)
    private volatile int state;
    
    // CLH队列(双向链表)
    private transient volatile Node head;
    private transient volatile Node tail;
}

关键操作流程




五、锁的性能优化策略

1. 减小锁粒度

// 错误示例:整个方法加锁
public synchronized void process(Data data) { /*...*/ }

// 改进方案:只锁必要部分
public void process(Data data) {
    preProcess();
    synchronized(this) {
        // 仅同步核心逻辑
    }
    postProcess();
}

2. 锁分段技术(如ConcurrentHashMap)

// JDK 7实现:Segment数组
final Segment<K,V>[] segments;

static final class Segment<K,V> extends ReentrantLock {
    // 每个Segment独立加锁
}

3. ThreadLocal避免锁竞争

private static final ThreadLocal<SimpleDateFormat> formatter = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

六、特殊锁机制详解

1. StampedLock(JDK 8+)

StampedLock sl = new StampedLock();

// 乐观读(不阻塞写线程)
long stamp = sl.tryOptimisticRead();
if (!sl.validate(stamp)) { // 检查期间是否有写操作
    stamp = sl.readLock(); // 升级为悲观读锁
    try {
        // 重新读取
    } finally {
        sl.unlockRead(stamp);
    }
}

三种访问模式​:

模式方法特点
写锁writeLock()独占访问
悲观读锁readLock()共享访问
乐观读tryOptimisticRead()无锁读(需验证)

2. Condition条件变量

Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();

// 生产者
lock.lock();
try {
    while (queue.isFull())
        notEmpty.await(); // 释放锁并等待
    // 生产数据...
    notFull.signal(); // 唤醒消费者
} finally {
    lock.unlock();
}

七、锁的注意事项

  1. 死锁预防​:

    • 避免嵌套锁(lock nesting)
    • 使用tryLock设置超时
    • 统一加锁顺序
  2. 性能监控​:

    # 查看锁竞争情况
    jstack <PID> | grep -A 10 "BLOCKED"
    
    # JFR记录锁事件
    jcmd <PID> JFR.start duration=60s filename=lock.jfr
  3. 最佳实践​:

    - 读多写少 → ReentrantReadWriteLock/StampedLock
    - 写多读少 → ReentrantLock
    - 简单同步 → synchronized
    - 无锁算法 → Atomic*/LongAdder

八、锁与内存语义

操作内存语义等效操作
锁释放将本地内存修改刷新到主内存volatile写
锁获取使本地缓存失效,从主内存读取volatile读

这也是为什么ReentrantLock能保证可见性


九、总结:锁的选择决策树



高级功能包括:

  • 可中断锁
  • 超时获取
  • 公平锁
  • 条件等待

推荐诊断工具​:

  • Arthas:monitor命令监控方法锁竞争
  • JConsole:线程死锁检测
  • Java Flight Recorder:分析锁竞争事件

记住:锁优化永无止境,但无锁才是终极目标。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值