Java ReentrantLock 深度解析:为什么它比 synchronized 更灵活?

Java ReentrantLock 深度解析:为什么它比 synchronized 更灵活?

目录


ReentrantLock 是什么?

ReentrantLock 是 Java 并发包(java.util.concurrent.locks)中的一种可重入锁,它提供了比 synchronized 更灵活的锁控制能力。
核心特点

  • 手动加锁/释放:需要显式调用 lock()unlock()
  • 可中断:等待锁时可以被其他线程中断。
  • 公平锁:支持先到先得的公平锁策略。
  • 条件变量:通过 Condition 实现线程间协作。

核心特性与使用场景

1. 可中断的锁获取

场景:线程等待锁时,如果长时间无法获取,允许被外部中断。
代码示例

ReentrantLock lock = new ReentrantLock();

try {
    lock.lockInterruptibly(); // 可中断的加锁
    // 执行关键代码...
} catch (InterruptedException e) {
    System.out.println("线程被中断,放弃获取锁");
} finally {
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

2. 公平锁与非公平锁

公平锁:按请求锁的顺序分配(先到先得)。
非公平锁:允许插队(默认策略,吞吐量更高)。

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

适用场景

  • 公平锁:避免线程饥饿(如高并发支付系统)。
  • 非公平锁:追求高吞吐量(多数场景)。

3. 尝试获取锁(带超时)

场景:防止线程无限等待,避免死锁。

if (lock.tryLock(3, TimeUnit.SECONDS)) { // 尝试获取锁,最多等3秒
    try {
        // 成功获取锁,执行操作
    } finally {
        lock.unlock();
    }
} else {
    System.out.println("获取锁超时,执行其他逻辑");
}

4. 条件变量(Condition)

场景:实现线程间精确协作(类似 wait()/notify(),但更灵活)。

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

// 线程A:等待条件
lock.lock();
try {
    while (!条件满足) {
        condition.await(); // 释放锁并等待
    }
    // 条件满足后继续执行...
} finally {
    lock.unlock();
}

// 线程B:通知条件满足
lock.lock();
try {
    条件 = true;
    condition.signalAll(); // 唤醒所有等待线程
} finally {
    lock.unlock();
}

代码实战:银行转账案例

需求:实现两个账户之间的安全转账,避免并发问题。

import java.util.concurrent.locks.ReentrantLock;

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

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

    // 转账方法
    public void transfer(BankAccount target, int amount) {
        boolean thisLock = false;
        boolean targetLock = false;
        
        try {
            // 尝试获取两把锁(避免死锁)
            thisLock = this.lock.tryLock(1, TimeUnit.SECONDS);
            targetLock = target.lock.tryLock(1, TimeUnit.SECONDS);
            
            if (thisLock && targetLock) {
                if (this.balance >= amount) {
                    this.balance -= amount;
                    target.balance += amount;
                    System.out.println("转账成功");
                }
            } else {
                System.out.println("获取锁失败,稍后重试");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (thisLock) this.lock.unlock();
            if (targetLock) target.lock.unlock();
        }
    }
}

与 synchronized 的对比

特性ReentrantLocksynchronized
锁获取方式手动 (lock()/unlock())自动(代码块结束释放)
可中断支持不支持
公平锁支持不支持
条件变量支持多个 Condition仅一个等待队列 (wait())
性能高并发下更优(需实测)JDK 优化后接近
代码复杂度高(需处理异常和释放锁)低(自动管理)

最佳实践与常见陷阱

1. 必须释放锁

  • 错误示例:忘记调用 unlock(),导致死锁。
  • 正确做法:在 finally 块中释放锁。

2. 避免嵌套锁

  • 问题:多个锁嵌套获取时容易死锁。
  • 解决方案:按固定顺序获取锁,或使用 tryLock() 超时机制。

3. 慎用公平锁

  • 陷阱:公平锁降低吞吐量(上下文切换增加)。
  • 建议:仅在明确需要公平性时使用。

总结

何时选择 ReentrantLock

  • 需要可中断、超时获取锁。
  • 需要公平锁策略。
  • 需要复杂的线程协作(多个条件变量)。

何时选择 synchronized

  • 简单同步需求,代码简洁优先。
  • JDK 5 之前性能敏感场景(现代 JDK 差距已缩小)。

核心思想:根据需求权衡灵活性与复杂度!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值