深度分析Java线程同步之使用ReentrantLock
在Java并发编程中,线程同步是确保数据一致性和线程安全的基石。虽然内置的 synchronized 关键字简单易用,但其局限性(如无法中断、非公平、单一条件队列)在复杂的应用场景中逐渐显现。java.util.concurrent.locks.ReentrantLock 作为JUC包中的佼佼者,提供了一种更灵活、更强大的互斥锁实现。
一、ReentrantLock核心优势
与 synchronized 相比,ReentrantLock 带来了多项关键提升:
- 可重入性:与
synchronized一样,一个线程可以多次获取同一把锁,避免了死锁。 - 尝试非阻塞获取锁:
tryLock()方法允许线程尝试获取锁,如果失败立即返回或等待指定时间,而非一直阻塞,极大地提高了程序的灵活性。 - 可中断的锁获取:
lockInterruptibly()方法允许在等待锁的过程中响应中断,为处理线程终止提供了更优雅的方式。 - 公平锁与非公平锁:构造函数允许选择创建公平锁或非公平锁。公平锁遵循FIFO原则,减少了线程饥饿;非公平锁则提供更高的吞吐量,默认是非公平的。
- 丰富的条件变量(Condition):一个
ReentrantLock可以关联多个Condition对象,实现更精细的线程等待/通知机制,远超synchronized的wait()/notifyAll()。
二、synchronized vs. ReentrantLock:何时选择?
- synchronized:语法简洁,由JVM自动释放锁,不易出错。适用于简单的、锁竞争不激烈的同步块。
- ReentrantLock:当需要上述高级功能(如可中断、超时、公平性、多个条件)时,或
synchronized成为性能瓶颈时,应毫不犹豫地选择它。代价是必须在finally块中手动释放锁,否则会导致严重后果。
三、实战代码示例
下面通过一个经典的“银行账户”示例,展示 ReentrantLock 的基本用法和高级特性。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class BankAccount {
private final Lock lock = new ReentrantLock();
// 创建一个与lock绑定的条件变量,用于表示“账户有足够余额”
private final Condition sufficientFunds = lock.newCondition();
private long balance;
public BankAccount(long balance) {
this.balance = balance;
}
// 基础用法:使用lock()和unlock()
public void withdrawBasic(long amount) {
lock.lock(); // 获取锁
try {
while (balance < amount) {
// 使用synchronized时,这里只能wait()
sufficientFunds.await(); // 释放锁并等待,被唤醒后会自动重新获取锁
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取款 " + amount + ",余额: " + balance);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 必须在finally中确保锁被释放
}
}
// 高级用法:尝试获取锁(tryLock)和可中断
public boolean tryTransfer(BankAccount to, long amount, long timeout) throws InterruptedException {
long stopTime = System.nanoTime() + timeout;
while (true) {
if (this.lock.tryLock()) { // 尝试获取本账户的锁
try {
if (to.lock.tryLock()) { // 尝试获取目标账户的锁
try {
if (balance >= amount) {
balance -= amount;
to.balance += amount;
System.out.println("转账成功!金额: " + amount);
return true;
} else {
return false; // 余额不足
}
} finally {
to.lock.unlock();
}
}
} finally {
this.lock.unlock();
}
}
if (System.nanoTime() > stopTime) {
return false; // 超时失败
}
// 短暂休眠,避免活锁
Thread.sleep(10);
}
}
public void deposit(long amount) {
lock.lock();
try {
balance += amount;
System.out.println(Thread.currentThread().getName() + " 存款 " + amount + ",余额: " + balance);
// 存款后,通知所有等待“sufficientFunds”条件的线程
sufficientFunds.signalAll();
} finally {
lock.unlock();
}
}
}
四、总结
ReentrantLock 将线程同步的控制权从JVM手中交还给了开发者。它通过显式的API调用,让我们能够编写出更适应复杂业务逻辑、更具弹性和鲁棒性的并发程序。虽然需要小心处理锁的释放以避免死锁,但其带来的强大功能使得这一点微不足道。在面对高性能、高并发的现代应用架构时,熟练掌握 ReentrantLock 是每一位Java开发者的必备技能。
243

被折叠的 条评论
为什么被折叠?



