ReentrantLock 与 Synchronized 的区别

ReentrantLock 与 Synchronized 的区别

ReentrantLock 和 Synchronized 都是 Java 中用于实现线程同步的机制,但它们有显著的区别:

1. 基本性质对比

特性ReentrantLockSynchronized
实现级别JDK 层面 (java.util.concurrent.locks)JVM 层面 (关键字)
锁的获取方式显式调用 lock()/unlock()隐式获取和释放 (代码块/方法)
灵活性高 (可中断、超时、公平锁等)低 (基本功能)
性能Java 6+ 性能相当Java 6+ 性能相当
可重入性支持支持

2. 功能区别详解

(1) 锁的获取方式

// ReentrantLock - 显式锁
ReentrantLock lock = new ReentrantLock();
lock.lock();  // 必须显式获取
try {
    // 临界区代码
} finally {
    lock.unlock();  // 必须显式释放
}

// Synchronized - 隐式锁
synchronized(this) {  // 自动获取和释放
    // 临界区代码
}

(2) 可中断性

// ReentrantLock 可响应中断
ReentrantLock lock = new ReentrantLock();
try {
    lock.lockInterruptibly();  // 可被中断的获取锁
    // 临界区代码
} catch (InterruptedException e) {
    // 处理中断
} finally {
    if(lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

// Synchronized 不可中断
synchronized(this) {
    // 如果其他线程不释放锁,当前线程会一直阻塞
}

(3) 公平锁支持

// ReentrantLock 可实现公平锁
ReentrantLock fairLock = new ReentrantLock(true);  // true表示公平锁

// Synchronized 只能是非公平锁
synchronized(this) {  // 非公平
    // ...
}

(4) 尝试获取锁

// ReentrantLock 可尝试获取锁
if (lock.tryLock()) {  // 立即返回是否成功
    try {
        // 获取锁成功
    } finally {
        lock.unlock();
    }
}

// 带超时的尝试获取
if (lock.tryLock(1, TimeUnit.SECONDS)) {  // 等待1秒
    try {
        // ...
    } finally {
        lock.unlock();
    }
}

// Synchronized 无法实现尝试获取

(5) 条件变量 (Condition)

// ReentrantLock 支持多个条件
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
    condition.await();  // 释放锁并等待
    condition.signal(); // 唤醒一个等待线程
} finally {
    lock.unlock();
}

// Synchronized 只有一个等待队列
synchronized(obj) {
    obj.wait();  // 等待
    obj.notify(); // 唤醒
}

3. 使用场景建议

使用 Synchronized 当:

  • 简单的同步需求
  • 不需要高级功能(如可中断、公平锁等)
  • 代码简洁性更重要时
  • 在 Java 5 之前的版本中

使用 ReentrantLock 当:

  • 需要可中断的锁获取
  • 需要尝试获取锁(带超时)
  • 需要公平锁机制
  • 需要多个条件变量
  • 需要更细粒度的控制

4. 性能考虑

  • 在 Java 5 中,ReentrantLock 性能优于 synchronized
  • 从 Java 6 开始,JVM 对 synchronized 进行了大量优化,两者性能相当
  • 在高度竞争的情况下,ReentrantLock 可能仍然有优势

5. 最佳实践

  1. 优先考虑 synchronized:在简单场景下,synchronized 更简洁且不易出错
  2. 需要高级功能时使用 ReentrantLock:当需要上述高级特性时选择 ReentrantLock
  3. 确保释放锁:使用 ReentrantLock 时必须在 finally 块中释放锁
  4. 避免嵌套:过度使用锁会导致死锁风险增加

总结

ReentrantLock 提供了比 synchronized 更灵活的锁操作,但随之而来的是更复杂的用法和更大的出错可能性。在大多数常见场景下,synchronized 已经足够使用且更安全。只有在确实需要 ReentrantLock 的高级功能时,才应该使用它。

ReentrantLock 与 synchronized 底层实现区别

一、synchronized 底层实现

1. JVM 层面的实现

synchronized 是 Java 语言内置的关键字,其实现完全由 JVM 负责:

  1. 监视器锁(Monitor)机制

    • 每个 Java 对象都有一个关联的监视器锁(Monitor)
    • 通过对象头中的 Mark Word 实现锁状态记录
  2. 对象内存布局

    |--------------------------------------------------------------|
    |                     Object Header (64 bits)                  |
    |------------------------------------|-------------------------|
    |        Mark Word (32/64 bits)      |  Klass Pointer (32 bits) |
    |------------------------------------|-------------------------|
    
  3. Mark Word 结构(32位 JVM):

    |-------------------------------------------------------|--------------------|
    |                  Mark Word (32 bits)                   |       State        |
    |-------------------------------------------------------|--------------------|
    |  hashcode:25 | age:4 | biased_lock:1 | lock:2 (01)     |       Normal       |
    |-------------------------------------------------------|--------------------|
    |  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 (01) |   Biased       |
    |-------------------------------------------------------|--------------------|
    |  ptr_to_lock_record:30                         | lock:2 (00) | Lightweight Lock |
    |-------------------------------------------------------|--------------------|
    |  ptr_to_heavyweight_monitor:30                  | lock:2 (10) | Heavyweight Lock |
    |-------------------------------------------------------|--------------------|
    |                                               | lock:2 (11) |    Marked     |
    |-------------------------------------------------------|--------------------|
    

2. 锁升级过程

synchronized 采用锁膨胀策略,根据竞争情况逐步升级:

  1. 无锁状态:初始状态
  2. 偏向锁(Biased Locking):
    • 适用于只有一个线程访问同步块
    • 通过 CAS 操作将线程 ID 写入 Mark Word
  3. 轻量级锁(Thin Lock):
    • 当有轻微竞争时,转换为轻量级锁
    • 使用栈中的 Lock Record 和 CAS 操作
  4. 重量级锁(Heavyweight Lock):
    • 竞争激烈时升级为重量级锁
    • 通过操作系统的互斥量(mutex)实现
    • 涉及线程阻塞和唤醒,成本较高

3. 底层指令

synchronized 同步块编译后会产生:

  • monitorenter 指令:进入同步块
  • monitorexit 指令:退出同步块(包括正常退出和异常退出)

二、ReentrantLock 底层实现

1. AQS(AbstractQueuedSynchronizer)框架

ReentrantLock 基于 Doug Lea 开发的 AQS 框架实现:

  1. 核心数据结构

    // AQS 内部类 Node 表示等待线程
    static final class Node {
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter; // 用于条件队列
    }
    
    // AQS 关键字段
    private volatile int state; // 同步状态
    private transient volatile Node head; // 队首
    private transient volatile Node tail; // 队尾
    
  2. state 状态

    • 0 表示锁未被占用
    • 0 表示锁被占用,数值表示重入次数

2. 获取锁流程(非公平锁为例)

  1. tryLock() 尝试获取

    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) { // 锁未被占用
            if (compareAndSetState(0, acquires)) { // CAS 尝试获取
                setExclusiveOwnerThread(current); // 设置当前线程为持有者
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) { // 重入检查
            int nextc = c + acquires;
            if (nextc < 0) throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
    
  2. 加入等待队列

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) { // CAS 设置尾节点
                pred.next = node;
                return node;
            }
        }
        enq(node); // 自旋入队
        return node;
    }
    
  3. 自旋/阻塞

    final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) { // 前驱是头节点且尝试获取锁
                    setHead(node);
                    p.next = null;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt()) // 阻塞当前线程
                    interrupted = true;
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }
    

