博学谷 Java并发编程原理精讲 百度网盘下载

68 阅读3分钟

Java锁体系经历了从基础同步机制到高性能读写锁的演进,核心锁类型包括synchronized、ReentrantLock、ReentrantReadWriteLock和StampedLock,其演进路径体现了对高并发场景下性能与灵活性的持续优化。以下是具体分析:

一、演进路径:从基础到高性能

  1. synchronized(内置锁)

    • 定位:Java最早期的同步机制,通过对象头中的Monitor实现基础互斥。
    • 特点
      • 简单易用,语法层面支持(方法或代码块加锁)。
      • 自动释放锁,避免死锁风险。
      • 性能优化:JDK 6+引入锁升级(无锁→偏向锁→轻量级锁→重量级锁)。
    • 局限
      • 灵活性差,无法中断等待线程或设置超时。
      • 高并发下性能下降明显(竞争激烈时转为重量级锁)。
    • 适用场景:简单同步逻辑,代码可读性要求高的场景。
  2. ReentrantLock(显式锁)

    • 定位:基于AQS(AbstractQueuedSynchronizer)实现的灵活锁机制。
    • 特点
      • 可重入、支持公平/非公平模式。
      • 提供tryLock()(超时获取锁)、lockInterruptibly()(可中断获取锁)。
      • 需手动释放锁,必须在finally块中调用unlock()
    • 优势
      • 精准控制线程通信(通过Condition)。
      • 避免死锁(支持超时和中断)。
    • 局限
      • 代码复杂度高于synchronized
    • 适用场景:需要可中断、公平性、条件变量的复杂同步场景。
  3. ReentrantReadWriteLock(读写锁)

    • 定位:分离读写操作的锁机制,优化读多写少场景。
    • 特点
      • 读锁(共享锁)与写锁(排他锁)分离。
      • 读操作可并发,写操作独占。
    • 优势
      • 读多写少时性能优于ReentrantLock
    • 局限
      • 读写切换效率低,写线程可能饥饿。
      • 不支持锁升级(读锁→写锁会死锁)。
    • 适用场景:读操作远多于写操作的场景(如缓存系统)。
  4. StampedLock(Java 8+)

    • 定位:针对读多写少场景的高性能锁,支持乐观读。
    • 特点
      • 三种模式:
        • 写锁:独占,类似ReentrantLock
        • 悲观读锁:共享,类似传统读锁。
        • 乐观读:无锁读取,通过版本戳(Stamp)验证数据一致性。
      • 非重入,同一线程重复获取锁会死锁。
      • 支持锁转换(如读锁→写锁)。
    • 优势
      • 乐观读减少锁争用,提升吞吐量。
      • 避免锁升级复杂性。
    • 局限
      • 使用复杂,需正确处理戳(Stamp)和验证。
    • 适用场景:高并发、读多写少的性能敏感场景(如金融数据读取)。

二、选择决策树:如何根据场景选型

  1. 简单同步需求

    • 选型synchronized
    • 理由:代码简洁,JVM已做大量优化(如锁升级)。
    • 示例:单方法同步、低竞争场景。
  2. 复杂同步需求(需中断、超时、公平性)

    • 选型ReentrantLock
    • 理由:支持灵活控制,避免死锁。
    • 示例:资源池管理、任务调度。
  3. 读多写少场景(传统读写分离)

    • 选型ReentrantReadWriteLock
    • 理由:读操作并发,写操作独占。
    • 示例:缓存系统、配置数据读取。
  4. 高并发读多写少场景(追求极致性能)

    • 选型StampedLock
    • 理由:乐观读减少锁争用,支持锁转换。
    • 示例:金融数据读取、实时分析系统。

三、性能对比:高并发下的表现

  • synchronized vs ReentrantLock

    • 竞争不激烈时,synchronized性能更优(JVM优化)。
    • 竞争激烈时,ReentrantLock性能更稳定(AQS队列管理)。
  • ReentrantReadWriteLock vs StampedLock

    • 读操作占比高时,StampedLock的乐观读显著提升吞吐量。
    • 写操作频繁时,两者性能接近,但StampedLock需注意锁转换开销。

四、最佳实践与注意事项

  1. 死锁预防

    • 避免嵌套锁(如lockA内获取lockB)。
    • 使用tryLock设置超时,统一加锁顺序。
  2. 性能监控

    • 通过jstack <PID> | grep -A10 "BLOCKED"查看锁竞争。
    • 使用JFR(Java Flight Recorder)记录锁事件。
  3. 无锁目标

    • 优先使用Atomic类(如AtomicInteger)或LongAdder(高竞争场景)。
    • 考虑无锁数据结构(如ConcurrentHashMap)。