基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Synchronized与ReentrantLock深度比较

知识点图片

本文重点对比了synchronized和Re-entrant-Lock。前者是JVM关键字,简单自动;后者是API类,功能强大,支持公平锁、可中断、超时等高级特性,但需手动释放锁。

我们来详细地、系统地比较一下 synchronizedReentrantLock

synchronizedReentrantLock 都是 Java 中用来实现线程同步的机制,它们的核心目标都是保证在多线程环境下,共享资源能够被安全地访问。尽管目标相同,但它们在实现方式、功能和使用上存在显著的区别。

可以把 synchronized 看作是 Java 内置的、更简单易用的“自动挡”,而 ReentrantLock 则是功能更强大、更灵活的“手动挡”。

核心区别概览

特性 synchronized ReentrantLock
本质 Java 关键字,由 JVM 实现 JUC (java.util.concurrent) 包中的一个类,基于 AQS (AbstractQueuedSynchronizer) 实现
锁的获取与释放 隐式操作,代码块执行完或异常退出后,JVM 自动释放锁 显式操作,必须手动调用 lock() 获取和 unlock() 释放锁
使用便利性 简单,不易出错(因为自动释放) 相对复杂,必须在 finally 块中调用 unlock(),否则可能导致死锁
可重入性 ,隐式支持 ,名称已表明,通过内部计数器实现
公平性 非公平锁 可选公平/非公平,通过构造函数指定(默认为非公平)
锁等待的可中断性 不可中断,线程一旦等待,只能等下去,不能响应中断 可中断,通过 lockInterruptibly() 方法
尝试获取锁 不支持,线程会一直阻塞等待 支持,通过 tryLock()tryLock(time, unit),可立即返回或超时返回
条件变量 (Condition) 单一条件,与锁本身绑定 (wait(), notify(), notifyAll()) 多个条件,可创建多个 Condition 对象,实现更精细的线程通信
性能 Java 1.6 后优化显著(偏向锁、轻量级锁、自旋锁),与 ReentrantLock 性能相当 在高竞争下通常有更好的吞吐量,但具体取决于场景和 JDK 版本

详细对比解析

1. 本质与层面

  • synchronized: 是 Java 语言的关键字,其实现是嵌入在 JVM 内部的。JVM 会将其编译成特定的字节码指令(monitorentermonitorexit),由 JVM 负责锁的获取和释放。
  • ReentrantLock: 是一个标准的 Java 类(java.util.concurrent.locks.ReentrantLock),它提供了面向对象的 API。它的实现依赖于 JUC 框架下的 AQS (AbstractQueuedSynchronizer)。

2. 使用方式与锁的释放

这是最直观的区别。

  • synchronized 使用起来非常简单,你只需要将它放在方法或代码块上。

    java
    public class SynchronizedCounter {
        private int count = 0;
    
        public synchronized void increment() {
            count++;
        }
    }

    锁的释放是自动的。当线程执行完同步代码块,或者在执行过程中抛出异常时,JVM 都会确保锁被释放。这大大降低了因忘记释放锁而导致死锁的风险。

  • ReentrantLock 需要手动控制。

    java
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockCounter {
        private int count = 0;
        private final ReentrantLock lock = new ReentrantLock();
    
        public void increment() {
            lock.lock(); // 手动获取锁
            try {
                count++;
            } finally {
                lock.unlock(); // 必须在 finally 块中手动释放锁
            }
        }
    }

    极其重要unlock() 调用必须放在 finally 块中。这是为了保证即使 try 块中的代码抛出异常,锁也一定会被释放。如果忘记释放锁,其他线程将永远无法获取该锁,造成“死锁”。

3. 功能特性

ReentrantLock 提供了 synchronized 不具备的几个高级功能:

  • 公平锁 (Fair Lock)

    • synchronized 是非公平的。当锁被释放时,任何一个正在等待的线程都有机会获取锁,这可能导致某些线程长时间“饥饿”。
    • ReentrantLock 默认也是非公平的,但可以通过构造函数创建公平锁:new ReentrantLock(true)。公平锁会按照线程请求锁的时间顺序(FIFO)来分配锁,但通常会带来额外的性能开销。
  • 可中断的锁等待 (Interruptible Lock)

    • 一个正在等待 synchronized 锁的线程是不可被中断的,它会一直傻等下去。
    • ReentrantLock 提供了 lock.lockInterruptibly() 方法。一个正在通过此方法等待锁的线程,如果被其他线程调用了 interrupt(),它会停止等待并抛出 InterruptedException,从而可以提前结束等待,去做其他事情。这在处理可能发生的死锁时非常有用。
  • 尝试获取锁 (Try Lock)

    • synchronized 没有这个功能。
    • ReentrantLock 提供了 lock.tryLock()lock.tryLock(long timeout, TimeUnit unit)
      • tryLock():尝试立即获取锁,如果成功则返回 true,如果锁已被其他线程持有则立即返回 false,线程不会阻塞。
      • tryLock(timeout, unit):在指定的时间内尝试获取锁,如果成功则返回 true,超时仍未获取则返回 false
        这两种方法为避免死锁和实现更灵活的业务逻辑提供了可能。
  • 多条件变量 (Multiple Conditions)

    • synchronizedObjectwait(), notify(), notifyAll() 方法配合使用,但一个锁实例只有一个条件队列。
    • ReentrantLock 可以通过 lock.newCondition() 创建多个 Condition 对象。每个 Condition 都有自己的等待队列 (await()) 和通知机制 (signal(), signalAll())。这在实现复杂的生产者-消费者模型时非常有用,例如,可以为“仓库未满”(生产者等待)和“仓库不空”(消费者等待)创建两个不同的 Condition,从而可以精确地唤醒特定类型的线程,提高效率。

    示例:

    java
    Lock lock = new ReentrantLock();
    Condition notFull = lock.newCondition();  // 生产者的条件
    Condition notEmpty = lock.newCondition(); // 消费者的条件
    
    // 生产者线程
    lock.lock();
    try {
        while (isFull) {
            notFull.await(); // 等待“未满”这个条件
        }
        // ...生产...
        notEmpty.signal(); // 通知消费者“不空”了
    } finally {
        lock.unlock();
    }

如何选择?

  • 优先选择 synchronized

    • 如果锁的竞争不激烈,或者同步逻辑非常简单。
    • synchronized 是 JVM 内置的,经过了大量优化,代码更简洁,不易出错。
    • “能用 synchronized 解决的问题,就不要用 ReentrantLock。” 这是大多数情况下的最佳实践。
  • 在以下场景中选择 ReentrantLock

    1. 需要使用公平锁来保证线程调度的公平性。
    2. 需要可中断的锁获取操作,以避免线程无限期等待。
    3. 需要非阻塞地尝试获取锁 (tryLock),例如,当获取不到锁时可以执行备用逻辑。
    4. 需要实现复杂的线程通信,使用多个条件变量来分组管理等待的线程。

总结

synchronizedReentrantLock 就像是汽车的自动挡和手动挡。对于日常驾驶(简单的同步需求),自动挡(synchronized)足够好用、方便且安全。但如果你需要上赛道,追求极致的性能和操控(复杂的同步场景),那么手动挡(ReentrantLock)提供的灵活性和高级功能就派上用场了。

00:00
00:00