Redisson那么多锁,你都用对了吗?
前言
上周五快下班的时候,实习生小袁抱着电脑走过来,一脸疑惑。
“师兄,有点事想请教。”他挠着头说,“我在读项目代码,发现各种地方用的锁都不一样。订单用的是 getLock()
,库存模块用 getReadWriteLock()
,转账那边竟然还有 getMultiLock()
,看得我脑壳疼。”
我本来已经开始关电脑了,一听倒来了点兴趣:“感觉用得太复杂了?”
“是啊,为啥不能统一用一种锁呢?我也搞不清楚各自到底是干嘛的。”
这个问题问得挺好。我刚入行的时候也以为“加个锁”就完事,后来踩过不少坑才发现:锁不是越多越复杂,而是用对了才有意义。
“那就趁现在不忙,我跟你讲讲 Redisson 提供的这些锁到底该怎么选。”
1. 可重入锁(Reentrant Lock):适合大多数普通场景
特性:同一个线程可以多次获得同一把锁,不会造成死锁。
优化案例:防止重复下单
RLock lock = redissonClient.getLock("order:lock:" + userId);
lock.lock();
try {
if (!hasOrdered(userId)) {
createOrder(userId);
}
} finally {
lock.unlock();
}
适合业务逻辑简单、只需防止并发执行的场景,比如防止重复下单、重复提交任务。
2. 公平锁(Fair Lock):保证先来先得
特性:维护请求队列,严格按顺序获得锁。
优化案例:按顺序处理订单
RLock fairLock = redissonClient.getFairLock("order:fairLock");
fairLock.lock();
try {
processOrder(orderId);
} finally {
fairLock.unlock();
}
适用于对顺序敏感的场景,如排队叫号、按用户提交顺序处理请求。
3. 联锁(MultiLock):多个资源同时加锁
特性:原子性地锁定多个资源,避免死锁。
优化案例:跨账户转账
RLock fromLock = redissonClient.getLock("account:" + fromId);
RLock toLock = redissonClient.getLock("account:" + toId);
RLock multiLock = redissonClient.getMultiLock(fromLock, toLock);
multiLock.lock();
try {
transfer(fromId, toId, amount);
} finally {
multiLock.unlock();
}
适合多个资源需要一致性修改的场景,如转账、合并库存等。
4. 红锁(RedLock):高可用分布式锁
特性:在多个独立 Redis 节点上加锁,需大多数节点成功。
优化案例:金融系统中的敏感操作
RLock lock1 = redissonClient1.getLock("txnLock");
RLock lock2 = redissonClient2.getLock("txnLock");
RLock lock3 = redissonClient3.getLock("txnLock");
RLock redLock = redisson.getRedLock(lock1, lock2, lock3);
redLock.lock();
try {
processCriticalTransaction();
} finally {
redLock.unlock();
}
适用于容忍部分 Redis 节点故障仍能保持锁有效的场景,如金融级系统。
5. 读写锁(ReadWrite Lock):提升读取性能
特性:读锁可共享,写锁独占。读多写少场景非常高效。
优化案例:商品详情缓存更新
RReadWriteLock rwLock = redissonClient.getReadWriteLock("product:" + productId);
RLock readLock = rwLock.readLock();
readLock.lock();
try {
Product data = getFromCache(productId);
display(data);
} finally {
readLock.unlock();
}
读多写少场景极为常见,如配置、商品信息、用户画像等缓存场景。
6. 信号量(Semaphore):用于限流控制
特性:控制可并发访问的资源数量。
优化案例:限制同时处理的订单数
RSemaphore semaphore = redissonClient.getSemaphore("order:limit");
semaphore.trySetPermits(100);
semaphore.acquire();
try {
handleOrderRequest();
} finally {
semaphore.release();
}
适合控制并发量,如限流接口、线程池、数据库连接池等。
7. 可过期信号量(PermitExpirableSemaphore):防止资源泄漏
特性:许可证具有有效期,自动回收。
优化案例:调用第三方服务时自动释放
RPermitExpirableSemaphore expSemaphore = redissonClient.getPermitExpirableSemaphore("api:access");
expSemaphore.trySetPermits(5);
String permitId = expSemaphore.acquire(10, TimeUnit.SECONDS);
try {
callExternalAPI();
} finally {
if (permitId != null) {
expSemaphore.release(permitId);
}
}
适合不确定是否能正常释放许可证的场景,例如网络调用失败、中断等。
8. 闭锁(CountDownLatch):等待多个任务完成
特性:线程等待直到其他线程完成任务。
优化案例:服务启动依赖多个模块初始化
RCountDownLatch latch = redissonClient.getCountDownLatch("startup:latch");
latch.trySetCount(3);
// 在子模块初始化完成后调用
latch.countDown();
// 在主线程中等待
latch.await();
用于分布式任务聚合、服务初始化同步等场景。
9. 自旋锁(Spin Lock):超短临界区的锁优化
特性:不停尝试获取锁,不休眠,占用CPU。
优化案例:高频短操作,如内存计数
RSpinLock spinLock = redissonClient.getSpinLock("counter:lock");
spinLock.lock();
try {
incrementInMemory();
} finally {
spinLock.unlock();
}
适合执行时间极短的操作,尽量避免在分布式系统中使用。
锁的组合使用建议
组合使用锁是提高系统性能与可靠性的重要手段。
优化案例:读操作限流 + 保证一致性
RSemaphore semaphore = redissonClient.getSemaphore("query:limit");
RReadWriteLock rwLock = redissonClient.getReadWriteLock("data:lock");
semaphore.acquire();
rwLock.readLock().lock();
try {
queryData();
} finally {
rwLock.readLock().unlock();
semaphore.release();
}
注意顺序:
- 获取锁顺序:限流 → 业务锁
- 释放顺序:业务锁 → 限流
避免因锁顺序错误导致死锁。
性能对比与选型建议
类型 | 特性 | 场景推荐 | 性能 |
---|---|---|---|
可重入锁 | 最常用,简单易用 | 防重复操作、普通业务 | ⭐⭐⭐⭐⭐ |
公平锁 | 严格顺序 | 排队系统、先到先服务 | ⭐⭐⭐ |
读写锁 | 分离读写,提升性能 | 缓存、配置、商品信息 | ⭐⭐⭐⭐ |
联锁 | 同时锁多个资源 | 转账、合并操作 | ⭐⭐ |
红锁 | 多节点高可用 | 金融系统、分布式关键任务 | ⭐⭐ |
信号量 | 并发控制 | 限流、连接池、批量处理 | ⭐⭐⭐ |
过期信号量 | 自动释放许可 | 第三方服务调用 | ⭐⭐⭐ |
闭锁 | 同步等待机制 | 初始化、分布式任务聚合 | ⭐⭐⭐ |
自旋锁 | 高频极短操作 | 内存操作 | ⭐⭐(需慎用) |
结语
讲了这么多,小袁已经记得满纸笔记。
“现在知道项目里为什么用了这么多不同的锁了吧?”我问。
“嗯,懂了!”小袁点点头,“普通业务用可重入锁,配置多用读写锁,转账要用联锁,金融操作得靠红锁……”
“对,就是这么个理儿。”我拍了拍他的肩,“加锁不是为了复杂,而是为了稳。”
他笑了笑,准备走,又回头问:“那有没有捷径能快速掌握这些?”
我想了想,说:“多看看别人怎么用,多在项目中试试。纸上得来终觉浅,实践才是硬道理。”
如果你还在为选哪种锁而困惑,不妨对照你的场景,从这些锁里找一个“刚刚好”的就够了。不要贪多,也别怕用错。理解业务、合理选型,才是架构优化的第一步。