1. 概述
简单来说,Lock 是一种比标准 synchronized
块更灵活、更强大的线程同步机制。
Lock 接口自 Java 1.5 起就存在了。它定义在 java.util.concurrent.lock
包中,提供了丰富的锁操作能力。
本教程将探讨 Lock 接口的不同实现及其应用场景。
2. Lock 与 synchronized 块的差异
使用 synchronized
块和 Lock API 有几个关键区别:
作用范围限制
synchronized
块必须完全包含在方法内。而 Lock API 的lock()
和unlock()
操作可以分散在不同方法中。公平性支持
synchronized
块不支持公平性。锁释放后任何线程都可能获取,无法指定优先级。Lock API 可通过fairness
属性实现公平性,确保等待时间最长的线程优先获取锁。非阻塞获取
线程无法获取synchronized
块时会被阻塞。Lock API 提供tryLock()
方法,线程仅在锁可用时获取,避免不必要的阻塞。可中断等待
等待synchronized
块的线程无法被中断。Lock API 提供lockInterruptibly()
方法,允许中断等待锁的线程。
3. Lock 接口方法
Lock 接口的核心方法包括:
void lock()
获取锁(若可用)。若锁不可用,线程将阻塞直到锁被释放。void lockInterruptibly()
类似lock()
,但允许阻塞线程被中断并抛出InterruptedException
。boolean tryLock()
lock()
的非阻塞版本。尝试立即获取锁,成功返回 true。boolean tryLock(long timeout, TimeUnit timeUnit)
增强版tryLock()
,等待指定超时时间后放弃获取锁。void unlock()
释放锁实例。
踩坑提醒:锁定的实例必须显式解锁,否则会导致死锁。推荐使用
try-finally
结构:Lock lock = ...; lock.lock(); try { // 访问受保护资源 } finally { lock.unlock(); }
除 Lock 接口外,还有 ReadWriteLock
接口,它维护一对锁:
- 读锁(支持多线程并发读)
- 写锁(独占式写操作)
ReadWriteLock 的关键方法:
Lock readLock()
- 返回读操作锁Lock writeLock()
- 返回写操作锁
4. 锁实现
4.1 ReentrantLock
ReentrantLock
实现了 Lock 接口,提供与 synchronized
相同的并发和内存语义,但功能更强大。
基本用法示例:
public class ReentrantLockExample {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
tryLock() 超时机制:
public class TryLockExample {
private final Lock lock = new ReentrantLock();
public void tryLockExample() throws InterruptedException {
boolean isLocked = lock.tryLock(1, TimeUnit.SECONDS);
if (isLocked) {
try {
// 访问受保护资源
} finally {
lock.unlock();
}
} else {
// 执行备用逻辑
}
}
}
调用
tryLock(1, SECONDS)
的线程会等待 1 秒,超时后自动放弃。
4.2 ReentrantReadWriteLock
ReentrantReadWriteLock
实现 ReadWriteLock 接口,遵循以下规则:
读锁获取
当没有线程持有写锁或请求写锁时,多个线程可获取读锁。写锁获取
仅当没有线程在读或写时,单个线程可获取写锁。
完整示例:
public class ReadWriteLockExample {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
// 写操作(独占)
public void writeOperation() {
writeLock.lock();
try {
// 临界区写操作
} finally {
writeLock.unlock();
}
}
// 读操作(共享)
public void readOperation() {
readLock.lock();
try {
// 临界区读操作
} finally {
readLock.unlock();
}
}
}
4.3 StampedLock
Java 8 引入的 StampedLock
支持读写锁,但采用 stamp(票据)机制管理锁状态:
public class StampedLockExample {
private final StampedLock stampedLock = new StampedLock();
public void writeOperation() {
long stamp = stampedLock.writeLock();
try {
// 写操作
} finally {
stampedLock.unlockWrite(stamp);
}
}
public void readOperation() {
long stamp = stampedLock.readLock();
try {
// 读操作
} finally {
stampedLock.unlockRead(stamp);
}
}
}
乐观锁特性:读操作通常无需等待写操作完成,可通过乐观读升级:
public void optimisticRead() {
long stamp = stampedLock.tryOptimisticRead();
// 非阻塞读取数据
if (!stampedLock.validate(stamp)) {
// 数据被修改,升级为读锁
stamp = stampedLock.readLock();
try {
// 重新读取数据
} finally {
stampedLock.unlockRead(stamp);
}
}
// 使用读取的数据
}
5. 使用 Condition
Condition
类允许线程在临界区内等待特定条件。当线程获取锁但缺少执行条件时(如队列空时读线程等待),传统方法使用 wait()/notify()
,而 Condition 支持多条件控制:
public class ConditionExample {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void awaitCondition() throws InterruptedException {
lock.lock();
try {
condition.await(); // 等待条件
} finally {
lock.unlock();
}
}
public void signalCondition() {
lock.lock();
try {
condition.signal(); // 通知等待线程
} finally {
lock.unlock();
}
}
}
6. 总结
本文探讨了 Lock 接口的不同实现(包括 Java 8 的 StampedLock)以及 Condition 类在多条件控制中的应用。相比 synchronized
,Lock API 提供了更精细的锁控制能力,是构建高性能并发系统的利器。
文中完整示例代码可在 GitHub 上 找到。