共400行
2026-05-08 13:09:43
Java锁(单机锁)和Redis锁(分布式锁)核心区别:Java锁只管用在同一JVM内多线程互斥;Redis锁用于跨机器/跨服务的分布式互斥。下面从原理、特性、场景、代码示例与选型做清晰对比。
Java锁(单机锁)
synchronized、ReentrantLock、StampedLockRedis锁(分布式锁)
SET key value NX EX)或Lua脚本,多服务抢同一Key;Key存在=锁被占,不存在=可抢| 特性 | Java锁 | Redis锁 |
|---|---|---|
| 适用范围 | 单机JVM内多线程 | 跨机器/跨服务分布式 |
| 性能 | 极高(内存操作,纳秒/微秒级) | 高(网络+Redis,毫秒级) |
| 可靠性 | 无单点问题 | 依赖Redis;主从/Redlock提升可靠 |
| 死锁规避 | 无自动过期;需代码控制 | 过期时间+续期;自动释放 |
| 可重入 | 原生支持(synchronized/ReentrantLock) | Redisson支持;原生需手动 |
| 阻塞/等待 | 原生阻塞/唤醒 | 需自旋/订阅;Redisson封装 |
| 部署成本 | 无额外组件 | 需Redis(集群更稳) |
用Java锁
用Redis锁
private static final ReentrantLock lock=new ReentrantLock();
public void doSomething() {
lock.lock(); // 加锁
try {
// 临界区:同一JVM内仅一线程执行
} finally {
lock.unlock(); // 释放
}
}String key="lock:product:1001";
// NX:不存在才设;EX:10秒过期
boolean ok=redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
if (ok) {
try {
// 分布式临界区:多服务仅一个执行
} finally {
redisTemplate.delete(key); // 释放
}
}RLock lock=redissonClient.getLock("lock:product:1001");
lock.lock(); // 看门狗自动续期(默认30秒)
try {
// 业务
} finally {
lock.unlock();
}问题
synchronized、ReentrantLock 是JVM 级锁,只在当前服务实例内生效。
多实例集群、微服务部署时,每个服务各自加锁,互不干涉,并发穿透、超卖、重复执行。
场景:单机测试正常,上集群直接崩。
解决:分布式场景必须用分布式锁(Redis/Zookeeper)。
问题
ReentrantLock 业务异常直接抛错,不走 unlock(),锁永久持有,线程死锁、接口卡死。
// 错误写法
lock.lock();
// 业务代码异常 → 无法解锁
lock.unlock();规范:lock() 后必须 try-finally 解锁。
多把锁嵌套获取时,不同线程获取顺序相反,互相等待。
解决:统一锁的获取顺序、设置超时锁 tryLock(time)。
// 错误:字符串重新赋值,锁对象变了
private String lockObj = "lock";
synchronized (lockObj) {
lockObj = "newLock";
}原因:锁依赖对象监视器,对象变了锁就彻底无效。
解决:用 final 修饰锁对象。
简单计数、本地缓存更新强行加悲观锁,浪费性能;
高竞争、强一致性场景乱用 CAS 乐观锁,导致自旋空转、CPU 飙高。
错误写法
redis.set(key,val);
redis.expire(key,10);两行命令非原子,若中间服务宕机,key 永久存在,死锁。
正确:使用原子命令 SET NX EX 或 Lua 脚本。
问题
A 加锁,业务执行超时,锁自动过期释放;
B 正常抢到锁;
A 执行完直接 delete key,把 B 的锁删了,并发安全失效。
解决
业务耗时 > 锁过期时间,锁提前失效,多个服务同时进入临界区,超卖/重复下单。
解决方案
问题
Redis 主从、哨兵、集群模式:
解决
手写 Redis 锁用 while + 循环 setNx 死循环抢锁,
大量无效请求打满 Redis,本地 CPU 飙升。
解决
Thread.sleep 控制自旋频率;同一服务同一线程,重复加锁直接死锁。
原因:原生 Redis 锁只是 Key 维度互斥,不识别线程。
解决:直接用 Redisson 可重入锁。
判断 key 是否存在、删除 key 分两步,并发下仍会误删别人锁。
正确:统一用 Lua 脚本实现「判断+删除」原子操作。
Redis Cluster 下,锁 key 分散在不同槽位,
若业务多锁跨槽位,增加锁冲突与异常概率。
优化:关联资源的锁统一用相同 hashTag,固定槽位。
final 锁对象 + try-finally 必释放。SET NX EX 原子指令;下面给你两套生产级安全 Redis 分布式锁模板:
SET NX EX + Lua 解锁,无第三方依赖)setIfAbsent(key, uuid, expire, timeUnit)try-finally 保证解锁import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLock {
@Resource
private RedisTemplate<String, String> redisTemplate;
// 锁过期时间,根据业务调整
private static final long LOCK_EXPIRE_TIME = 30;
private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
// 解锁Lua脚本:判断value一致才删除
private static final String UNLOCK_LUA_SCRIPT =
"if redis.call('get',KEYS[1]) == ARGV[1] then " +
"return redis.call('del',KEYS[1]) " +
"else return 0 end";
/**
* 加锁
* @param lockKey 锁标识
* @return 成功返回唯一标识,失败返回null
*/
public String lock(String lockKey) {
// 唯一标识,防止误删
String lockValue = UUID.randomUUID().toString();
// NX:不存在才上锁 EX:自动过期
boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, LOCK_EXPIRE_TIME, TIME_UNIT);
return success ? lockValue : null;
}
/**
* 解锁
* @param lockKey 锁标识
* @param lockValue 加锁时返回的唯一标识
*/
public void unlock(String lockKey, String lockValue) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(UNLOCK_LUA_SCRIPT);
script.setResultType(Long.class);
// 原子执行:校验+删除
redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
}
}@Service
public class BusinessService {
@Resource
private RedisDistributedLock redisDistributedLock;
public void seckillGoods(Long goodsId) {
String lockKey = "lock:goods:" + goodsId;
String lockValue = null;
try {
// 尝试加锁
lockValue = redisDistributedLock.lock(lockKey);
if (lockValue == null) {
// 加锁失败,直接返回/重试
throw new RuntimeException("操作频繁,请稍后再试");
}
// --------------- 临界区业务 ---------------
// 库存扣减、订单创建、防重复提交等
// -----------------------------------------
} finally {
// 必须解锁
if (lockValue != null) {
redisDistributedLock.unlock(lockKey, lockValue);
}
}
}
}自动解决:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.5</version>
</dependency>@Service
public class RedissonLockService {
@Resource
private RedissonClient redissonClient;
public void doBiz(Long resourceId) {
String lockKey = "lock:biz:" + resourceId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 无参lock:看门狗续期,适合长耗时业务
lock.lock();
// --------------- 业务代码 ---------------
} finally {
// 释放锁
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}// 1. 阻塞加锁,看门狗自动续期(常用)
lock.lock();
// 2. 尝试获取锁,等待5秒,锁15秒自动过期
boolean tryLock = lock.tryLock(5, 15, TimeUnit.SECONDS);set + expire 分开写 → ✅ 原子 setIfAbsent NX EXdelete 删锁 → ✅ Lua 校验 value 再删除