云端行笔
发布于 2026-02-01 / 5 阅读
0
0

Java锁机制深度解析:从synchronized原理到Lock实战

摘要:本文深入解析Java锁机制,涵盖synchronized的monitor原理与锁升级过程、Lock的AQS实现原理,以及两者的演进对比与实战应用。适合需要深入理解Java并发编程、解决线程安全问题的开发者。

前言:为什么深入理解锁这么重要

早些年,曾接手过一个核心交易系统的性能优化任务。上线后用户反馈下单时偶尔会卡顿几秒钟,排查后发现是锁竞争导致的。系统用的是最原始的synchronized,在高并发场景下大量线程阻塞等待,严重拖慢了响应时间。

当时我对锁的理解停留在"加上synchronized就能保证线程安全"的层面。深入排查后才发现:锁不是简单的开关,而是有多种状态、多种实现、需要根据场景选择的复杂机制。合理使用锁,可以既保证安全又保证性能;不合理使用,可能成为系统瓶颈。

这篇文章会系统梳理Java锁的核心原理、演进历程、实战经验,帮你从"知道有锁"进阶到"真正会用锁"。

一、线程安全与锁的本质

1.1 为什么需要锁

多线程环境下,多个线程同时访问共享资源,可能出现数据不一致的问题。

经典例子:计数器

  public class Counter {
      private int count = 0;
  
      public void increment() {
          count++;  // 看起来是一步操作,实际是三步
      }
  
      public int getCount() {
          return count;
      }
  }
  
  // 多线程并发调用
  Counter counter = new Counter();
  for (int i = 0; i < 1000; i++) {
      new Thread(() -> counter.increment()).start();
  }
  // 结果可能小于1000

count++的字节码:

  1. getfield #2  // 读取count值到操作数栈
  2. iinc #2 1    // 将count值加1
  3. putfield #2  // 将新值写入count

如果两个线程同时执行:

  • 线程A读取count=0

  • 线程B读取count=0

  • 线程A加1,count=1

  • 线程B加1,count=1(而不是预期的2)

  • 最终结果丢失了一次增量

解决方法:加锁

锁的核心作用:保证同一时刻只有一个线程能执行临界区代码

1.2 锁的核心概念

互斥性:同一时刻只有一个线程持有锁

可见性:锁释放前对共享变量的修改,在锁获取后对其他线程可见

可重入性:同一个线程可以多次获取同一把锁(避免死锁)

  线程A获取锁 → 执行代码 → 再次获取锁(可重入)→ 执行代码 → 释放锁 → 释放锁

公平性

  • 公平锁:按照请求顺序获取锁

  • 非公平锁:允许插队,可能提高吞吐量但可能导致某些线程长时间等待

二、synchronized深入解析

2.1 synchronized的基本用法

三种使用方式

  // 1. 同步实例方法:锁当前对象
  public synchronized void method() {
      // 临界区
  }
  
  // 2. 同步静态方法:锁当前类的Class对象
  public static synchronized void staticMethod() {
      // 临界区
  }
  
  // 3. 同步代码块:锁指定对象
  public void method() {
      synchronized (this) {  // 锁当前对象
          // 临界区
      }
  
      synchronized (lockObject) {  // 锁指定对象
          // 临界区
      }
  }

2.2 synchronized的底层原理

synchronized的实现依赖于JVM的**Monitor(监视器锁)**机制。

对象头结构

每个Java对象在堆内存中都有一个对象头,其中包含与锁相关的信息:

Mark Word在不同锁状态下的结构:

2.3 Monitor机制

Monitor是synchronized的重量级实现,依赖操作系统互斥量。

Monitor结构

工作流程

Monitor的代价

Monitor依赖操作系统互斥量,涉及:

  • 用户态与内核态切换

  • 线程阻塞与唤醒

  • 系统调用开销

这些操作成本高,所以传统synchronized被称为"重量级锁"。

2.4 锁升级(锁膨胀)过程

为了减少重量级锁的性能开销,JVM引入了锁升级机制:从偏向锁到轻量级锁再到重量级锁,逐步升级。

