解释重入锁(ReentrantLock)的概念及其工作原理。

参考回答

重入锁(ReentrantLock) 是 Java 提供的一种可重入的锁,位于 java.util.concurrent.locks 包中。它具有类似于 synchronized 的锁功能,但提供了更灵活的锁机制,支持显式锁获取和释放,以及可中断、超时、和公平锁等特性。

关键点:

  1. 可重入:同一个线程可以多次获得同一把锁,而不会导致死锁。
  2. 显式控制:需要显式调用 lock()unlock() 来加锁和解锁。
  3. 灵活性:支持公平锁和非公平锁、锁中断、超时等高级特性。

详细讲解与拓展

1. ReentrantLock 的主要特性

特性 描述
可重入 同一线程可以重复获得锁,但每次 lock() 都必须对应一次 unlock()
公平锁和非公平锁 默认是非公平锁,锁的分配顺序可能与线程的等待顺序无关;也可以设置为公平锁,按线程等待顺序获取锁。
中断支持 线程在等待锁时,可以响应中断,通过 lockInterruptibly() 方法实现。
超时获取锁 通过 tryLock(long timeout, TimeUnit unit) 可以在超时时间内尝试获取锁,避免死锁。
条件变量支持 提供 Condition 对象,用于实现复杂的线程间通信(如 await()signal())。

2. 使用示例

基本用法:加锁与解锁
import java.util.concurrent.locks.ReentrantLock;

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

    public void criticalSection() {
        lock.lock(); // 加锁
        try {
            // 临界区代码
            System.out.println(Thread.currentThread().getName() + " is executing critical section");
        } finally {
            lock.unlock(); // 确保释放锁
        }
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();

        Runnable task = () -> {
            for (int i = 0; i < 3; i++) {
                example.criticalSection();
            }
        };

        Thread t1 = new Thread(task, "Thread-1");
        Thread t2 = new Thread(task, "Thread-2");

        t1.start();
        t2.start();
    }
}

输出示例:

Thread-1 is executing critical section
Thread-2 is executing critical section
Thread-1 is executing critical section
Thread-2 is executing critical section
...

关键点:

  • 必须显式调用 lock()unlock(),确保锁的正确释放(使用 finally 块)。
  • 如果忘记释放锁,可能导致死锁。

公平锁与非公平锁
import java.util.concurrent.locks.ReentrantLock;

public class FairLockExample {
    private final ReentrantLock lock = new ReentrantLock(true); // 创建公平锁

    public void criticalSection() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " got the lock");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        FairLockExample example = new FairLockExample();

        Runnable task = example::criticalSection;

        for (int i = 1; i <= 5; i++) {
            new Thread(task, "Thread-" + i).start();
        }
    }
}

输出示例:

Thread-1 got the lock
Thread-2 got the lock
Thread-3 got the lock
Thread-4 got the lock
Thread-5 got the lock

解释:

  • 公平锁按照线程的等待顺序分配锁,避免“饥饿”现象。
  • 非公平锁性能更高,因为线程可能会“抢占”锁,减少线程调度的开销。

支持中断的锁
public class InterruptibleLockExample {
    private final ReentrantLock lock = new ReentrantLock();

    public void interruptibleTask() {
        try {
            lock.lockInterruptibly(); // 支持中断的加锁方式
            System.out.println(Thread.currentThread().getName() + " acquired the lock");
            Thread.sleep(2000); // 模拟任务
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + " was interrupted");
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock(); // 确保释放锁
            }
        }
    }

    public static void main(String[] args) {
        InterruptibleLockExample example = new InterruptibleLockExample();

        Thread t1 = new Thread(example::interruptibleTask, "Thread-1");
        Thread t2 = new Thread(() -> {
            example.interruptibleTask();
        }, "Thread-2");

        t1.start();
        t2.start();

        try {
            Thread.sleep(500); // 等待一段时间后中断线程 2
            t2.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出示例:

Thread-1 acquired the lock
Thread-2 was interrupted

3. ReentrantLock 的工作原理

  1. 底层基于 AQS(AbstractQueuedSynchronizer)
  • ReentrantLock 使用 AQS 实现,其核心是一个双向队列状态变量

  • state变量记录锁的状态:

    • 0 表示锁未被占用。
    • 大于 0 表示锁被占用,值代表重入次数。
  1. 获取锁(lock)
  • 非公平锁:线程尝试直接 CAS 修改 state 为 1,如果成功则获得锁。
  • 公平锁:线程需要先检查队列中是否有其他线程等待锁,避免抢占。
  1. 释放锁(unlock)
  • 释放锁时,state 减 1,如果减到 0,锁完全释放,并唤醒等待队列中的线程。
  1. 支持可重入
  • 同一线程再次调用 lock(),只需增加 state 值,而不需要阻塞。

4. 使用场景

  • 高并发场景:需要显式控制锁的获取和释放。
  • 复杂线程间同步:需要使用 Condition 实现复杂的等待和通知机制。
  • 避免死锁:通过超时和中断机制降低死锁风险。

5. 小结

  • ReentrantLock 的优势:
    • synchronized 更灵活(如支持公平锁、中断、超时等)。
    • 提供显式锁控制,适合复杂并发场景。
  • ReentrantLock 的缺点:
    • 显式调用 lock()unlock(),容易出现忘记释放锁的问题。
    • 在简单场景下,synchronized 更适合,因为它会自动管理锁。

发表评论

后才能评论