在Java中使用Redis实现分布式锁主要有两种方式:一种是使用Redisson客户端库,另一种是使用Jedis/Lettuce配合Lua脚本。下面分别介绍这两种实现方式:
1. 使用Redisson实现(推荐)
Redisson是一个成熟的Redis客户端,内置了分布式锁的实现。
1.1 添加依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.0</version>
</dependency>
1.2 配置Redisson客户端
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
public class RedissonConfig {
public static RedissonClient getRedissonClient() {
Config config = new Config();
// 单节点模式
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("your_password")
.setDatabase(0);
// 集群模式
// config.useClusterServers()
// .addNodeAddress("redis://127.0.0.1:7001", "redis://127.0.0.1:7002");
return Redisson.create(config);
}
}
1.3 使用分布式锁
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
public class DistributedLockWithRedisson {
private final RedissonClient redissonClient;
public DistributedLockWithRedisson(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
/**
* 尝试获取锁
*/
public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 释放锁
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
/**
* 示例:使用锁执行业务逻辑
*/
public void executeWithLock(String lockKey, Runnable task) {
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,最多等待10秒,锁持有时间30秒
boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (locked) {
task.run();
} else {
throw new RuntimeException("获取锁失败");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁被中断", e);
} finally {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 可重入锁示例
*/
public void reentrantLockExample() {
String lockKey = "order:123";
RLock lock = redissonClient.getLock(lockKey);
try {
// 第一次加锁
lock.lock();
// 在同一个线程中再次获取锁(可重入)
lock.lock();
try {
// 执行业务逻辑
System.out.println("执行业务逻辑");
} finally {
lock.unlock(); // 释放内层锁
}
} finally {
lock.unlock(); // 释放外层锁
}
}
}
2. 使用Jedis + Lua脚本实现
2.1 添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
2.2 实现分布式锁工具类
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;
import java.util.Collections;
import java.util.UUID;
public class RedisDistributedLock {
private final JedisPool jedisPool;
public RedisDistributedLock(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
// 解锁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";
// 续期Lua脚本
private static final String RENEW_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('pexpire', KEYS[1], ARGV[2]) " +
"else " +
" return 0 " +
"end";
/**
* 尝试获取锁
* @param lockKey 锁的key
* @param requestId 请求标识(可以使用UUID)
* @param expireTime 过期时间(毫秒)
* @return 是否获取成功
*/
public boolean tryLock(String lockKey, String requestId, long expireTime) {
try (Jedis jedis = jedisPool.getResource()) {
SetParams params = SetParams.setParams()
.nx() // 仅当key不存在时设置
.px(expireTime); // 设置过期时间(毫秒)
String result = jedis.set(lockKey, requestId, params);
return "OK".equals(result);
}
}
/**
* 阻塞获取锁(自旋)
*/
public boolean lock(String lockKey, String requestId,
long expireTime, long timeout) throws InterruptedException {
long endTime = System.currentTimeMillis() + timeout;
while (System.currentTimeMillis() < endTime) {
if (tryLock(lockKey, requestId, expireTime)) {
return true;
}
Thread.sleep(100); // 等待100ms后重试
}
return false;
}
/**
* 释放锁
*/
public boolean unlock(String lockKey, String requestId) {
try (Jedis jedis = jedisPool.getResource()) {
Object result = jedis.eval(UNLOCK_SCRIPT,
Collections.singletonList(lockKey),
Collections.singletonList(requestId));
return Long.valueOf(1L).equals(result);
}
}
/**
* 锁续期
*/
public boolean renewLock(String lockKey, String requestId, long expireTime) {
try (Jedis jedis = jedisPool.getResource()) {
Object result = jedis.eval(RENEW_SCRIPT,
Collections.singletonList(lockKey),
Arrays.asList(requestId, String.valueOf(expireTime)));
return Long.valueOf(1L).equals(result);
}
}
/**
* 使用示例
*/
public void example() {
String lockKey = "order:process:123";
String requestId = UUID.randomUUID().toString();
try {
// 尝试获取锁,过期时间30秒
if (tryLock(lockKey, requestId, 30000)) {
try {
// 执行业务逻辑
System.out.println("获取锁成功,执行业务逻辑");
// 如果需要,可以启动一个守护线程进行锁续期
startRenewalThread(lockKey, requestId, 30000);
} finally {
// 释放锁
unlock(lockKey, requestId);
}
} else {
System.out.println("获取锁失败");
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 启动锁续期线程
*/
private void startRenewalThread(String lockKey, String requestId, long expireTime) {
Thread renewalThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(expireTime / 3); // 每过期时间的1/3续期一次
if (!renewLock(lockKey, requestId, expireTime)) {
break; // 续期失败,退出
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
renewalThread.setDaemon(true);
renewalThread.start();
}
}
3. 使用Spring Boot + Lettuce实现
3.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.2 配置类
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
@Bean
public RedisLockHelper redisLockHelper(RedisTemplate<String, Object> redisTemplate) {
return new RedisLockHelper(redisTemplate);
}
}
3.3 分布式锁工具类
@Component
public class RedisLockHelper {
private final RedisTemplate<String, Object> redisTemplate;
public RedisLockHelper(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
private static final DefaultRedisScript<Long> RENEW_SCRIPT;
static {
// 初始化解锁脚本
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setScriptText(
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end");
UNLOCK_SCRIPT.setResultType(Long.class);
// 初始化续期脚本
RENEW_SCRIPT = new DefaultRedisScript<>();
RENEW_SCRIPT.setScriptText(
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('pexpire', KEYS[1], ARGV[2]) " +
"else " +
" return 0 " +
"end");
RENEW_SCRIPT.setResultType(Long.class);
}
/**
* 获取锁
*/
public boolean acquireLock(String lockKey, String requestId, long expireMillis) {
return Boolean.TRUE.equals(redisTemplate.execute(
(RedisConnection connection) ->
connection.set(
lockKey.getBytes(),
requestId.getBytes(),
Expiration.milliseconds(expireMillis),
RedisStringCommands.SetOption.SET_IF_ABSENT
)
));
}
/**
* 释放锁
*/
public boolean releaseLock(String lockKey, String requestId) {
Long result = redisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(lockKey),
requestId
);
return result != null && result == 1;
}
/**
* 自动重试获取锁
*/
public boolean tryLockWithRetry(String lockKey, String requestId,
long expireMillis, int maxRetry,
long retryInterval) throws InterruptedException {
for (int i = 0; i < maxRetry; i++) {
if (acquireLock(lockKey, requestId, expireMillis)) {
return true;
}
Thread.sleep(retryInterval);
}
return false;
}
}
4. 最佳实践和注意事项
4.1 关键要点
- 原子性操作:使用SET NX PX命令或Lua脚本保证原子性
- 唯一标识:每个锁的value使用唯一ID(如UUID),避免误删
- 设置过期时间:防止死锁
- 锁续期机制:对于长任务,需要续期
- 可重入性:考虑是否支持同一线程重入
4.2 使用模板
@Component
public class DistributedLockService {
@Autowired
private RedisLockHelper redisLockHelper;
/**
* 分布式锁执行模板
*/
public <T> T executeWithLock(String lockKey, long expireMillis,
Callable<T> task) {
String requestId = UUID.randomUUID().toString();
boolean locked = false;
try {
// 尝试获取锁
locked = redisLockHelper.acquireLock(lockKey, requestId, expireMillis);
if (!locked) {
throw new RuntimeException("获取分布式锁失败");
}
// 执行业务逻辑
return task.call();
} catch (Exception e) {
throw new RuntimeException("执行业务逻辑失败", e);
} finally {
if (locked) {
// 释放锁
redisLockHelper.releaseLock(lockKey, requestId);
}
}
}
/**
* 使用示例
*/
public void processOrder(String orderId) {
String lockKey = "lock:order:process:" + orderId;
executeWithLock(lockKey, 30000, () -> {
// 业务逻辑
System.out.println("处理订单: " + orderId);
return null;
});
}
}
总结
- Redisson:功能最全,支持可重入锁、公平锁、读写锁、红锁等,内置看门狗自动续期
- Jedis/Lettuce + Lua:更轻量,控制更灵活,但需要自己实现更多功能
- 选择建议:
- 如果项目复杂,需要多种锁类型,推荐使用Redisson
- 如果只是简单场景,可以使用Jedis/Lettuce实现
- Spring Boot项目可以使用Spring Data Redis
分布式锁的实现需要考虑:原子性、死锁预防、锁续期、可重入性、性能等因素。