完整升级流程

(1)偏向锁

原理:锁偏向第一个获取它的线程,后续该线程获取锁无需同步。

适用场景:只有一个线程反复获取锁(如单线程访问同步块)。

获取过程

  1. 检查Mark Word是否为偏向锁状态(biased_lock=1)
  2. 检查线程ID是否为当前线程
     → 如果是,直接进入临界区(无需CAS)
     → 如果不是,尝试CAS替换线程ID
  3. CAS成功,获得偏向锁
  4. CAS失败,说明有竞争,撤销偏向锁

撤销过程

  1. 等待全局安全点(没有正在执行的字节码)
  2. 暂停拥有偏向锁的线程
  3. 检查锁对象是否被锁定
     → 如果未被锁定,恢复为无锁状态
     → 如果被锁定,升级为轻量级锁
  4. 恢复线程执行

JDK 15的变化

偏向锁在JDK 15中被废弃并默认禁用,原因:

  • 复杂度高,维护困难

  • 实际收益有限(现代应用多线程竞争普遍)

  • 与其他优化(如逃逸分析)冲突

可以通过参数启用:-XX:+UseBiasedLocking(JDK 15之前默认开启)

(2)轻量级锁

原理:用CAS在栈帧中创建Lock Record,将Mark Word指向Lock Record。

适用场景:多个线程交替执行同步块,竞争不激烈。

获取过程

  1. 在当前线程栈帧中创建Lock Record
  2. 用CAS将Mark Word替换为Lock Record的指针
  3. CAS成功,获得轻量级锁
  4. CAS失败,说明有竞争
     → 如果是当前线程,可重入(添加一条Lock Record)
     → 如果是其他线程,自旋等待或膨胀为重量级锁

自旋锁

轻量级锁失败后,JVM会让当前线程"自旋"等待(循环尝试获取锁),而不是立即阻塞。

  while (尝试获取锁) {
      if (成功) break;
      自旋次数++;
      if (自旋次数达到阈值) {
          膨胀为重量级锁;
          break;
      }
  }

自旋的好处:避免线程阻塞的开销,适合锁被占用时间很短的情况。

自旋的坏处:如果锁被占用时间长,自旋会浪费CPU。

JVM自适应自旋:根据历史自旋成功率动态调整自旋次数。

(3)重量级锁

原理:使用Monitor,线程真正阻塞等待。

适用场景:竞争激烈,锁被占用时间长。

膨胀触发条件

  • 自旋失败次数达到阈值

  • 多个线程同时竞争

膨胀过程

  1. 创建Monitor对象
  2. 将Mark Word替换为Monitor指针
  3. 竞争失败的线程进入Monitor的Entry Set阻塞

2.5 锁降级

锁可以从低级升级到高级,但不会从高级降级到低级。

原因:锁降级需要全局安全点,成本高;且一旦竞争激烈,后续大概率还是竞争激烈,降级意义不大。

唯一的例外:GC过程中的锁降级(SafePoint期间)。

2.6 synchronized的优化历程

锁消除

JVM检测到不可能被共享的对象,自动消除其同步锁。

  public String concat(String s1, String s2) {
      StringBuffer sb = new StringBuffer();  // sb是局部变量,不会被共享
      sb.append(s1);  // append是synchronized方法
      sb.append(s2);
      return sb.toString();
  }
  // JVM可以消除sb的锁,因为sb不会被其他线程访问

锁粗化

多次连续加锁解锁,JVM合并为一次加锁解锁。

  for (int i = 0; i < 100; i++) {
      synchronized (lock) {
          // 临界区
      }
  }
  
  // 优化为:
  synchronized (lock) {
      for (int i = 0; i < 100; i++) {
          // 临界区
      }
  }

三、Lock深入解析

3.1 Lock接口

synchronized是隐式锁(JVM管理),Lock是显式锁(代码控制)。

public interface Lock {
    void lock();                // 获取锁
    void lockInterruptibly();   // 可响应中断地获取锁
    boolean tryLock();          // 尝试获取锁(立即返回)
    boolean tryLock(long time, TimeUnit unit);  // 尝试获取锁(超时返回)
    void unlock();              // 释放锁
    Condition newCondition();   // 创建条件变量
}

