跳到主要内容

🔐 ReentrantLock 全面解析

📖 概述

ReentrantLock 是 Java 并发包(java.util.concurrent.locks)中提供的可重入互斥锁,它比 synchronized 提供了更强大的功能和更好的扩展性。💪

⭐ 核心特性

1. 🔑 可重入性

同一线程可以多次获取同一个锁,不会发生死锁:

import java.util.concurrent.locks.ReentrantLock;

class ReentrantExample {
private final ReentrantLock lock = new ReentrantLock();

public void outerMethod() {
lock.lock();
try {
System.out.println("外层方法获取锁");
innerMethod(); // 可以再次获取同一个锁
} finally {
lock.unlock();
}
}

public void innerMethod() {
lock.lock();
try {
System.out.println("内层方法获取锁");
} finally {
lock.unlock();
}
}
}

2. ⚖️ 公平性选择

  • 非公平锁(默认):吞吐量高,但可能导致线程饥饿
  • 公平锁:按请求顺序获取锁,保证公平性但性能较低
// 非公平锁(默认)
ReentrantLock unfairLock = new ReentrantLock();

// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);

3. 🚨 中断响应

支持在等待锁的过程中响应中断:

public void interruptibleLock() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();

// 可以被中断的锁获取
lock.lockInterruptibly();
try {
// 执行临界区代码
System.out.println("获取到锁,执行业务逻辑");
} finally {
lock.unlock();
}
}

4. ⏰ 超时机制

可以设置获取锁的超时时间:

public boolean tryLockWithTimeout() {
ReentrantLock lock = new ReentrantLock();

try {
// 尝试在3秒内获取锁
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 执行临界区代码
System.out.println("成功获取锁");
return true;
} finally {
lock.unlock();
}
} else {
System.out.println("获取锁超时");
return false;
}
} catch (InterruptedException e) {
System.out.println("等待过程中被中断");
return false;
}
}

🔧 基本用法

标准使用模式

import java.util.concurrent.locks.ReentrantLock;

class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;

public void increment() {
lock.lock();
try {
count++;
System.out.println(Thread.currentThread().getName() +
": count = " + count);
} finally {
lock.unlock(); // 必须在finally块中释放锁
}
}

public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}

tryLock 示例

class TryLockExample {
private final ReentrantLock lock = new ReentrantLock();

public void performTask() {
if (lock.tryLock()) { // 立即尝试获取,不会阻塞
try {
System.out.println(Thread.currentThread().getName() +
" 获取到锁,执行任务");
// 执行任务
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() +
" 未能获取锁,执行其他任务");
// 执行其他不需要锁的任务
}
}
}

🆚 ReentrantLock vs Synchronized

特性ReentrantLocksynchronized
锁获取方式手动 lock/unlock自动获取/释放
可重入性✅ 支持✅ 支持
公平性✅ 可选择❌ 非公平
中断响应✅ lockInterruptibly()❌ 不支持
超时机制✅ tryLock(timeout)❌ 不支持
条件变量✅ Condition 对象✅ wait/notify
性能高竞争下表现更好JVM 优化后性能不错
使用复杂度🔧 较复杂📝 简单
// synchronized 方式
public class SyncExample {
private int count = 0;

public synchronized void increment() {
count++;
}

public synchronized int getCount() {
return count;
}
}

// ReentrantLock 方式
public class LockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;

public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}

public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}

🔍 Condition 条件变量

ReentrantLock 可以创建多个 Condition 对象,实现精细化的线程等待/通知机制:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

class BoundedBuffer<T> {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // 队列未满条件
private final Condition notEmpty = lock.newCondition(); // 队列非空条件

private final Object[] items = new Object[10];
private int putIndex = 0, takeIndex = 0, count = 0;

public void put(T item) throws InterruptedException {
lock.lock();
try {
// 队列满时等待
while (count == items.length) {
notFull.await();
}

items[putIndex] = item;
putIndex = (putIndex + 1) % items.length;
count++;

System.out.println(Thread.currentThread().getName() +
" 生产: " + item + ", 库存: " + count);

// 通知消费者
notEmpty.signal();
} finally {
lock.unlock();
}
}

@SuppressWarnings("unchecked")
public T take() throws InterruptedException {
lock.lock();
try {
// 队列空时等待
while (count == 0) {
notEmpty.await();
}

T item = (T) items[takeIndex];
items[takeIndex] = null;
takeIndex = (takeIndex + 1) % items.length;
count--;

System.out.println(Thread.currentThread().getName() +
" 消费: " + item + ", 库存: " + count);

// 通知生产者
notFull.signal();
return item;
} finally {
lock.unlock();
}
}
}