3. 释放锁流程

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) { // 完全释放
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c); // 更新状态
    return free;
}

三、核心区别对比

特性synchronizedReentrantLock (基于AQS)
实现层面JVM 内置,通过对象头和 monitor 实现JDK 实现,基于 CAS 和 CLH 队列
锁状态存储对象头中的 Mark WordAQS 中的 state 变量
等待队列JVM 维护的等待队列显式的 CLH 队列(双向链表)
竞争处理锁膨胀(偏向锁→轻量级锁→重量级锁)直接 CAS 竞争,失败后入队
阻塞机制重量级锁使用 OS 互斥量使用 LockSupport.park() 阻塞
可重入实现通过_recursions计数器通过 state 计数器
性能优化偏向锁、适应性自旋等 JVM 优化纯 Java 实现,依赖 CAS 和 park/unpark
中断响应不支持支持 lockInterruptibly()
公平性只有非公平模式可选择公平/非公平模式
条件变量只有一个 wait/notify 队列可创建多个 Condition 对象

四、底层原理图示

synchronized 锁状态转换

[无锁] → [偏向锁] → [轻量级锁] → [重量级锁]
  ↑____________|          |
      撤销偏向锁      自旋失败

ReentrantLock 获取锁流程

尝试获取锁 (CAS)
  ↓
成功? → 执行同步代码
  ↓ No
加入CLH队列
  ↓
自旋检查前驱节点
  ↓
前驱是头节点? → 尝试获取锁 → 成功? → 成为新头节点
  ↓ No                           ↓ No
阻塞等待唤醒                  继续自旋

五、选择建议

  1. 优先 synchronized

    • 简单同步场景
    • 不需要高级功能
    • Java 6+ 性能已优化
  2. 选择 ReentrantLock

    • 需要可中断、超时、公平锁等高级特性
    • 需要多个条件变量
    • 需要更细粒度的控制
    • 明确知道锁竞争情况的高并发场景

理解这些底层实现差异有助于在特定场景下做出更合适的技术选型,并能够更好地进行性能调优和问题排查。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值