核心优势

  • 可中断:等待锁时可响应中断

  • 可超时:尝试获取锁有超时机制

  • 公平性选择:可指定公平或非公平

  • 条件变量:支持多个等待队列

3.2 ReentrantLock详解

ReentrantLock是最常用的Lock实现,基于AQS(AbstractQueuedSynchronizer)

基本用法

  public class Counter {
      private final Lock lock = new ReentrantLock();
      private int count = 0;
  
      public void increment() {
          lock.lock();  // 获取锁
          try {
              count++;
          } finally {
              lock.unlock();  // 必须在finally中释放锁
          }
      }
  
      // 可中断获取
      public void incrementInterruptibly() throws InterruptedException {
          lock.lockInterruptibly();
          try {
              count++;
          } finally {
              lock.unlock();
          }
      }
  
      // 尝试获取(非阻塞)
      public boolean tryIncrement() {
          if (lock.tryLock()) {
              try {
                  count++;
                  return true;
              } finally {
                  lock.unlock();
              }
          }
          return false;  // 获取锁失败
      }
  
      // 尝试获取(超时)
      public boolean tryIncrementTimeout() throws InterruptedException {
          if (lock.tryLock(1, TimeUnit.SECONDS)) {
              try {
                  count++;
                  return true;
              } finally {
                  lock.unlock();
              }
          }
          return false;  // 超时未获取到锁
      }
  }

3.3 AQS核心原理

AQS是Java并发包的核心框架,ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch等都基于AQS。

AQS结构

public abstract class AbstractQueuedSynchronizer {
    // 核心状态变量
    private volatile int state;  // 同步状态(0=未锁定,>0=锁定次数)

    // 等待队列
    private transient volatile Node head;  // 队列头
    private transient volatile Node tail;  // 队列尾

    // 队列节点
    static final class Node {
        volatile Thread thread;     // 等待的线程
        volatile int waitStatus;    // 等待状态
        volatile Node prev;         // 前驱节点
        volatile Node next;         // 后继节点
        Node nextWaiter;            // 条件队列中的下一个节点
    }
}

核心思想

  • state表示锁状态

  • 用CAS修改state获取锁

  • 获取失败的线程进入CLH队列等待

  • 用LockSupport.park()阻塞线程,LockSupport.unpark()唤醒线程

获取锁流程

公平锁与非公平锁

  // 非公平锁(默认)
  public ReentrantLock() {
      sync = new NonfairSync();
  }
  
  // 公平锁
  public ReentrantLock(boolean fair) {
      sync = fair ? new FairSync() : new NonfairSync();
  }

非公平锁获取

  final void lock() {
      // 直接尝试CAS获取,不检查队列
      if (compareAndSetState(0, 1))
          setExclusiveOwnerThread(Thread.currentThread());
      else
          acquire(1);  // CAS失败,进入获取流程
  }

公平锁获取

  final void lock() {
      acquire(1);  // 不直接CAS,先检查队列
  }
  
  protected final boolean tryAcquire(int acquires) {
      // 检查是否有前驱节点等待
      if (hasQueuedPredecessors())
          return false;  // 有等待的线程,不获取
      // 无等待线程,尝试CAS
      return compareAndSetState(0, acquires);
  }

对比

  • 非公平锁:可能插队,吞吐量高,但可能导致某些线程长时间等待

  • 公平锁:按顺序获取,公平性保证,但吞吐量略低

3.4 Condition条件变量

