🔐 Lock接口
深入理解Java并发编程中的高级锁机制,掌握Lock接口的设计思想和实际应用
学习目标
- 掌握Lock接口的设计思想和核心方法
- 熟练使用ReentrantLock进行线程同步
- 理解公平锁和非公平锁的区别与选择
- 学会使用可中断锁和锁超时机制
- 对比Lock与synchronized的优缺点
Lock接口详解
Lock接口是Java并发包(java.util.concurrent.locks)中的核心接口,它提供了比synchronized关键字更灵活、更强大的锁机制。Lock接口的设计目标是提供更细粒度的锁控制,支持可中断的锁获取、超时锁获取以及非阻塞的锁获取。
Lock接口不是用来替代synchronized的,而是在synchronized无法满足需求时提供更多选择。它们各有适用场景。
Lock接口的核心方法
public interface Lock {
// 获取锁,如果锁不可用则阻塞等待
void lock();
// 可中断的锁获取
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁,立即返回结果
boolean tryLock();
// 在指定时间内尝试获取锁
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 创建条件变量
Condition newCondition();
}
Lock vs synchronized
特性 | synchronized | Lock |
---|---|---|
使用方式 | 关键字,自动获取和释放 | 接口,手动获取和释放 |
可中断性 | 不可中断 | 支持可中断锁 |
超时机制 | 不支持 | 支持超时获取锁 |
公平性 | 非公平锁 | 支持公平锁和非公平锁 |
条件变量 | 单一条件(wait/notify) | 支持多个条件变量 |
性能 | JVM优化,轻量级场景更好 | 高竞争场景性能更好 |
ReentrantLock详解
ReentrantLock是Lock接口最常用的实现类,它是一个可重入的互斥锁。"可重入"意味着同一个线程可以多次获取同一把锁,这与synchronized的行为一致。
基本使用方法
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 获取锁
try {
count++; // 临界区代码
System.out.println("Count: " + count);
} finally {
lock.unlock(); // 释放锁(必须在finally块中)
}
}
public void safeMethod() {
lock.lock();
try {
// 可重入性:同一线程可以再次获取锁
increment();
} finally {
lock.unlock();
}
}
}
使用Lock时必须在finally块中释放锁,否则可能导致死锁。这是与synchronized的重要区别。
可重入性演示
public class ReentrantDemo {
private final ReentrantLock lock = new ReentrantLock();
public void methodA() {
lock.lock();
try {
System.out.println("Method A - Hold count: " + lock.getHoldCount());
methodB(); // 调用另一个需要锁的方法
} finally {
lock.unlock();
}
}
public void methodB() {
lock.lock();
try {
System.out.println("Method B - Hold count: " + lock.getHoldCount());
methodC();
} finally {
lock.unlock();
}
}
public void methodC() {
lock.lock();
try {
System.out.println("Method C - Hold count: " + lock.getHoldCount());
} finally {
lock.unlock();
}
}
}
公平锁和非公平锁
ReentrantLock支持两种锁获取方式:公平锁和非公平锁。这是通过构造函数参数来控制的。
公平锁与非公平锁的使用
import java.util.concurrent.locks.ReentrantLock;
public class FairVsUnfairLock {
// 非公平锁(默认)
private final ReentrantLock unfairLock = new ReentrantLock();
// 公平锁
private final ReentrantLock fairLock = new ReentrantLock(true);
public void unfairMethod() {
unfairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " acquired unfair lock");
Thread.sleep(100); // 模拟工作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
unfairLock.unlock();
}
}
public void fairMethod() {
fairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " acquired fair lock");
Thread.sleep(100); // 模拟工作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
fairLock.unlock();
}
}
}
性能对比测试
public class LockPerformanceTest {
private static final int THREAD_COUNT = 10;
private static final int ITERATIONS = 100000;
public static void main(String[] args) throws InterruptedException {
testLockPerformance("非公平锁", new ReentrantLock(false));
testLockPerformance("公平锁", new ReentrantLock(true));
}
private static void testLockPerformance(String lockType, ReentrantLock lock)
throws InterruptedException {
long startTime = System.currentTimeMillis();
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < ITERATIONS; j++) {
lock.lock();
try {
// 模拟少量工作
} finally {
lock.unlock();
}
}
latch.countDown();
}).start();
}
latch.await();
long endTime = System.currentTimeMillis();
System.out.println(lockType + "耗时: " + (endTime - startTime) + "ms");
}
}
可中断锁
Lock接口提供了可中断的锁获取方法lockInterruptibly(),这允许等待锁的线程响应中断信号。这在需要取消长时间等待的场景中非常有用。
import java.util.concurrent.locks.ReentrantLock;
public class InterruptibleLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void interruptibleMethod() {
try {
// 可中断的锁获取
lock.lockInterruptibly();
try {
// 模拟长时间工作
System.out.println(Thread.currentThread().getName() + " 开始工作");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " 工作完成");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 被中断");
Thread.currentThread().interrupt(); // 恢复中断状态
}
}
public static void main(String[] args) throws InterruptedException {
InterruptibleLockExample example = new InterruptibleLockExample();
// 启动第一个线程,获取锁并长时间持有
Thread thread1 = new Thread(example::interruptibleMethod, "Thread-1");
thread1.start();
Thread.sleep(100); // 确保thread1先获取锁
// 启动第二个线程,尝试获取锁
Thread thread2 = new Thread(example::interruptibleMethod, "Thread-2");
thread2.start();
Thread.sleep(2000); // 等待2秒后中断thread2
System.out.println("中断 Thread-2");
thread2.interrupt();
thread1.join();
thread2.join();
}
}
可中断锁特别适用于需要响应用户取消操作的场景,比如GUI应用中的后台任务,或者需要超时控制的服务。
锁超时机制
tryLock()方法提供了非阻塞和超时的锁获取方式,这可以有效避免死锁,并提供更好的用户体验。
非阻塞锁获取
public class TryLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int counter = 0;
public void tryLockMethod() {
if (lock.tryLock()) { // 立即尝试获取锁
try {
counter++;
System.out.println(Thread.currentThread().getName() +
" 获取锁成功,counter = " + counter);
Thread.sleep(1000); // 模拟工作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() +
" 获取锁失败,执行其他逻辑");
// 执行不需要锁的其他逻辑
doAlternativeWork();
}
}
private void doAlternativeWork() {
System.out.println(Thread.currentThread().getName() +
" 执行不需要锁的工作");
}
}
超时锁获取
import java.util.concurrent.TimeUnit;
public class TimeoutLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void timeoutLockMethod() {
try {
// 尝试在3秒内获取锁
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread().getName() +
" 在超时时间内获取锁成功");
Thread.sleep(2000); // 模拟工作
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() +
" 超时未能获取锁");
handleTimeout();
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() +
" 在等待锁时被中断");
Thread.currentThread().interrupt();
}
}
private void handleTimeout() {
System.out.println("处理超时情况,可能需要降级服务或返回错误");
}
}
死锁预防示例
public class DeadlockPrevention {
private final ReentrantLock lock1 = new ReentrantLock();
private final ReentrantLock lock2 = new ReentrantLock();
public void method1() {
try {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("Method1 获取 lock1");
Thread.sleep(100);
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("Method1 获取 lock2");
// 执行需要两个锁的操作
} finally {
lock2.unlock();
}
} else {
System.out.println("Method1 无法获取 lock2,避免死锁");
}
} finally {
lock1.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void method2() {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("Method2 获取 lock2");
Thread.sleep(100);
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("Method2 获取 lock1");
// 执行需要两个锁的操作
} finally {
lock1.unlock();
}
} else {
System.out.println("Method2 无法获取 lock1,避免死锁");
}
} finally {
lock2.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
实战案例:银行转账系统
让我们通过一个银行转账的例子来综合运用Lock接口的各种特性,包括超时机制、死锁预防等。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount {
private final String accountId;
private double balance;
private final ReentrantLock lock = new ReentrantLock();
public BankAccount(String accountId, double initialBalance) {
this.accountId = accountId;
this.balance = initialBalance;
}
// 安全的转账方法,使用超时机制避免死锁
public boolean transfer(BankAccount target, double amount) {
// 为了避免死锁,总是按照账户ID的字典序获取锁
BankAccount firstLock = this.accountId.compareTo(target.accountId) < 0 ? this : target;
BankAccount secondLock = this.accountId.compareTo(target.accountId) < 0 ? target : this;
try {
// 尝试获取第一个锁
if (firstLock.lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 尝试获取第二个锁
if (secondLock.lock.tryLock(1, TimeUnit.SECONDS)) {
try {
return performTransfer(target, amount);
} finally {
secondLock.lock.unlock();
}
} else {
System.out.println("转账失败:无法获取目标账户锁");
return false;
}
} finally {
firstLock.lock.unlock();
}
} else {
System.out.println("转账失败:无法获取源账户锁");
return false;
}
} catch (InterruptedException e) {
System.out.println("转账被中断");
Thread.currentThread().interrupt();
return false;
}
}
private boolean performTransfer(BankAccount target, double amount) {
if (this.balance >= amount) {
this.balance -= amount;
target.balance += amount;
System.out.printf("转账成功:从 %s 向 %s 转账 %.2f,余额:%.2f -> %.2f%n",
this.accountId, target.accountId, amount, this.balance + amount, this.balance);
return true;
} else {
System.out.println("转账失败:余额不足");
return false;
}
}
public double getBalance() {
lock.lock();
try {
return balance;
} finally {
lock.unlock();
}
}
public String getAccountId() {
return accountId;
}
}
测试转账系统
public class BankTransferTest {
public static void main(String[] args) throws InterruptedException {
BankAccount account1 = new BankAccount("A001", 1000.0);
BankAccount account2 = new BankAccount("A002", 1000.0);
BankAccount account3 = new BankAccount("A003", 1000.0);
// 创建多个转账线程
Thread[] threads = new Thread[6];
threads[0] = new Thread(() -> account1.transfer(account2, 100), "Transfer-1-2");
threads[1] = new Thread(() -> account2.transfer(account1, 150), "Transfer-2-1");
threads[2] = new Thread(() -> account1.transfer(account3, 200), "Transfer-1-3");
threads[3] = new Thread(() -> account3.transfer(account1, 50), "Transfer-3-1");
threads[4] = new Thread(() -> account2.transfer(account3, 75), "Transfer-2-3");
threads[5] = new Thread(() -> account3.transfer(account2, 125), "Transfer-3-2");
// 启动所有线程
for (Thread thread : threads) {
thread.start();
}
// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}
// 打印最终余额
System.out.println("\n最终余额:");
System.out.printf("%s: %.2f%n", account1.getAccountId(), account1.getBalance());
System.out.printf("%s: %.2f%n", account2.getAccountId(), account2.getBalance());
System.out.printf("%s: %.2f%n", account3.getAccountId(), account3.getBalance());
}
}
Lock使用最佳实践
- 总是在finally块中释放锁
- 确保每个lock()都有对应的unlock()
- 避免在获取锁之前就进入try块
- 使用tryLock()设置超时时间
- 按固定顺序获取多个锁
- 尽量减少锁的持有时间
- 根据场景选择公平锁或非公平锁
- 考虑使用读写锁分离读写操作
- 在低竞争场景优先使用synchronized
- 忘记释放锁:不在finally块中调用unlock()导致死锁
- 重复释放锁:调用unlock()次数超过lock()次数
- 锁泄漏:在异常情况下没有正确释放锁
- 锁顺序不一致:多个锁的获取顺序不固定导致死锁