本文目录导读:

我来介绍Java实现Redis分布式锁的几种常见方案:
基于SETNX + EXPIRE (基础版)
import redis.clients.jedis.Jedis;
public class RedisDistributedLock {
private Jedis jedis;
private String lockKey;
private String lockValue;
private int expireTime = 3000; // 锁过期时间,单位毫秒
public RedisDistributedLock(Jedis jedis, String lockKey) {
this.jedis = jedis;
this.lockKey = lockKey;
this.lockValue = String.valueOf(System.currentTimeMillis());
}
/**
* 获取锁
*/
public boolean tryLock() {
// SET NX 命令:key不存在时才设置
String result = jedis.set(lockKey, lockValue, "NX", "PX", expireTime);
return "OK".equals(result);
}
/**
* 释放锁
*/
public void unlock() {
// 使用Lua脚本确保原子性操作
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
jedis.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(lockValue));
}
}
使用Redisson (推荐方案)
<!-- 添加依赖 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.7</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 RedissonLockService {
private RedissonClient redissonClient;
public RedissonLockService() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("password")
.setDatabase(0);
redissonClient = Redisson.create(config);
}
/**
* 使用Redisson实现分布式锁
*/
public void executeWithLock(String key, Runnable task) {
RLock lock = redissonClient.getLock(key);
try {
// 尝试获取锁,最多等待100秒,锁自动释放30秒
boolean isLocked = lock.tryLock(100, 30, TimeUnit.SECONDS);
if (isLocked) {
System.out.println("获取锁成功,执行业务逻辑");
task.run();
} else {
System.out.println("获取锁失败");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("锁操作被中断");
} finally {
// 释放锁(Redisson自动处理)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 使用示例
public void businessMethod() {
executeWithLock("my_lock_key", () -> {
// 需要加锁的业务逻辑
System.out.println("执行业务操作...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
使用Spring Data Redis + Lua脚本
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class SpringRedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// Lua脚本:原子性释放锁
private static final String UNLOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
/**
* 获取分布式锁
* @param lockKey 锁的key
* @param expireTime 过期时间(秒)
* @return 锁标识(用于释放锁)
*/
public String tryLock(String lockKey, long expireTime) {
String lockValue = UUID.randomUUID().toString();
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, expireTime, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(success)) {
return lockValue; // 返回锁标识
}
return null; // 获取锁失败
}
/**
* 释放分布式锁
* @param lockKey 锁的key
* @param lockValue 锁标识
* @return 是否释放成功
*/
public boolean unlock(String lockKey, String lockValue) {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(UNLOCK_SCRIPT);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(
redisScript,
Collections.singletonList(lockKey),
lockValue
);
return Long.valueOf(1).equals(result);
}
// 使用示例
public void businessWithLock() {
String lockKey = "business_lock";
String lockValue = tryLock(lockKey, 30);
if (lockValue != null) {
try {
// 获取锁成功,执行业务逻辑
System.out.println("执行业务操作...");
} finally {
// 释放锁
unlock(lockKey, lockValue);
}
} else {
System.out.println("获取锁失败,稍后重试");
}
}
}
完整的使用案例(订单防重复提交)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Jedis;
@Service
public class OrderService {
@Autowired
private JedisPool jedisPool;
/**
* 创建订单(防重复提交)
*/
public String createOrder(String userId, String orderId) {
String lockKey = "order_lock:" + userId;
String lockValue = orderId;
Jedis jedis = jedisPool.getResource();
try {
// 尝试获取锁,设置过期时间10秒
String result = jedis.set(lockKey, lockValue, "NX", "EX", 10);
if (!"OK".equals(result)) {
return "订单正在处理中,请勿重复提交";
}
// 检查是否重复提交(业务逻辑)
// ...
// 执行业务逻辑
Thread.sleep(3000);
return "订单创建成功";
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "订单处理中断";
} finally {
// 安全释放锁
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
jedis.eval(script,
Collections.singletonList(lockKey),
Collections.singletonList(lockValue));
if (jedis != null) {
jedis.close();
}
}
}
}
关键要点
- 原子性:使用Redis的NX + PX/EX参数确保原子操作
- 锁标识:使用UUID等唯一标识防止误删其他线程的锁
- 过期时间:设置合理的过期时间防止死锁
- Lua脚本:释放锁时使用Lua脚本确保原子性
- 推荐Redisson:生产环境建议使用Redisson,它提供了更完善的实现
注意事项
- 锁超时要根据业务执行时间合理设置
- 考虑Redis集群下的一致性问题
- 高并发场景下注意性能优化
- 业务代码做好重试和降级处理