Condition是Lock的高级功能,类似synchronized的wait/notify,但更灵活。

  public class BoundedQueue<T> {
      private final Lock lock = new ReentrantLock();
      private final Condition notFull = lock.newCondition();
      private final Condition notEmpty = lock.newCondition();
  
      private final Object[] items;
      private int putIndex, takeIndex, count;
  
      public BoundedQueue(int capacity) {
          items = new Object[capacity];
      }
  
      public void put(T item) throws InterruptedException {
          lock.lock();
          try {
              while (count == items.length) {
                  notFull.await();  // 队列满,等待
              }
              items[putIndex] = item;
              putIndex = (putIndex + 1) % items.length;
              count++;
              notEmpty.signal();  // 通知消费者
          } finally {
              lock.unlock();
          }
      }
  
      public T take() throws InterruptedException {
          lock.lock();
          try {
              while (count == 0) {
                  notEmpty.await();  // 队列空,等待
              }
              T item = (T) items[takeIndex];
              items[takeIndex] = null;
              takeIndex = (takeIndex + 1) % items.length;
              count--;
              notFull.signal();  // 通知生产者
              return item;
          } finally {
              lock.unlock();
          }
      }
  }

Condition vs wait/notify

3.5 ReentrantReadWriteLock

读写锁:读读共享、读写互斥、写写互斥。

  public class Cache {
      private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
      private final Lock readLock = rwLock.readLock();
      private final Lock writeLock = rwLock.writeLock();
  
      private Map<String, Object> cache = new HashMap<>();
  
      public Object get(String key) {
          readLock.lock();
          try {
              return cache.get(key);
          } finally {
              readLock.unlock();
          }
      }
  
      public void put(String key, Object value) {
          writeLock.lock();
          try {
              cache.put(key, value);
          } finally {
              writeLock.unlock();
          }
      }
  
      public void clear() {
          writeLock.lock();
          try {
              cache.clear();
          } finally {
              writeLock.unlock();
          }
      }
  }

原理

AQS的state分为两部分:

  • 高16位:共享锁(读锁)计数

  • 低16位:独占锁(写锁)计数

  state = (sharedCount << 16) | exclusiveCount
  
  读锁:state & 0xFFFF0000
  写锁:state & 0x0000FFFF

锁降级

写锁可以降级为读锁(持有写锁时获取读锁),但不能升级(持有读锁时获取写锁会死锁)。

  public void processData() {
      writeLock.lock();
      try {
          // 更新数据
          updateData();
  
          // 降级为读锁,继续读取数据(其他线程不能写)
          readLock.lock();
          try {
              writeLock.unlock();  // 先释放写锁(在持有读锁的情况下)
              // 现在持有读锁,可以安全读取
              readProcessedData();
          } finally {
              readLock.unlock();
          }
      } finally {
          // 注意:此时写锁已释放,这段代码实际不会执行
      }
  }

3.6 StampedLock

JDK 8引入的乐观读写锁,性能更好但使用复杂。

  public class Point {
      private final StampedLock sl = new StampedLock();
      private double x, y;
  
      // 写锁
      public void move(double deltaX, double deltaY) {
          long stamp = sl.writeLock();  // 获取写锁戳
          try {
              x += deltaX;
              y += deltaY;
          } finally {
              sl.unlockWrite(stamp);  // 释放写锁(需要戳)
          }
      }
  
      // 读锁
      public double distanceFromOrigin() {
          long stamp = sl.readLock();  // 获取读锁戳
          try {
              return Math.sqrt(x * x + y * y);
          } finally {
              sl.unlockRead(stamp);
          }
      }
  
      // 乐观读(无锁读取,验证版本)
      public double optimisticRead() {
          long stamp = sl.tryOptimisticRead();  // 获取乐观读戳
          double currentX = x, currentY = y;
  
          // 验证期间是否有写操作
          if (!sl.validate(stamp)) {
              // 有写操作,乐观读失败,升级为读锁
              stamp = sl.readLock();
              try {
                  currentX = x;
                  currentY = y;
              } finally {
                  sl.unlockRead(stamp);
              }
          }
          return Math.sqrt(currentX * currentX + currentY * currentY);
      }
  }

特点

  • 乐观读:不加锁读取,验证期间是否有写操作

  • 性能更好:读操作大部分情况下不需要获取锁

  • 使用复杂:需要手动验证、升级锁

四、synchronized与Lock对比

4.1 功能对比

4.2 性能对比

JDK 1.6之前:Lock性能优于synchronized(synchronized只有重量级锁)

JDK 1.6之后:synchronized优化后,两者性能差距缩小

