MST
星途 面试题库

面试题:Java中ReentrantLock与synchronized在锁的获取与释放机制上有何不同

ReentrantLock和synchronized都是Java并发编程中常用的锁机制。请深入分析它们在锁的获取与释放机制方面的差异,比如获取锁的过程、是否可中断、锁的公平性,以及释放锁的时机和方式等,并举例说明在哪些场景下更适合使用ReentrantLock而不是synchronized,反之亦然。
34.2万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. 锁的获取与释放机制差异

  • 获取锁的过程
    • synchronized:是基于JVM层面的内置锁,当一个线程进入同步代码块(使用synchronized修饰的代码块或方法)时,会自动获取锁。如果锁不可用,线程会进入阻塞状态,等待锁的释放。
    • ReentrantLock:通过调用lock()方法获取锁。如果锁不可用,线程同样会等待,但ReentrantLock提供了更灵活的获取锁方式,比如可以尝试获取锁(tryLock()方法)。
  • 是否可中断
    • synchronized:当一个线程尝试获取synchronized锁时,如果锁被其他线程持有,该线程会一直阻塞,无法被中断,除非持有锁的线程释放锁或者抛出异常。
    • ReentrantLock:支持可中断的锁获取操作。通过调用lockInterruptibly()方法,在等待获取锁的过程中,如果线程被中断,会抛出InterruptedException异常并停止等待。
  • 锁的公平性
    • synchronized:是非公平锁,即锁的分配是随机的,新进入的线程有可能比等待时间较长的线程先获取到锁。
    • ReentrantLock:默认也是非公平锁,但可以通过构造函数ReentrantLock(boolean fair)设置为公平锁。公平锁会按照线程等待的先后顺序分配锁,等待时间最长的线程会优先获取锁。
  • 释放锁的时机和方式
    • synchronized:当同步代码块或方法执行完毕(正常结束或抛出异常),JVM会自动释放锁。无需手动释放。
    • ReentrantLock:需要手动调用unlock()方法释放锁,且必须在获取锁的代码块内的最后执行。如果没有正确释放锁,可能会导致死锁等问题。同时,ReentrantLock支持在锁获取的代码块内多次调用lock()方法(可重入特性),相应地也需要调用相同次数的unlock()方法来释放锁。

2. 适用场景分析

  • 适合使用ReentrantLock的场景
    • 需要可中断的锁获取操作:例如在处理一些长时间运行的任务时,如果需要在等待锁的过程中能够响应中断信号,就可以使用ReentrantLocklockInterruptibly()方法。
import java.util.concurrent.locks.ReentrantLock;

public class InterruptibleLockExample {
    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            try {
                lock.lockInterruptibly();
                try {
                    System.out.println("Thread 1 acquired the lock");
                    // 模拟长时间运行的任务
                    Thread.sleep(2000);
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                System.out.println("Thread 1 was interrupted while waiting for the lock");
            }
        });

        Thread thread2 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("Thread 2 acquired the lock");
                // 中断thread1
                thread1.interrupt();
            } finally {
                lock.unlock();
            }
        });

        thread1.start();
        thread2.start();
    }
}
- **需要公平锁**:在一些对公平性要求较高的场景,比如多个线程按照顺序执行任务,公平锁可以确保等待时间最长的线程优先获取锁,避免线程饥饿问题。
import java.util.concurrent.locks.ReentrantLock;

public class FairLockExample {
    private static final ReentrantLock fairLock = new ReentrantLock(true);

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                fairLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " acquired the fair lock");
                } finally {
                    fairLock.unlock();
                }
            }).start();
        }
    }
}
  • 适合使用synchronized的场景
    • 简单的同步场景:当代码逻辑比较简单,对锁的功能需求不复杂时,使用synchronized可以使代码更简洁,因为它不需要手动获取和释放锁,由JVM自动管理。
public class SynchronizedExample {
    private static final Object monitor = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (monitor) {
                System.out.println("Thread 1 entered synchronized block");
                // 同步代码逻辑
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (monitor) {
                System.out.println("Thread 2 entered synchronized block");
                // 同步代码逻辑
            }
        });

        thread1.start();
        thread2.start();
    }
}
- **与其他Java内置特性结合**:由于`synchronized`是Java内置的关键字,与Java的其他特性(如对象的内置锁机制)紧密结合,在一些依赖这些特性的场景下使用更方便。例如在实现单例模式时,使用`synchronized`修饰静态方法来确保线程安全的实例创建。
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}