Redis实现可重入锁
什么是可重入锁?
可重入锁(Reentrant Lock)是指同一个线程可以多次获取同一把锁而不会造成死锁的一种锁机制。它具有以下特点:
可重入性:同一个线程可以重复获取已经持有的锁
锁计数:内部维护一个计数器,记录锁被获取的次数
正确释放:必须释放与获取次数相同的次数才能真正释放锁
可重入锁避免了线程因重复获取自己持有的锁而导致死锁的情况,是分布式系统中常用的锁机制。
实现原理
Redis实现可重入锁主要依赖:
SET key value NX PX milliseconds
命令实现原子性加锁Lua脚本保证解锁的原子性
使用线程标识和计数器实现可重入
代码实现
RedisReentrantLock:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;public class RedisReentrantLock {private final Jedis jedis;private final String lockKey;private final String lockValue;private final long expireTime; // 锁过期时间(毫秒)// 线程局部变量,存储锁计数器private static final ThreadLocal<Map<String, Integer>> LOCK_COUNTER = ThreadLocal.withInitial(HashMap::new);public RedisReentrantLock(Jedis jedis, String lockKey, long expireTime) {this.jedis = jedis;this.lockKey = lockKey;this.lockValue = UUID.randomUUID().toString() + ":" + Thread.currentThread().getId();this.expireTime = expireTime;}/*** 获取锁* @param waitTime 等待时间(毫秒)* @return 是否获取成功* @throws InterruptedException*/public boolean tryLock(long waitTime) throws InterruptedException {Map<String, Integer> counterMap = LOCK_COUNTER.get();Integer count = counterMap.get(lockKey);// 如果已经持有锁,增加计数并返回成功if (count != null && count > 0) {counterMap.put(lockKey, count + 1);return true;}long endTime = System.currentTimeMillis() + waitTime;while (System.currentTimeMillis() < endTime) {// 尝试获取锁String result = jedis.set(lockKey, lockValue, SetParams.setParams().nx().px(expireTime));if ("OK".equals(result)) {counterMap.put(lockKey, 1); // 初始化计数器return true;}// 检查是否是当前线程持有的锁(锁重入)String currentValue = jedis.get(lockKey);if (lockValue.equals(currentValue)) {counterMap.put(lockKey, 1);return true;}Thread.sleep(100); // 等待一段时间后重试}return false;}/*** 释放锁*/public void unlock() {Map<String, Integer> counterMap = LOCK_COUNTER.get();Integer count = counterMap.get(lockKey);if (count == null || count <= 0) {throw new IllegalStateException("未持有锁,不能释放");}// 减少计数int newCount = count - 1;counterMap.put(lockKey, newCount);// 如果计数为0,真正释放锁if (newCount == 0) {// 使用Lua脚本保证原子性String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +" return redis.call('del', KEYS[1]) " +"else " +" return 0 " +"end";jedis.eval(luaScript, 1, lockKey, lockValue);counterMap.remove(lockKey);}}
}
使用示例:
public class LockExample {public static void main(String[] args) {Jedis jedis = new Jedis("localhost", 6379);RedisReentrantLock lock = new RedisReentrantLock(jedis, "order_lock", 30000);try {// 尝试获取锁,等待5秒if (lock.tryLock(5000)) {try {System.out.println("第一次获取锁成功");// 可重入测试if (lock.tryLock(5000)) {try {System.out.println("第二次获取锁成功(可重入)");// 执行业务逻辑processOrder();} finally {lock.unlock();}}} finally {lock.unlock();}} else {System.out.println("获取锁失败");}} catch (InterruptedException e) {Thread.currentThread().interrupt();System.out.println("线程被中断");} finally {jedis.close();}}private static void processOrder() {// 订单处理逻辑System.out.println("处理订单中...");}
}