1. Lock接口与并发编程基础
在Java并发编程中,Lock接口提供了比synchronized关键字更灵活的线程同步机制。与synchronized不同,Lock接口允许更精细的控制,包括可中断的锁获取、超时获取锁以及尝试非阻塞获取锁等特性。
Lock接口的核心方法包括:
lock():获取锁,如果锁不可用则等待unlock():释放锁tryLock():尝试非阻塞获取锁lockInterruptibly():可中断地获取锁
java复制public interface Lock {
void lock();
void unlock();
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void lockInterruptibly() throws InterruptedException;
Condition newCondition();
}
与synchronized相比,Lock接口的主要优势在于:
- 可中断性:线程可以被中断等待获取锁
- 超时机制:可以设置获取锁的超时时间
- 公平性:可以选择公平或非公平的锁获取策略
- 多条件变量:一个锁可以关联多个Condition对象
提示:使用Lock时必须手动释放锁,通常放在finally块中确保锁的释放,否则可能导致死锁。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. ReentrantLock深度解析
2.1 ReentrantLock核心实现
ReentrantLock是Lock接口的主要实现类,它基于AQS(AbstractQueuedSynchronizer)框架实现。AQS使用一个volatile int类型的state变量来表示同步状态,并通过一个FIFO队列管理获取锁的线程。
ReentrantLock的可重入性是通过记录当前持有锁的线程和重入次数实现的:
java复制final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
2.2 公平锁与非公平锁
ReentrantLock提供了两种锁获取策略:
- 非公平锁(默认):新请求锁的线程可以插队,可能比等待时间更长的线程先获取锁
- 公平锁:按照线程请求锁的顺序分配锁
java复制// 非公平锁
ReentrantLock nonFairLock = new ReentrantLock();
// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);
公平锁的实现差异主要体现在tryAcquire方法中:
java复制protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && // 检查是否有前驱节点
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ...重入逻辑与非公平锁相同
}
2.3 性能对比与选择建议
在实际应用中,非公平锁通常性能更好,因为:
- 减少了线程切换的开销
- 利用了时间局部性原理,刚释放锁的线程更可能再次获取锁
但在以下情况考虑使用公平锁:
- 需要严格的先来先服务顺序
- 防止线程饥饿
- 锁持有时间较长且竞争激烈
3. ReentrantReadWriteLock实现原理
3.1 读写锁设计思想
ReentrantReadWriteLock将锁分为读锁和写锁:
- 读锁:共享锁,允许多个线程同时持有
- 写锁:独占锁,同一时刻只能有一个线程持有
这种设计特别适合读多写少的场景,可以显著提高并发性能。
3.2 状态设计与锁获取
ReentrantReadWriteLock使用一个32位的int状态变量同时维护读锁和写锁状态:
- 高16位:读锁计数
- 低16位:写锁计数
java复制static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 获取读锁计数
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 获取写锁计数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
3.3 锁降级机制
锁降级是指将写锁降级为读锁的过程,这是ReentrantReadWriteLock的一个重要特性:
java复制// 获取写锁
writeLock.lock();
try {
// 修改数据...
// 获取读锁(锁降级)
readLock.lock();
} finally {
// 释放写锁,但仍持有读锁
writeLock.unlock();
}
try {
// 读取数据...
} finally {
readLock.unlock();
}
锁降级的意义在于:
- 保证数据可见性:确保其他线程看到的是最新修改的数据
- 提高并发性:在保持数据一致性的同时允许其他读操作并行
注意:ReentrantReadWriteLock不支持锁升级(读锁升级为写锁),因为这容易导致死锁。
4. 并发场景实践与性能优化
4.1 缓存实现案例
下面是一个使用ReentrantReadWriteLock实现的线程安全缓存:
java复制public class Cache<K, V> {
private final Map<K, V> map = new HashMap<>();
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public V get(K key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
}
}
public void put(K key, V value) {
w.lock();
try {
map.put(key, value);
} finally {
w.unlock();
}
}
public V computeIfAbsent(K key, Function<K, V> mappingFunction) {
V value;
r.lock();
try {
value = map.get(key);
if (value != null) {
return value;
}
} finally {
r.unlock();
}
w.lock();
try {
// 再次检查,防止其他线程已经修改
value = map.get(key);
if (value == null) {
value = mappingFunction.apply(key);
map.put(key, value);
}
return value;
} finally {
w.unlock();
}
}
}
4.2 性能调优建议
-
锁粒度控制:
- 尽量缩小锁的代码块范围
- 考虑使用分段锁减少竞争
-
锁选择策略:
- 读多写少:优先考虑ReentrantReadWriteLock
- 写多读少:考虑使用普通ReentrantLock
- 极高并发:考虑使用StampedLock的乐观读
-
避免常见陷阱:
- 防止死锁:按固定顺序获取多个锁
- 避免锁泄露:确保在finally块中释放锁
- 减少锁持有时间:将耗时操作移出同步块
4.3 锁监控与诊断
在实际生产环境中,可以使用以下工具监控锁状态:
- JVisualVM:查看线程状态和锁持有情况
- JStack:生成线程转储分析锁竞争
- Java Mission Control:提供详细的锁分析
诊断锁问题的常用方法:
java复制// 检查锁是否被当前线程持有
boolean isHeldByCurrentThread = lock.isHeldByCurrentThread();
// 获取等待锁的线程数
int queueLength = lock.getQueueLength();
// 判断是否是公平锁
boolean isFair = lock.isFair();
5. 高级特性与替代方案
5.1 Condition条件变量
Condition提供了比Object.wait/notify更灵活的线程等待/通知机制:
java复制public class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
5.2 StampedLock性能优化
Java 8引入了StampedLock,它在特定场景下比ReentrantReadWriteLock性能更好:
java复制public class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead();
double currentX = x, currentY = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
StampedLock的特点:
- 乐观读:不阻塞写操作
- 支持锁转换:读锁可以转换为写锁
- 不支持重入
- 没有公平性选择
5.3 并发容器选择
根据不同的并发需求,Java提供了多种并发容器:
- CopyOnWriteArrayList:适合读多写少的List场景
- ConcurrentHashMap:高并发Map实现
- ConcurrentLinkedQueue:无界非阻塞队列
- BlockingQueue实现类:
- ArrayBlockingQueue:有界阻塞队列
- LinkedBlockingQueue:可选有界阻塞队列
- PriorityBlockingQueue:优先级阻塞队列
- SynchronousQueue:不存储元素的阻塞队列
在实际项目中,我发现合理选择并发容器往往比直接使用锁能获得更好的性能和更简洁的代码。例如,对于大多数缓存场景,ConcurrentHashMap的性能通常优于手动使用ReentrantReadWriteLock实现的Map。
