1️⃣ 基于 SETNX 实现分布式锁

1. 🔹 SETNX 的基本原理

SETNX(SET if Not Exists)用于 当 key 不存在时,才设置值:

SETNX lock_key unique_id  # 只有 lock_key 不存在时才会设置成功

但是 SETNX 只会设置 key,并不会自动过期。因此,如果客户端崩溃,锁可能会永远不会释放,导致死锁。
解决方案:使用 SET 命令 + EX 过期时间

SET lock_key unique_id NX EX 10  # 仅当 lock_key 不存在时设置,并 10 秒后自动释放

2. 🔹 加锁


String lockKey = "lock_key";
String uniqueId = UUID.randomUUID().toString();
boolean lockAcquired = jedis.set(lockKey, uniqueId, "NX", "EX", 10) != null;
  • NX:如果 lock_key 不存在,则创建(避免覆盖其他线程的锁)。
  • EX 10:10 秒后锁自动释放,防止死锁。

3. 🔹 释放锁(用 Lua 脚本保证原子性)

if redis.call("GET", KEYS[1]) == ARGV[1] then
  return redis.call("DEL", KEYS[1])
else
  return 0
end

Java 代码:

String luaScript = 
"if redis.call('GET', KEYS[1]) == ARGV[1] then " +
"   return redis.call('DEL', KEYS[1]) " +
"else " +
"   return 0 " +
"end";
Object result = jedis.eval(luaScript, Collections.singletonList(lockKey), Collections.singletonList(uniqueId));

✅ Lua 脚本确保 GET 和 DEL 在同一事务中执行,避免锁被误删。

4. 🔹 存在的问题

  1. 锁过期时任务未完成,可能导致锁失效(解决方案:滑块锁)。
  2. 不支持重入,同一线程无法重复加锁(解决方案:Redisson)。
  3. 锁可能被误删(解决方案:用 Lua 脚本释放)。

2️⃣ 使用 Redisson 实现分布式锁

Redisson 是基于 Redis 实现的 Java 分布式锁组件,支持:

  • 可重入锁(Reentrant Lock)
  • 读写锁(ReadWrite Lock)
  • 公平锁
  • 联锁 / 红锁
  • 看门狗自动续期(防止锁过期)

🔹 Redisson 加锁


Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);

RLock lock = redisson.getLock("lock_key");
try {
    lock.lock(); // 获取锁(默认 30 秒自动续约)
    System.out.println("加锁成功,执行任务...");
    Thread.sleep(15000); // 模拟任务执行
} finally {
    lock.unlock(); // 释放锁
}

✅ 支持重入锁,同一线程可以多次 lock(),只需 unlock() 一次即可释放。

🔹 看门狗机制(自动续约)

Redisson 默认有 30 秒超时时间,并会 每 10 秒自动续约:

  • 如果任务还在执行,锁不会过期。
  • 如果任务完成,调用 unlock() 释放锁。

lock.lock(10, TimeUnit.SECONDS); // 10 秒后自动释放(无看门狗)
lock.lock(); // 默认 30 秒超时 + 看门狗续期

Redisson 的各种锁机制

可重入锁(Reentrant Lock)

🔹 概念

  • 同一线程可以多次加锁,必须释放相同次数才能完全释放锁。
  • 解决了普通 SETNX 无法重复加锁的问题,防止死锁。

🔹 工作原理

  • 使用 Redis hash 结构,存储 锁的持有线程 ID 和重入次数。
  • 每次相同线程加锁时,count +1;每次释放锁时,count -1,当 count == 0 时,真正释放锁。

🔹 代码示例

RLock lock = redisson.getLock("reentrant_lock");
try {
    lock.lock();  // 第一次加锁
    lock.lock();  // 第二次加锁(重入)
    System.out.println("锁被成功获取");
} finally {
    lock.unlock(); // 释放一次锁
    lock.unlock(); // 释放第二次锁(真正释放)
}

🔹 实际应用

  • 支付扣款
    • 防止用户多次支付导致金额扣减错误
    • 线程 A:调用 lock.lock() 进行扣款
    • 线程 A 还需要检查库存,也需要 lock.lock(),如果不支持重入锁,则会死锁

读写锁(ReadWrite Lock)

🔹 概念

  • 多个线程可以同时读
  • 写操作必须互斥
  • 适用于高读低写的场景,避免写操作影响读性能。

🔹 工作原理

  • 读锁(共享锁):多个线程可以同时 lock.readLock().lock(),提高并发能力。
  • 写锁(独占锁):当有线程 lock.writeLock().lock() 时,其他读/写操作都要等待。

🔹 代码示例

RReadWriteLock rwLock = redisson.getReadWriteLock("rw_lock");

