本文目录导读:

分布式锁的核心目标是在分布式系统中,保证多个进程(或线程)对共享资源的互斥访问,在Java中实现分布式锁,通常需要依赖一个外部协调组件(如Redis、ZooKeeper、数据库等)。
以下是使用 Redis 和 ZooKeeper 两种最主流方式实现的详细Java案例。
基于 Redis(推荐,性能高)
Redis 实现分布式锁常用的是 SET NX EX 命令,或者使用成熟的 Redisson 客户端(自动处理续期、重入等问题)。
使用 Redis 原生命令(基础版)
核心思路:利用 Redis 的 SET key value NX EX time 原子命令。
NX:只有当 key 不存在时才能设置成功(互斥性)。EX:设置过期时间(防止死锁)。
代码示例(Jedis客户端):
import redis.clients.jedis.Jedis;
import java.util.Collections;
public class RedisDistributedLock {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX"; // 毫秒
private static final Long RELEASE_SUCCESS = 1L;
private Jedis jedis;
public RedisDistributedLock(Jedis jedis) {
this.jedis = jedis;
}
/**
* 获取锁
* @param lockKey 锁的key
* @param requestId 请求标识(用于防止误解锁,通常使用UUID)
* @param expireTime 过期时间(毫秒)
* @return 是否成功获取
*/
public boolean tryLock(String lockKey, String requestId, int expireTime) {
// 1. SET NX EX 原子操作
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
return LOCK_SUCCESS.equals(result);
}
/**
* 释放锁(使用Lua脚本保证原子性)
* @param lockKey 锁的key
* @param requestId 请求标识(只有持有锁的线程才能释放)
* @return 是否成功释放
*/
public boolean unlock(String lockKey, String requestId) {
// Lua脚本:判断当前锁的值是否等于requestId,等于才删除
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
return RELEASE_SUCCESS.equals(result);
}
}
使用示例:
public class OrderService {
private RedisDistributedLock lock = new RedisDistributedLock(new Jedis("localhost", 6379));
public void processOrder(String orderId) {
String lockKey = "lock:order:" + orderId;
String requestId = UUID.randomUUID().toString(); // 每个线程唯一的标识
try {
boolean locked = lock.tryLock(lockKey, requestId, 30000); // 30秒过期
if (!locked) {
System.out.println("获取锁失败,订单正在处理中...");
return;
}
// 执行业务逻辑...
System.out.println("处理订单:" + orderId);
} finally {
lock.unlock(lockKey, requestId);
}
}
}
注意:基础版需要处理锁续期问题(业务执行时间超过过期时间),建议使用 Redisson。
使用 Redisson(生产级推荐)
Redisson 提供了开箱即用的 RLock,支持自动续期(Watch Dog)、可重入、公平锁等。
Maven依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.0</version>
</dependency>
代码示例:
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import java.util.concurrent.TimeUnit;
public class RedissonLockExample {
public static void main(String[] args) {
// 1. 创建Redisson客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 2. 获取锁对象
RLock lock = redisson.getLock("myLock");
try {
// 3. 尝试加锁,最多等待10秒,锁30秒后自动释放
boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (isLocked) {
System.out.println("获取锁成功,执行业务...");
// 业务逻辑...
} else {
System.out.println("获取锁失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 4. 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
redisson.shutdown();
}
}
优点:
- 自动续期:默认每10秒检查一次,如果业务未完成,自动续期到30秒。
- 可重入:同一个线程可以多次加锁而不会死锁。
- 高可用:支持主从、哨兵、集群模式。
基于 ZooKeeper(强一致性,适合对可靠性要求高的场景)
ZooKeeper 利用临时有序节点和监听机制实现锁。
核心思路:所有线程在 /locks 下创建临时有序节点,获取节点序号最小的那个节点即获得锁;未获得锁的线程监听前一个节点的删除事件。
使用 Curator 框架(推荐)
Curator 是 ZooKeeper 的 Java 高级客户端,提供了 InterProcessMutex 可重入锁。
Maven依赖:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.6.0</version>
</dependency>
代码示例:
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.concurrent.TimeUnit;
public class ZkLockExample {
private static final String ZK_ADDRESS = "127.0.0.1:2181";
private static final String LOCK_PATH = "/distributed-lock";
public static void main(String[] args) {
// 1. 创建Curator客户端
CuratorFramework client = CuratorFrameworkFactory.newClient(ZK_ADDRESS,
new ExponentialBackoffRetry(1000, 3));
client.start();
// 2. 获取锁对象
InterProcessMutex lock = new InterProcessMutex(client, LOCK_PATH);
try {
// 3. 尝试加锁,最多等待5秒
boolean acquired = lock.acquire(5, TimeUnit.SECONDS);
if (acquired) {
System.out.println("获取锁成功");
// 执行业务逻辑...
} else {
System.out.println("获取锁超时");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 4. 释放锁
if (lock.isAcquiredInThisProcess()) {
lock.release();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
优点:
- 强一致性:Zookeeper 的 ZAB 算法保证了数据一致性。
- 自动释放:临时节点,客户端断开后节点自动删除,避免死锁。
- 公平锁:按照节点创建顺序排队。
缺点:
- 性能比 Redis 低(需要磁盘写入和 Leader 选举)。
- 频繁创建/删除节点对 ZK 有一定压力。
三种方案对比总结
| 方案 | 性能 | 一致性 | 实现复杂度 | 典型场景 |
|---|---|---|---|---|
| Redis(Redisson) | 高 | 最终一致(极端情况可能丢锁) | 低 | 高并发、追求性能(电商秒杀、订单防重) |
| ZooKeeper(Curator) | 中 | 强一致 | 中 | 对数据一致性要求极高(配置中心、调度任务) |
| 数据库(行锁/乐观锁) | 低 | 强一致(取决于隔离级别) | 低 | 无需额外组件、依赖业务数据库(简单场景) |
最佳实践建议
- 首选 Redis + Redisson:大多数业务场景下性能最优,功能完善(自动续期、可重入)。
- 锁粒度要细:锁的 key 应该包含业务 ID(如
lock:order:123),而不是全局一个锁。 - 设置合理超时时间:避免因程序异常导致锁一直未释放。
- 必须有防误解锁机制:加锁和解锁使用相同的
requestId(或 UUID),防止误删其他线程的锁。 - 考虑高可用:Redis 使用哨兵/集群模式,Zookeeper 使用集群。