实测对比(高并发场景):

4.3 使用选择

推荐synchronized的场景

  • 简单同步需求

  • 不需要高级功能(中断、超时、公平锁)

  • 代码可读性优先(自动释放锁)

推荐Lock的场景

  • 需要中断获取锁

  • 需要超时机制

  • 需要公平锁

  • 需要多个条件变量

  • 需要读写锁

  • 复杂并发控制

五、锁实战:问题排查与优化

5.1 死锁问题

经典死锁场景

  public class DeadlockDemo {
      private final Object lockA = new Object();
      private final Object lockB = new Object();
  
      public void methodA() {
          synchronized (lockA) {
              System.out.println("Thread A holds lockA");
              try { Thread.sleep(100); } catch (InterruptedException e) {}
  
              synchronized (lockB) {
                  System.out.println("Thread A holds lockA and lockB");
              }
          }
      }
  
      public void methodB() {
          synchronized (lockB) {
              System.out.println("Thread B holds lockB");
              try { Thread.sleep(100); } catch (InterruptedException e) {}
  
              synchronized (lockA) {
                  System.out.println("Thread B holds lockB and lockA");
              }
          }
      }
  
      public static void main(String[] args) {
          DeadlockDemo demo = new DeadlockDemo();
  
          new Thread(demo::methodA).start();
          new Thread(demo::methodB).start();
      }
  }

死锁排查

  # 1. 查看线程栈
  jstack <pid>
  
  # 输出:
  Found one Java-level deadlock:
  =============================
  "Thread-1":
    waiting to lock monitor 0x... (object 0x..., a java.lang.Object)
    which is held by "Thread-0"
  "Thread-0":
    waiting to lock monitor 0x... (object 0x..., a java.lang.Object)
    which is held by "Thread-1"

解决方案

  // 1. 统一加锁顺序
  public void methodA() {
      synchronized (lockA) {
          synchronized (lockB) {
              // 临界区
          }
      }
  }
  
  public void methodB() {
      synchronized (lockA) {  // 先获取lockA
          synchronized (lockB) {
              // 临界区
          }
      }
  }
  
  // 2. 使用Lock的超时机制
  public void methodA() throws InterruptedException {
      if (lockA.tryLock(1, TimeUnit.SECONDS)) {
          try {
              if (lockB.tryLock(1, TimeUnit.SECONDS)) {
                  try {
                      // 临界区
                  } finally {
                      lockB.unlock();
                  }
              }
          } finally {
              lockA.unlock();
          }
      }
  }
  
  // 3. 使用Lock的一致性哈希(避免嵌套)
  // 按对象哈希值顺序获取锁

5.2 锁竞争问题

现象:高并发时系统响应变慢,CPU利用率低。

诊断

  # 查看线程状态
  jstack <pid> | grep BLOCKED
  
  # 输出:
  "Thread-10" #10 prio=5 os_prio=0 tid=0x... nid=0x... waiting for monitor entry [0x...]
    waiting to lock <0x...> (a java.lang.Object)
    which is held by "Thread-0"

大量BLOCKED状态线程 = 锁竞争严重。

解决方案

1. 减小锁粒度

  // 原方案:全局锁
  public class Cache {
      private final Object lock = new Object();
      private Map<String, Object> data = new HashMap<>();
  
      public Object get(String key) {
          synchronized (lock) {
              return data.get(key);
          }
      }
  }
  
  // 优化:分段锁
  public class Cache {
      private final Object[] locks = new Object[16];
      private final Map<String, Object>[] segments = new Map[16];
  
      public Cache() {
          for (int i = 0; i < 16; i++) {
              locks[i] = new Object();
              segments[i] = new HashMap<>();
          }
      }
  
      private int segmentIndex(String key) {
          return key.hashCode() % 16;
      }
  
      public Object get(String key) {
          int index = segmentIndex(key);
          synchronized (locks[index]) {
              return segments[index].get(key);
          }
      }
  }

2. 使用读写锁

