MST

星途 面试题库

面试题:Java中ReentrantLock与synchronized在并发编程锁机制上有哪些主要区别?

从锁的获取与释放方式、锁的公平性、锁的可中断性、锁的绑定条件等多个方面,深入分析ReentrantLock和synchronized在并发编程场景下的差异,并举例说明在何种场景下应优先选择其中某一种锁机制。
32.6万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. 锁的获取与释放方式

  • synchronized
    • 它是基于JVM层面的内置锁,通过字节码指令 monitorentermonitorexit 实现。当一个线程进入同步块时,会自动获取锁,退出同步块(正常结束或异常)时自动释放锁。
    • 例如:
public class SynchronizedExample {
    private static final Object lock = new Object();
    public void synchronizedMethod() {
        synchronized (lock) {
            // 同步代码块
        }
    }
}
  • ReentrantLock
    • 它是基于API层面的锁,通过调用 lock() 方法获取锁,调用 unlock() 方法释放锁。需要手动在 finally 块中释放锁,以确保在异常情况下也能正确释放锁,避免死锁。
    • 例如:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
    private static final ReentrantLock lock = new ReentrantLock();
    public void lockMethod() {
        lock.lock();
        try {
            // 同步代码块
        } finally {
            lock.unlock();
        }
    }
}

2. 锁的公平性

  • synchronized
    • 是非公平锁。线程在竞争锁时,不考虑等待时间的长短,只要锁一释放,任何线程都有机会获取锁,可能导致某些线程长时间等待。
  • ReentrantLock
    • 可以通过构造函数指定是否为公平锁。默认构造函数创建的是非公平锁。公平锁按照线程等待的先后顺序分配锁,能减少线程饥饿现象,但性能比非公平锁低,因为公平锁需要维护一个等待队列,线程切换开销更大。
    • 例如,创建公平锁:
ReentrantLock fairLock = new ReentrantLock(true);

3. 锁的可中断性

  • synchronized
    • 不可中断。当一个线程获取到锁后,其他线程只能等待,无法在等待过程中被中断。
  • ReentrantLock
    • 可中断。通过 lockInterruptibly() 方法获取锁时,如果当前线程在等待过程中被中断,会抛出 InterruptedException 异常并放弃获取锁。
    • 例如:
try {
    lock.lockInterruptibly();
    // 同步代码块
} catch (InterruptedException e) {
    // 处理中断
} finally {
    lock.unlock();
}

4. 锁的绑定条件

  • synchronized
    • 与一个对象实例绑定,每个对象都有一个监视器(monitor),通过 synchronized 关键字修饰的代码块或方法,实际上是获取该对象的监视器锁。
  • ReentrantLock
    • 可以绑定多个 Condition 对象,每个 Condition 可以实现不同的等待和唤醒策略。ConditionObjectwait()notify() 方法更灵活,可实现更细粒度的线程间通信。
    • 例如:
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
try {
    lock.lock();
    condition.await();
} catch (InterruptedException e) {
    // 处理中断
} finally {
    lock.unlock();
}

场景选择

  • 优先选择synchronized的场景
    • 代码简单,对锁的功能要求不高,主要关注代码的简洁性和可读性。例如,简单的同步方法或同步块来保证数据的一致性。
    • 不需要考虑锁的公平性、可中断性等特殊需求时,因为 synchronized 性能优化后,在一般场景下能满足并发需求且开销较小。
  • 优先选择ReentrantLock的场景
    • 需要实现公平锁机制,避免线程长时间等待,例如在一些对公平性要求较高的资源分配场景。
    • 需要锁的可中断性,当线程在等待锁的过程中需要能够响应中断信号时,如在处理任务取消的场景。
    • 需要更灵活的线程间通信,通过 Condition 实现比 synchronized 更细粒度的控制,例如在生产者 - 消费者模型中实现更复杂的唤醒策略。