🎯 实战应用场景

1. 银行账户转账

class BankAccount {
private final ReentrantLock lock = new ReentrantLock();
private double balance;

public BankAccount(double balance) {
this.balance = balance;
}

public void transfer(BankAccount target, double amount) {
// 避免死锁:按账户ID排序获取锁
BankAccount first = this.hashCode() < target.hashCode() ? this : target;
BankAccount second = this.hashCode() < target.hashCode() ? target : this;

first.lock.lock();
try {
second.lock.lock();
try {
if (this.balance >= amount) {
this.balance -= amount;
target.balance += amount;
System.out.println("转账成功: " + amount + "元");
} else {
System.out.println("余额不足");
}
} finally {
second.lock.unlock();
}
} finally {
first.lock.unlock();
}
}

public double getBalance() {
lock.lock();
try {
return balance;
} finally {
lock.unlock();
}
}
}

2. 资源池管理

class ResourcePool<T> {
private final ReentrantLock lock = new ReentrantLock();
private final Condition available = lock.newCondition();
private final List<T> resources = new ArrayList<>();
private final Set<T> used = new HashSet<>();

public ResourcePool(List<T> resources) {
this.resources.addAll(resources);
}

public T acquire() throws InterruptedException {
lock.lock();
try {
while (resources.isEmpty()) {
available.await();
}

T resource = resources.remove(0);
used.add(resource);
return resource;
} finally {
lock.unlock();
}
}

public void release(T resource) {
lock.lock();
try {
if (used.remove(resource)) {
resources.add(resource);
available.signal();
}
} finally {
lock.unlock();
}
}

public int getAvailableCount() {
lock.lock();
try {
return resources.size();
} finally {
lock.unlock();
}
}
}

⚠️ 注意事项和最佳实践

1. 必须在 finally 块中释放锁

// ❌ 错误做法
public void badPractice() {
lock.lock();
// 如果这里抛出异常,锁不会被释放
doSomething();
lock.unlock();
}

// ✅ 正确做法
public void goodPractice() {
lock.lock();
try {
doSomething();
} finally {
lock.unlock(); // 确保锁一定会被释放
}
}

2. 避免 lock() 和 unlock() 不匹配

// ❌ 错误:多次调用 unlock()
public void incorrectUnlock() {
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
lock.unlock(); // 第二次unlock会抛出IllegalMonitorStateException
}
}

// ❌ 错误:忘记调用 unlock()
public void forgetUnlock() {
lock.lock();
try {
// 如果这里return或抛异常,可能忘记unlock
if (someCondition) {
return;
}
} finally {
// 如果有多个return点,容易忘记unlock
lock.unlock();
}
}

3. 使用 try-避免死锁

class DeadlockAvoidance {
private final ReentrantLock lock1 = new ReentrantLock();
private final ReentrantLock lock2 = new ReentrantLock();

public void method1() {
while (true) {
if (lock1.tryLock()) {
try {
if (lock2.tryLock()) {
try {
// 同时持有两个锁的业务逻辑
System.out.println("method1: 获取到两个锁");
return;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
// 短暂等待后重试
Thread.sleep(10);
}
}
}

4. 锁的粒度控制

class LockGranularity {
private final ReentrantLock lock = new ReentrantLock();

// ❌ 锁粒度过大
public void coarseGrainedLock() {
lock.lock();
try {
// 耗时的读取操作
readHeavyOperation();
// 快速的写入操作
quickWrite();
} finally {
lock.unlock();
}
}

// ✅ 合理的锁粒度
public void fineGrainedLock() {
// 读取操作可能不需要锁,或者使用读写锁
readHeavyOperation();

lock.lock();
try {
quickWrite();
} finally {
lock.unlock();
}
}
}

🔧 锁监控和诊断

获取锁的状态信息

class LockDiagnostics {
private final ReentrantLock lock = new ReentrantLock();

public void printLockInfo() {
System.out.println("队列长度: " + lock.getQueueLength());
System.out.println("是否被锁定: " + lock.isLocked());
System.out.println("当前线程是否持有锁: " + lock.isHeldByCurrentThread());
System.out.println("重入次数: " + lock.getHoldCount());
System.out.println("是否有等待线程: " + lock.hasQueuedThreads());
}

public boolean hasQueuedThread(Thread thread) {
return lock.hasQueuedThread(thread);
}
}

📊 性能测试对比

class LockPerformanceTest {
private static final int THREAD_COUNT = 10;
private static final int ITERATIONS = 1000000;

// synchronized 测试
private static volatile int syncCounter = 0;

public static void testSynchronized() throws InterruptedException {
Object lock = new Object();
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);

long startTime = System.currentTimeMillis();

for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < ITERATIONS; j++) {
synchronized (lock) {
syncCounter++;
}
}
latch.countDown();
}).start();
}

latch.await();
long endTime = System.currentTimeMillis();

System.out.println("synchronized 耗时: " + (endTime - startTime) + "ms");
}