读多写少场景,使用ReadWriteLock:

  public class Cache {
      private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
      private Map<String, Object> data = new HashMap<>();
  
      public Object get(String key) {
          rwLock.readLock().lock();
          try {
              return data.get(key);
          } finally {
              rwLock.readLock().unlock();
          }
      }
  
      public void put(String key, Object value) {
          rwLock.writeLock().lock();
          try {
              data.put(key, value);
          } finally {
              rwLock.writeLock().unlock();
          }
      }
  }

3. 使用并发容器

  // ConcurrentHashMap:分段锁(JDK 7)或CAS+synchronized(JDK 8+)
  ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
  
  // 无锁操作
  cache.put(key, value);  // 内部已优化
  cache.get(key);

4. 使用无锁方案

  // AtomicLong:CAS无锁
  AtomicLong counter = new AtomicLong(0);
  counter.incrementAndGet();  // 无阻塞
  
  // LongAdder:分段累加(高并发更好)
  LongAdder adder = new LongAdder();
  adder.increment();

5.3 锁优化实战案例

案例1:订单系统锁优化

背景:订单创建流程,用synchronized保护整个流程,高峰期响应慢。

问题分析

  // 原代码
  public Order createOrder(OrderRequest request) {
      synchronized (this) {
          // 1. 检查库存(耗时100ms)
          checkInventory(request);
  
          // 2. 计算价格(耗时50ms)
          calculatePrice(request);
  
          // 3. 创建订单(耗时50ms)
          Order order = saveOrder(request);
  
          // 4. 发送通知(耗时100ms)
          sendNotification(order);
  
          return order;
      }
  }

锁住了整个流程,包括不需要同步的步骤。

优化方案

  // 只锁需要同步的部分
  public Order createOrder(OrderRequest request) {
      // 1. 检查库存(可并行,用乐观锁)
      Inventory inventory = checkInventoryOptimistic(request);
  
      // 2. 计算价格(无需锁)
      Price price = calculatePrice(request);
  
      // 3. 创建订单(需要锁)
      Order order;
      synchronized (orderLock) {
          order = saveOrder(request, inventory, price);
      }
  
      // 4. 发送通知(异步,不阻塞)
      notificationService.sendAsync(order);
  
      return order;
  }
  
  // 库存乐观锁
  public Inventory checkInventoryOptimistic(OrderRequest request) {
      while (true) {
          Inventory inv = inventoryDao.get(request.getProductId());
          if (inv.getStock() >= request.getQuantity()) {
              // CAS更新库存
              boolean success = inventoryDao.casUpdate(
                  inv.getId(),
                  inv.getVersion(),
                  inv.getStock() - request.getQuantity()
              );
              if (success) {
                  return inv;
              }
          } else {
              throw new InsufficientStockException();
          }
      }
  }

效果:响应时间从300ms降到150ms,吞吐量提升50%。

案例2:缓存系统锁优化

背景:缓存系统,读多写少,synchronized导致读取性能下降。

优化

  // 原方案
  public class Cache {
      private final Object lock = new Object();
      private Map<String, Object> cache = new HashMap<>();
  
      public Object get(String key) {
          synchronized (lock) {
              return cache.get(key);
          }
      }
  
      public void put(String key, Object value) {
          synchronized (lock) {
              cache.put(key, value);
          }
      }
  }
  
  // 优化1:读写锁
  public class Cache {
      private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
      private Map<String, Object> cache = new HashMap<>();
  
      public Object get(String key) {
          rwLock.readLock().lock();
          try {
              return cache.get(key);
          } finally {
              rwLock.readLock().unlock();
          }
      }
  }
  
  // 优化2:StampedLock(性能更好)
  public class Cache {
      private final StampedLock sl = new StampedLock();
      private Map<String, Object> cache = new HashMap<>();
  
      public Object get(String key) {
          long stamp = sl.tryOptimisticRead();
          Object value = cache.get(key);
          if (!sl.validate(stamp)) {
              stamp = sl.readLock();
              try {
                  value = cache.get(key);
              } finally {
                  sl.unlockRead(stamp);
              }
          }
          return value;
      }
  }
  
  // 优化3:ConcurrentHashMap(最简单)
  public class Cache {
      private ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
  
      public Object get(String key) {
          return cache.get(key);  // 无锁读取
      }
  
      public void put(String key, Object value) {
          cache.put(key, value);  // 内部优化
      }
  }

