Java 并发锁核心解析:synchronized vs ReentrantLock(含底层原理)

在 Java 并发体系中,synchronizedReentrantLock 是最核心的两种锁实现。面试与实际开发中都高频出现。本文从底层实现机制出发,系统梳理二者原理,并在最后进行简洁对比。

目录


一、synchronized 介绍及底层原理

1.1 基本介绍

synchronized 是 Java 内置的关键字锁,也称为监视器锁(Monitor Lock),属于 JVM 层面的实现。

它用于保证:

  • 互斥访问(同一时刻只有一个线程执行临界区)
  • 可见性(释放锁时刷新主内存)

使用方式:

id
1
2
3
synchronized (obj) {
// 临界区
}

或方法级别:

id
1
public synchronized void test() {}

1.2 底层实现机制

synchronized 的底层依赖 JVM 的对象头与 Monitor(管程机制)。

1.2.1 对象头(Mark Word)

每个 Java 对象都包含对象头,其中 Mark Word 用于存储锁信息:

  • 无锁状态
  • 偏向锁线程ID
  • 轻量级锁指针
  • 重量级锁 Monitor 指针

1.2.2 锁升级机制

synchronized 在 JVM 中会根据竞争情况自动升级:

id
1
无锁 → 偏向锁 → 轻量级锁 → 重量级锁

偏向锁

  • 适用于无竞争场景
  • 记录第一次获取锁的线程

轻量级锁

  • 使用 CAS 自旋竞争
  • 避免线程阻塞

重量级锁

  • 竞争激烈时升级
  • 依赖 OS Mutex
  • 线程进入阻塞状态

1.2.3 Monitor(监视器)

当升级为重量级锁时,会使用 Monitor:

  • Entry List:竞争队列
  • Wait Set:等待队列
  • Owner:持有锁的线程

👉 本质:线程通过 Monitor 实现阻塞与唤醒。

1.3 synchronized 特点总结

  • JVM 内置实现
  • 自动加锁/释放锁
  • 支持锁升级优化
  • 不可中断
  • 不支持超时获取

二、ReentrantLock 介绍及底层原理

2.1 基本介绍

ReentrantLock 是 JUC 提供的可重入锁类,属于 JDK 层实现。

相比 synchronized,它提供更多可控能力:

  • 可中断
  • 可超时
  • 公平/非公平锁
  • 多条件变量

2.2 底层核心:AQS

ReentrantLock 的核心依赖:

👉 AbstractQueuedSynchronizer

AQS 是一个同步框架,用于构建锁与同步器。

2.3 AQS 核心结构

2.3.1 state(同步状态)

id
1
volatile int state;

state 是 AQS 的核心变量,但它不是锁本身,而是同步状态的抽象:

场景 state 含义
ReentrantLock 重入次数
Semaphore 资源数量
CountDownLatch 计数器

2.3.2 CLH 队列(等待队列)

AQS 维护一个 FIFO 双向队列:

id
1
Head → Node → Node → Node

每个 Node 表示一个线程:

id
1
2
3
Thread thread;
Node prev;
Node next;

用于管理阻塞线程的排队顺序。

2.3.3 CAS + LockSupport

  • CAS:用于抢占 state
  • LockSupport:用于阻塞/唤醒线程
id
1
2
LockSupport.park();
LockSupport.unpark();

2.4 ReentrantLock 加锁流程

2.4.1 Step 1:CAS 尝试加锁

id
1
compareAndSetState(0, 1)

成功 → 当前线程获得锁

2.4.2 Step 2:失败 → 入队

线程加入 AQS 队列尾部:

id
1
Thread → CLH Queue

2.4.3 Step 3:自旋 + 阻塞

id
1
2
3
4
5
6
7
for (;;) {
if (前驱是head && CAS成功) {
获取锁
} else {
LockSupport.park();
}
}

本质:先竞争,再阻塞

2.5 ReentrantLock 解锁流程

2.5.1 Step 1:state 减 1

id
1
state--

2.5.2 Step 2:完全释放

当 state == 0:

  • 释放锁持有线程
  • 设置 owner = null

2.5.3 Step 3:唤醒后继线程