// ReentrantLock 测试
private static int lockCounter = 0;

public static void testReentrantLock() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);

long startTime = System.currentTimeMillis();

for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < ITERATIONS; j++) {
lock.lock();
try {
lockCounter++;
} finally {
lock.unlock();
}
}
latch.countDown();
}).start();
}

latch.await();
long endTime = System.currentTimeMillis();

System.out.println("ReentrantLock 耗时: " + (endTime - startTime) + "ms");
}
}

🎓 高级特性

1. 锁降级

class LockDegradation {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private volatile boolean dataReady = false;
private String data;

public void processData() {
lock.lock();
try {
// 获取写锁进行数据准备
while (!dataReady) {
condition.await();
}

// 降级:保持锁的情况下读取数据
System.out.println("处理数据: " + data);

// 可以选择释放锁或者继续持有

} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}

public void prepareData(String newData) {
lock.lock();
try {
this.data = newData;
this.dataReady = true;
condition.signalAll();
} finally {
lock.unlock();
}
}
}

2. 锁超时轮询

class LockPolling {
private final ReentrantLock lock = new ReentrantLock();

public void tryWithBackoff() {
long waitTime = 10; // 初始等待时间
long maxWaitTime = 1000; // 最大等待时间

while (true) {
try {
if (lock.tryLock(waitTime, TimeUnit.MILLISECONDS)) {
try {
// 执行需要加锁的操作
System.out.println("成功获取锁,执行任务");
break;
} finally {
lock.unlock();
}
} else {
System.out.println("获取锁失败,等待 " + waitTime + "ms 后重试");
waitTime = Math.min(waitTime * 2, maxWaitTime);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("等待锁时被中断");
break;
}
}
}
}

❓ 常见面试题

基础题目

Q1: ReentrantLock 和 synchronized 的区别是什么?

A: 主要区别包括:

  1. 实现方式:ReentrantLock 是 API 层面的锁,synchronized 是 JVM 层面的锁
  2. 功能特性:ReentrantLock 支持公平锁、可中断锁、超时锁等高级功能
  3. 使用方式:ReentrantLock 需要手动 lock/unlock,synchronized 自动管理
  4. 性能表现:在低竞争时两者性能接近,高竞争时 ReentrantLock 通常更好

Q2: 什么是可重入锁?为什么需要可重入性?

A: 可重入锁是指同一个线程可以多次获取同一个锁而不会发生死锁。可重入性很重要,因为:

  1. 递归调用:方法递归调用时需要重复获取同一个锁
  2. 方法调用链:一个同步方法调用另一个同步方法
  3. 避免死锁:防止同一线程自己阻塞自己
// 可重入的例子
public void methodA() {
lock.lock();
try {
methodB(); // 调用methodB时会再次获取同一个锁
} finally {
lock.unlock();
}
}

public void methodB() {
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
}

中级题目

Q3: ReentrantLock 的公平锁和非公平锁有什么区别?如何选择?

A:

公平锁

  • 按照线程请求锁的顺序来分配(FIFO)
  • 避免线程饥饿,保证公平性
  • 性能较低,因为需要维护等待队列

非公平锁

  • 允许插队,新线程可以直接尝试获取锁
  • 吞吐量更高,减少线程上下文切换
  • 可能导致线程饥饿

选择策略

  • 高吞吐量场景:使用非公平锁(默认)
  • 需要严格公平性:使用公平锁
  • 线程竞争激烈:非公平锁通常表现更好

Q4: Condition 相比 Object 的 wait/notify 有什么优势?

A:

  1. 多条件支持:一个锁可以创建多个Condition,实现更精确的等待/通知
  2. 避免虚假唤醒:await() 会自动释放锁并重新获取
  3. 可中断等待:awaitInterruptibly() 支持中断
  4. 超时等待:awaitNanos() 支持超时机制
// 传统方式:一个锁只能有一组等待线程
synchronized(lock) {
while(!condition) {
lock.wait(); // 所有等待的线程都在同一个条件上
}
}

// Condition方式:可以有多个条件
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();

// 生产者等待notFull,消费者等待notEmpty

高级题目

Q5: 如何使用 ReentrantLock 实现一个高性能的缓存?

A: 实现要点包括:

  1. 分段锁策略:使用多个ReentrantLock减少竞争
  2. 读写分离:结合ReadWriteLock优化读操作
  3. 锁降级:先获取写锁更新,然后降级为读锁读取
  4. 超时机制:避免长时间阻塞
class HighPerformanceCache<K, V> {
private final int segments;
private final Segment<K, V>[] segmentArray;

@SuppressWarnings("unchecked")
public HighPerformanceCache(int segments) {
this.segments = segments;
this.segmentArray = new Segment[segments];
for (int i = 0; i < segments; i++) {
segmentArray[i] = new Segment<>();
}
}

private static class Segment<K, V> {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Map<K, V> map = new HashMap<>();

public V get(K key) {
rwLock.readLock().lock();
try {
return map.get(key);
} finally {
rwLock.readLock().unlock();
}
}

public void put(K key, V value) {
rwLock.writeLock().lock();
try {
map.put(key, value);
} finally {
rwLock.writeLock().unlock();
}
}

public V putIfAbsent(K key, V value) {
rwLock.readLock().lock();
try {
V existing = map.get(key);
if (existing != null) {
return existing;
}
} finally {
rwLock.readLock().unlock();
}

rwLock.writeLock().lock();
try {
V existing = map.get(key);
if (existing == null) {
map.put(key, value);
}
return existing;
} finally {
rwLock.writeLock().unlock();
}
}
}

private Segment<K, V> getSegment(K key) {
int hash = key.hashCode();
return segmentArray[Math.abs(hash % segments)];
}

public V get(K key) {
return getSegment(key).get(key);
}

public void put(K key, V value) {
getSegment(key).put(key, value);
}
}

Q6: ReentrantLock 在高并发场景下可能遇到哪些性能问题?如何优化?

A: 常见性能问题及优化策略:

  1. 锁竞争激烈

    • 问题:大量线程等待获取锁
    • 优化:使用读写锁、分段锁、无锁数据结构
  2. 上下文切换频繁

    • 问题:线程频繁阻塞和唤醒
    • 优化:使用tryLock减少阻塞,自旋等待
  3. 内存可见性问题

    • 问题:锁的获取和释放成本
    • 优化:减少锁的粒度,使用volatile变量
class OptimizedCounter {
private final ReentrantLock lock = new ReentrantLock();
private volatile long count = 0;

// 优化:使用分段计数减少竞争
private final int segmentCount = 16;
private final long[] segments = new long[segmentCount];

public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}

// 优化版本:减少锁竞争
public void optimizedIncrement() {
int segment = ThreadLocalRandom.current().nextInt(segmentCount);
// 使用Unsafe CAS操作实现无锁更新
// 这里简化为锁操作
lock.lock();
try {
segments[segment]++;
} finally {
lock.unlock();
}
}

public long getCount() {
long sum = 0;
for (long segment : segments) {
sum += segment;
}
return sum;
}
}

🎯 总结

ReentrantLock 是 Java 并发编程中的重要工具,提供了比 synchronized 更强大的功能:

✨ 核心优势

  • 🔑 可重入性:避免死锁,支持递归调用
  • ⚖️ 公平性选择:可选择公平或非公平模式
  • 🚨 中断响应:支持可中断的锁获取
  • 超时机制:避免无限期等待
  • 🔍 条件变量:实现精确的等待/通知机制

📝 使用建议

  1. 简单场景:优先使用 synchronized,代码更简洁
  2. 复杂场景:需要高级功能时选择 ReentrantLock
  3. 性能敏感:在竞争激烈时 ReentrantLock 通常表现更好
  4. 注意安全:确保在 finally 块中释放锁

🚀 进阶学习

  • 深入理解 AQS(AbstractQueuedSynchronizer)实现原理
  • 学习读写锁(ReadWriteLock)的使用
  • 掌握无锁编程技术和 CAS 操作
  • 了解 StampedLock 等更高级的锁实现

掌握 ReentrantLock 将让你在并发编程中更加游刃有余!🎉