效果:读取性能提升10倍(ConcurrentHashMap方案)。

5.4 锁使用最佳实践

原则1:锁范围最小化

  // 不推荐
  public void method() {
      synchronized (lock) {
          // 大量代码,大部分不需要同步
          prepareData();  // 无需锁
          processData();  // 需要锁
          saveResult();   // 需要锁
          sendNotification();  // 无需锁
      }
  }
  
  // 推荐
  public void method() {
      prepareData();
  
      synchronized (lock) {
          processData();
          saveResult();
      }
  
      sendNotification();
  }

原则2:避免嵌套锁

  // 不推荐(可能死锁)
  public void methodA() {
      synchronized (lockA) {
          synchronized (lockB) {
              // 临界区
          }
      }
  }
  
  // 推荐(单一锁)
  public void methodA() {
      synchronized (lockA) {
          // 用其他方式实现功能
      }
  }

原则3:用并发容器代替手动加锁

  // 不推荐
  Map<String, Integer> map = new HashMap<>();
  synchronized (lock) {
      map.put(key, value);
  }
  
  // 推荐
  ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
  map.put(key, value);  // 内部已优化

原则4:正确释放锁

  // 不推荐(异常时锁不释放)
  public void method() {
      lock.lock();
      // 可能抛异常
      doSomething();
      lock.unlock();  // 异常后不会执行
  }
  
  // 推荐
  public void method() {
      lock.lock();
      try {
          doSomething();
      } finally {
          lock.unlock();  // 总是释放
      }
  }

原则5:wait/notify在循环中使用

  // 不推荐(虚假唤醒)
  synchronized (lock) {
      if (condition) {
          lock.wait();
      }
      // 条件可能已改变
  }
  
  // 推荐
  synchronized (lock) {
      while (!condition) {
          lock.wait();
      }
      // 条件一定满足
  }

六、常见问题

Q1:synchronized和Lock怎么选?

简单场景用synchronized:自动释放锁、代码简洁。 高级需求用Lock:中断、超时、公平锁、多条件变量、读写锁。

Q2:偏向锁为什么被废弃?

偏向锁复杂度高、维护困难,且现代应用多线程竞争普遍,收益有限。JDK 15默认禁用。

Q3:什么情况锁会膨胀?

竞争激烈时:自旋失败次数达到阈值、多个线程同时竞争。从轻量级锁膨胀到重量级锁。

Q4:CAS是什么?

Compare And Swap,无锁更新:

  if (当前值 == 期望值) {
      更新为新值;
      return 成功;
  } else {
      return 失败;
  }

是乐观锁的核心,AQS依赖CAS。

Q5:如何判断是否需要加锁?

看是否有共享变量被多个线程访问:

  • 局部变量:不需要锁(线程私有)

  • ThreadLocal:不需要锁(线程私有)

  • 共享变量:需要锁或使用并发容器

Q6:锁竞争严重怎么排查?

jstack <pid> | grep BLOCKED  # 查看阻塞线程
jstack <pid>  # 查看完整线程栈

大量BLOCKED线程 = 锁竞争严重。

七、总结

Java锁机制是并发编程的核心,理解它需要从三个层面:

原理层面

  • synchronized:Monitor机制、对象头、锁升级

  • Lock:CAS、AQS、等待队列

演进层面

  • synchronized:从重量级锁到偏向锁、轻量级锁、自适应自旋

  • Lock:从简单互斥锁到可重入锁、读写锁、StampedLock

实战层面

  • 选择合适的锁类型

  • 优化锁的范围和粒度

  • 排查和解决锁相关问题

实际项目中,锁使用的关键是:

  • 确有必要才加锁(避免过度同步)

  • 锁范围最小化

  • 用并发容器代替手动锁

  • 监控锁竞争,及时优化

锁不是银弹,合理使用才能既保证安全又保证性能。

参考资料:


评论