1. Lock锁的基本概念与应用场景
Lock锁是多线程编程中的核心同步机制,它解决了传统synchronized关键字的局限性。我第一次真正理解Lock的重要性是在一个电商秒杀项目中——当并发请求达到每秒5000+时,synchronized导致的性能瓶颈让整个系统几乎瘫痪。
与synchronized相比,Lock锁提供了更精细的控制能力。最典型的ReentrantLock实现了可重入锁,允许线程多次获取同一把锁。这在递归调用场景中特别有用,比如遍历树形结构时对节点加锁:
java复制ReentrantLock lock = new ReentrantLock();
void traverse(Node node) {
lock.lock();
try {
// 处理当前节点
for(Node child : node.children) {
traverse(child); // 递归调用仍能获取锁
}
} finally {
lock.unlock();
}
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 主流Lock实现对比与选型
2.1 ReentrantLock的可中断与公平性
ReentrantLock有两个关键特性常被忽视:
- 可中断的锁获取:
lockInterruptibly()方法允许在等待锁时响应中断 - 公平锁模式:构造时传入true启用公平锁,避免线程饥饿
java复制// 公平锁实例
ReentrantLock fairLock = new ReentrantLock(true);
try {
fairLock.lockInterruptibly(); // 可中断的加锁
// 临界区代码
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
} finally {
if(fairLock.isHeldByCurrentThread()) {
fairLock.unlock();
}
}
注意:公平锁会带来约10倍的性能损耗,除非确有强公平性需求,否则建议使用非公平锁
2.2 ReadWriteLock的读写分离
在高读低写场景,ReentrantReadWriteLock能大幅提升吞吐量。其核心规则:
- 读锁可被多个线程同时持有
- 写锁是排他的
- 写锁可降级为读锁(反向则会导致死锁)
java复制ReadWriteLock rwLock = new ReentrantReadWriteLock();
void readData() {
rwLock.readLock().lock();
try {
// 并发读取操作
} finally {
rwLock.readLock().unlock();
}
}
3. Lock的高级用法与性能优化
3.1 条件变量(Condition)的精准控制
Condition实现了线程间的精确唤醒,比Object.wait()/notify()更灵活。典型的生产者-消费者模式实现:
java复制ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 等待"不满"条件
items[putPtr] = x;
notEmpty.signal(); // 唤醒消费者
} finally {
lock.unlock();
}
}
3.2 锁分段技术
ConcurrentHashMap的锁分段设计值得借鉴。将数据分片,每片独立加锁:
java复制// 简化版分段锁实现
class Segment<K,V> extends ReentrantLock {
// 分段存储数据
}
class ConcurrentHashMap<K,V> {
final Segment<K,V>[] segments;
V get(K key) {
int hash = hash(key);
Segment<K,V> seg = segments[hash & SEGMENT_MASK];
seg.lock();
try {
// 访问分段数据
} finally {
seg.unlock();
}
}
}
4. 典型问题排查与实战技巧
4.1 死锁检测与预防
使用jstack检测死锁时,会看到类似输出:
code复制Found one Java-level deadlock:
"Thread-1":
waiting to lock monitor 0x00007f3a4800f2e8 (object 0x000000076ab73e58)
which is held by "Thread-0"
预防死锁的实用技巧:
- 按固定顺序获取多把锁
- 使用tryLock()设置超时
- 用jconsole或VisualVM实时监控
4.2 锁性能瓶颈定位
通过JFR(Java Flight Recorder)可以分析:
- 锁争用热点
- 持有时间过长的锁
- 线程阻塞时间
关键指标:
- 锁等待时间占比 >5%就需要优化
- 单个锁持有时间 >1ms可能有问题
5. 数据库锁的关联实践
5.1 MySQL锁等待超时处理
当出现"Lock wait timeout exceeded"错误时,排查步骤:
- 查询当前锁等待:
sql复制SELECT * FROM information_schema.INNODB_TRX;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
- 分析事务隔离级别
- 检查是否有未提交的长事务
5.2 Oracle的v$lock视图
Oracle中监控锁的关键视图:
sql复制SELECT s.sid, s.serial#, s.username, l.type, l.id1, l.id2
FROM v$session s, v$lock l
WHERE s.sid = l.sid AND s.type != 'BACKGROUND';
6. 分布式锁的实现考量
6.1 Redis分布式锁的陷阱
常见错误实现:
java复制// 错误示范 - 非原子性操作
if(!redis.exists(key)) {
redis.set(key, value);
}
正确姿势应使用SETNX+EXPIRE原子命令:
java复制String result = redis.set(key, value, "NX", "PX", expireTime);
if("OK".equals(result)) {
// 获取锁成功
}
6.2 ZooKeeper的临时顺序节点
实现步骤:
- 创建临时顺序节点
- 检查自己是否是最小节点
- 不是则监听前一个节点
- 处理完删除自身节点
这种方案能避免Redis锁的时钟漂移问题,但性能较低。