id
1
LockSupport.unpark(nextThread);

2.6 可重入机制

id
1
2
3
if (currentThread == ownerThread) {
state++;
}

释放时:

id
1
state--;

同一线程可重复获取锁,不会阻塞。

2.7 Condition(条件变量)

ReentrantLock 支持多个条件队列:

id
1
2
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();

机制:

  • await() → 进入 Condition 队列
  • signal() → 转移到 AQS 队列

2.8 公平 vs 非公平

非公平锁(默认)

  • CAS直接抢锁
  • 允许插队

公平锁

id
1
if (hasQueuedPredecessors()) return false;

按队列顺序执行


三、synchronized vs ReentrantLock 对比总结

  • synchronizedReentrantLock的对比主要在于以下四个方面:
    1. 可重入性:synchronized是可重入锁,ReentrantLock也是可重入锁。
    2. 依赖:synchronized依赖于JVM,而ReentrantLock依赖于API。
    3. 功能:ReentrantLock相比synchronized提供了更多的功能,主要有四点:
      1. 等待可中断
      2. 可实现公平锁
      3. 可实现选择性通知
      4. 支持超时
    4. 是否可中断:ReentrantLock属于可中断锁,而synchronized属于不可中断锁。

3.1 可重入性

  • 可重入锁也叫递归锁,指的是线程可以再次获取自己的内部锁。
  • JDK提供的所有现成的Lock实现类,以及synchronized关键字都是可重入锁。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class SynchronizedDemo {
    public synchronized void methodA() {
    System.out.println("In method A");
    methodB(); // 再次获取锁
    }

    public synchronized void methodB() {
    System.out.println("In method B");
    }
    }
  • 在上述示例中,同一个线程在调用methodA时获取了当前对象的锁,执行methodB时可以再次获取当前对象的锁,不会产生死锁问题。

3.2 依赖

  • synchronized是Java语言内置的同步机制,依赖于JVM的实现。
  • ReentrantLock是JDK层面实现的,也就是API层面,需要使用lock()unlock()方法配合try/finally来使用。

3.3 功能

  • ReentrantLock相比synchronized提供了更多的功能,主要有以下四点:
    • 等待可中断:ReentrantLock中的lock.lockInterruptibly()方法允许线程在等待锁的过程中被中断。也就是当前线程在等待获取锁的过程中被其他线程使用interrupt()中断,当前线程就会抛出InterruptedException异常,可以捕捉该异常并进行处理。
    • 可实现公平锁:ReentrantLock可以选择使用公平锁还是非公平锁,而synchronized只能是非公平锁。公平锁是指线程获取锁的顺序是按照请求锁的顺序来分配的,而非公平锁则不保证顺序。ReentrantLock默认使用非公平锁。
      1
      2
      3
      4
      // 传入一个boolean参数,true表示公平锁,false表示非公平锁
      public ReentrantLock(boolean fair) {
      sync = fair ? new FairSync() : new NonfairSync();
      }
    • 可实现选择性通知:synchronized关键字与wait()notify()/notifyAll()方法相结合可以实现等待/通知机制。但是synchronized只能使用一个等待队列,而ReentrantLock可以通过Condition接口和newCondition)_方法创建多个等待队列,从而实现更灵活的等待/通知机制。
    • 支持超时:ReentrantLock提供的tryLock(timeout)方法可以指定等待获取锁的最长等待时间,超过这个时间就会获取锁失败,不会一直等待。

3.4 是否可中断

  • ReentrantLock属于可中断锁,而synchronized属于不可中断锁。

3.5 对比总结

维度 synchronized ReentrantLock
实现层级 JVM JDK(AQS)
核心机制 Monitor + 对象头 CAS + AQS队列
可重入 支持 支持
公平性 不支持 支持
可中断 不支持 支持
超时机制 不支持 支持
条件变量 单一 wait/notify 多 Condition
使用复杂度 简单 较复杂

四、总结

synchronized 是 JVM 层面的自动优化锁,适合简单场景;
ReentrantLock 基于 AQS 构建,提供更强的灵活性与控制能力,适用于复杂并发场景。