// 读锁(多个线程可以同时读)
rwLock.readLock().lock();
try {
    System.out.println("正在读取数据...");
} finally {
    rwLock.readLock().unlock();
}

// 写锁(写入时,其他线程不能读)
rwLock.writeLock().lock();
try {
    System.out.println("正在写入数据...");
} finally {
    rwLock.writeLock().unlock();
}

🔹 实际应用

  • 商品库存查询
    • 读多写少的场景:允许多个线程并发读取库存数据
    • 防止写操作时数据不一致
  • 秒杀活动
    • 用户可以同时查看商品详情(读锁)
    • 当库存减少(写锁)时,读操作必须等待,保证数据一致性

公平锁(Fair Lock)

🔹 概念

  • 先请求的线程,先获取锁(FIFO 先进先出)
  • 适用于高并发业务,防止某些线程长时间等待锁。

🔹 工作原理

  • 非公平锁:默认情况下,Redis 直接尝试 SETNX,谁先抢到算谁的。
  • 公平锁:
    • 使用 Redis List 存储排队的线程
    • 按顺序唤醒等待时间最长的线程,保证公平性。

🔹 代码示例

RLock fairLock = redisson.getFairLock("fair_lock");
try {
    fairLock.lock();
    System.out.println("获取公平锁成功");
} finally {
    fairLock.unlock();
}

🔹 实际应用

  • 秒杀抢购
    • 传统非公平锁可能让某些用户连续抢到商品,导致部分用户一直抢不到
    • 公平锁可以按用户请求时间排序,防止饥饿
  • 分布式任务调度
    • 避免任务调度时,某些任务长期得不到执行

联锁(MultiLock)

🔹 概念

  • 需要同时获取多个锁才能成功加锁,防止单点故障。
  • 适用于多个 Redis 实例的分布式环境。

🔹 工作原理

  • 同时对多个 Redis 实例 执行 SETNX,只有全部成功才算加锁成功。
  • 防止 Redis 宕机导致锁失效。

🔹 代码示例

RLock lock1 = redisson.getLock("lock1");
RLock lock2 = redisson.getLock("lock2");
RLock lock3 = redisson.getLock("lock3");

// 获取多个锁
RLock multiLock = redisson.getMultiLock(lock1, lock2, lock3);
try {
    multiLock.lock();
    System.out.println("成功获取多个锁");
} finally {
    multiLock.unlock();
}

🔹 实际应用

  • 跨数据中心的分布式锁
    • 例如:你有多个 Redis 服务器(中国、美国、欧洲),你希望所有数据中心都能加锁,防止某个 Redis 挂掉导致锁失效。

红锁(RedLock)

🔹 概念

  • 保证分布式锁的高可用性
  • 适用于 多个 Redis 实例 的环境

🔹 工作原理

  • 采用 N 个 Redis 实例,加锁时必须在 大多数(N/2+1)个实例 上成功,才算真正加锁成功。
  • 这样,即使部分 Redis 宕机,锁仍然有效。

🔹 代码示例

RedissonClient redisson1 = createRedissonClient("redis://127.0.0.1:6379");
RedissonClient redisson2 = createRedissonClient("redis://127.0.0.2:6379");
RedissonClient redisson3 = createRedissonClient("redis://127.0.0.3:6379");

RLock redLock = redisson1.getRedLock(
    redisson1.getLock("red_lock"),
    redisson2.getLock("red_lock"),
    redisson3.getLock("red_lock")
);

try {
    redLock.lock();
    System.out.println("获取红锁成功");
} finally {
    redLock.unlock();
}

🔹 实际应用

  • 银行转账
    • 确保多个数据中心的账户不会因为 Redis 宕机而重复转账
  • 高可用微服务
    • 微服务 A/B/C 访问相同资源,确保 Redis 宕机时仍然可以加锁

看门狗自动续期

🔹 概念

  • 防止锁过期,导致任务中断
  • Redisson 默认锁 30 秒,如果任务未完成,会自动续期

🔹 工作原理

  • 每 10 秒,Redisson 检查锁是否还在使用
  • 如果任务还未完成,就自动 续期 30 秒
  • 任务完成后,unlock() 释放锁

🔹 代码示例

RLock lock = redisson.getLock("auto_extend_lock");
try {
    lock.lock(); // 看门狗自动续期
    System.out.println("执行任务...");
    Thread.sleep(20000); // 任务超过 30s 也不会丢失锁
} finally {
    lock.unlock();
}

🔹 实际应用

  • 防止任务执行超时
    • 适用于 爬虫任务、视频转码 等长时间任务
  • 防止 Redis 宕机导致任务失败
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