Skip to content

Java 有哪些锁

约 1015 字大约 3 分钟

多线程与并发京东

2025-03-25

⭐ 题目日期:

京东 - 2024/12/26

📝 题解:

在Java中,锁是并发编程的核心工具,用于控制多线程对共享资源的访问。以下是Java中常见的锁机制及其分类:


一、按锁的实现方式分类

1. 内置锁(隐式锁)

  • 关键字:synchronized
    • 用法:修饰方法或代码块。
    • 特性
      • 可重入:同一线程可重复获取同一锁。
      • 非公平锁:默认抢占式获取锁,不保证线程等待顺序。
      • 自动释放:代码块执行完毕或抛出异常时自动释放锁。
    • 示例
      public synchronized void method() { ... }  // 同步方法
      public void block() {
          synchronized (this) { ... }            // 同步代码块
      }

2. 显式锁(java.util.concurrent.locks 包)

  • 核心类:ReentrantLock
    • 特性
      • 可重入:同内置锁。
      • 支持公平性:可指定为公平锁(按等待顺序获取)或非公平锁。
      • 灵活控制:支持尝试获取锁(tryLock())、超时等待(tryLock(timeout))、可中断(lockInterruptibly())。
    • 示例
      ReentrantLock lock = new ReentrantLock(true); // 公平锁
      lock.lock();
      try {
          // 临界区
      } finally {
          lock.unlock();
      }

二、按锁的用途分类

1. 读写锁(ReentrantReadWriteLock

  • 核心思想:读共享,写独占。
    • 读锁(共享锁):允许多个线程同时读。
    • 写锁(排他锁):同一时间仅一个线程可写。
    • 适用场景:读多写少的高并发场景(如缓存)。
    • 示例
      ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
      rwLock.readLock().lock();    // 获取读锁
      rwLock.writeLock().lock();   // 获取写锁

2. 乐观锁(无锁编程)

  • 实现方式:CAS(Compare and Swap)操作,如 AtomicIntegerAtomicReference
    • 原理:通过版本号或值比较实现原子更新。
    • 优点:避免线程阻塞,适用于低竞争场景。
    • 示例
      AtomicInteger counter = new AtomicInteger(0);
      counter.incrementAndGet();   // 原子递增

3. 分布式锁

  • 场景:跨进程或跨机器的资源同步。
  • 实现方式:依赖外部系统(如Redis、ZooKeeper)。
    • Redis实现:使用 SETNX 命令或 RedLock 算法。
    • ZooKeeper实现:通过临时顺序节点实现。

三、按锁的特性分类

1. 可重入锁

  • 定义:线程可重复获取已持有的锁(避免死锁)。
  • 实现synchronizedReentrantLockReentrantReadWriteLock

2. 公平锁 vs 非公平锁

  • 公平锁:按线程等待顺序分配锁(ReentrantLock(true))。
  • 非公平锁:允许线程插队(默认模式,吞吐量更高)。

3. 悲观锁 vs 乐观锁

  • 悲观锁:假设会发生竞争,直接加锁(如 synchronized)。
  • 乐观锁:假设无竞争,通过版本控制实现(如 CAS)。

四、高级锁工具

1. StampedLock(Java 8+)

  • 特点:支持乐观读、读写锁升级/降级。
    • 乐观读:不阻塞写操作,读后需验证数据一致性。
    • 适用场景:读远多于写,且读操作较耗时。
    • 示例
      StampedLock lock = new StampedLock();
      long stamp = lock.tryOptimisticRead(); // 乐观读
      if (!lock.validate(stamp)) {           // 验证是否被修改
          stamp = lock.readLock();           // 退化为悲观读
      }

2. LockSupport

  • 底层工具:直接操作线程的阻塞与唤醒。
    • 核心方法
      • park():阻塞当前线程。
      • unpark(Thread):唤醒指定线程。
    • 用途:实现自定义同步器(如AQS)。

3. Condition

  • 与显式锁配合:替代 Object.wait()/notify(),提供更灵活的等待/通知机制。
    • 示例
      ReentrantLock lock = new ReentrantLock();
      Condition condition = lock.newCondition();
      lock.lock();
      try {
          condition.await();      // 等待
          condition.signal();      // 唤醒
      } finally {
          lock.unlock();
      }

五、锁的性能与选择建议

场景推荐锁类型理由
简单同步synchronized代码简洁,JVM自动优化(如锁升级)。
高竞争、灵活控制ReentrantLock支持超时、中断、公平性。
读多写少ReentrantReadWriteLock提升读并发性能。
读远多于写,长读操作StampedLock(乐观读)减少写线程阻塞。
无锁编程(低竞争)CAS(如 AtomicInteger避免线程上下文切换。

六、锁的注意事项

  1. 死锁预防:避免嵌套锁、使用超时机制。
  2. 锁粒度:尽量减小锁的作用范围(如锁代码块而非方法)。
  3. 性能监控:关注锁竞争(如JVM参数 -XX:+PrintLockStatistics)。

通过合理选择锁机制,可以显著提升多线程程序的性能和稳定性。理解每种锁的特性和适用场景是关键!