java分布式锁工具类(DistributedLockUtils)

下面是一个关于 Java 分布式锁工具类 DistributedLockUtils 的完整实现及详细解释。分布式锁在分布式系统中非常重要,可以防止多个进程或服务同时访问共享资源,导致数据不一致或冲突。常见的实现方式包括基于数据库、Redis、ZooKeeper 等。

1. 分布式锁的基本概念

分布式锁的核心是确保在分布式环境下,多个进程/线程可以协调对共享资源的访问。实现一个可靠的分布式锁通常需要考虑以下几个方面:

  • 原子性:获取和释放锁的操作必须是原子的。
  • 过期时间:防止因服务异常未释放锁而导致死锁问题。
  • 锁的唯一性:确保每个锁具有唯一标识。
  • 可重入性:如果需要同一进程多次获取锁,还需要考虑可重入性。

2. Redis 实现分布式锁

我们将基于 Redis 来实现分布式锁,利用 Redis 的 SETNX 命令(“set if not exists”)来确保锁的原子性,再结合锁的过期时间,避免死锁。

Redis 相关命令
  • SETNX key value:如果 key 不存在,则设置 key 的值为 value,并返回 1;如果 key 已存在,则返回 0。
  • EXPIRE key seconds:为 key 设置过期时间,过期后 Redis 会自动删除 key
  • DEL key:删除 key,释放锁。

3. DistributedLockUtils 工具类实现

DistributedLockUtils 工具类代码
import redis.clients.jedis.Jedis;
import java.util.Collections;

/**
 * DistributedLockUtils - 基于 Redis 实现的分布式锁工具类
 */
public class DistributedLockUtils {

    // Lua脚本,确保原子性释放锁
    private static final String LUA_SCRIPT_UNLOCK = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "return redis.call('del', KEYS[1]) " +
        "else return 0 end";

    /**
     * 获取分布式锁
     * 
     * @param jedis      Redis客户端实例
     * @param lockKey    锁的键
     * @param requestId  请求ID,用于标识请求,防止误删他人的锁
     * @param expireTime 锁的过期时间,单位秒
     * @return 是否成功获取锁
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        // 尝试获取锁
        String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
        return "OK".equals(result);
    }

    /**
     * 释放分布式锁
     * 
     * @param jedis     Redis客户端实例
     * @param lockKey   锁的键
     * @param requestId 请求ID,确保只能释放自己持有的锁
     * @return 是否成功释放锁
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        Object result = jedis.eval(LUA_SCRIPT_UNLOCK, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        return result != null && "1".equals(result.toString());
    }

    /**
     * 续期锁(延长锁的过期时间)
     * 
     * @param jedis      Redis客户端实例
     * @param lockKey    锁的键
     * @param requestId  请求ID,确保是自己持有的锁
     * @param expireTime 新的过期时间,单位秒
     * @return 是否成功续期
     */
    public static boolean renewLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        String currentLockOwner = jedis.get(lockKey);
        if (requestId.equals(currentLockOwner)) {
            jedis.expire(lockKey, expireTime);
            return true;
        }
        return false;
    }
}

4. 主要功能详解

4.1. 获取分布式锁 (tryGetDistributedLock)
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
    return "OK".equals(result);
}
  • 参数说明

    • jedis:Redis 客户端实例。
    • lockKey:锁的键,通常与共享资源关联,如某个业务场景的标识。
    • requestId:请求的唯一标识,可以使用 UUID 确保每次请求的唯一性。
    • expireTime:锁的过期时间,防止服务异常导致锁一直存在。
  • 核心逻辑

    • 使用 SET key value NX EX expireTime 命令尝试获取锁:
      • NX:只在 key 不存在时才设置 key
      • EX expireTime:设置过期时间,单位为秒。
    • 如果获取锁成功,Redis 会返回 OK,否则返回 null
4.2. 释放分布式锁 (releaseDistributedLock)
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
    Object result = jedis.eval(LUA_SCRIPT_UNLOCK, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    return result != null && "1".equals(result.toString());
}
  • 参数说明

    • jedis:Redis 客户端实例。
    • lockKey:锁的键。
    • requestId:请求的唯一标识,用于确保只能释放自己持有的锁。
  • 核心逻辑

    • 使用 Lua 脚本来保证原子性:在获取到 lockKeyvalue 后,判断是否与 requestId 相同,若相同则删除 key 以释放锁。
    • 使用 jedis.eval() 方法执行 Lua 脚本,确保锁的释放过程是原子操作,避免竞争条件。
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

该 Lua 脚本首先通过 GET 操作检查 lockKey 是否为当前请求的 requestId,如果是,则调用 DEL 删除锁;否则返回 0,表示释放失败。

4.3. 续期分布式锁 (renewLock)
public static boolean renewLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    String currentLockOwner = jedis.get(lockKey);
    if (requestId.equals(currentLockOwner)) {
        jedis.expire(lockKey, expireTime);
        return true;
    }
    return false;
}
  • 参数说明

    • jedis:Redis 客户端实例。
    • lockKey:锁的键。
    • requestId:请求的唯一标识,用于确认是持有锁的请求在续期。
    • expireTime:新的锁的过期时间。
  • 核心逻辑

    • 首先通过 jedis.get(lockKey) 获取当前锁的持有者。
    • 如果持有者与当前请求的 requestId 一致,则调用 expire 方法延长锁的过期时间。

5. 代码示例

以下是如何使用 DistributedLockUtils 工具类的示例:

import redis.clients.jedis.Jedis;
import java.util.UUID;

public class DistributedLockDemo {

    public static void main(String[] args) {
        // 连接到 Redis 服务器
        Jedis jedis = new Jedis("localhost", 6379);
        
        // 生成请求ID
        String requestId = UUID.randomUUID().toString();
        
        // 定义锁的键和过期时间
        String lockKey = "myLock";
        int expireTime = 10;  // 10 秒过期时间

        // 尝试获取分布式锁
        boolean lockAcquired = DistributedLockUtils.tryGetDistributedLock(jedis, lockKey, requestId, expireTime);

        if (lockAcquired) {
            System.out.println("成功获取到锁!");
            
            // 执行一些需要同步的业务逻辑
            try {
                Thread.sleep(5000);  // 模拟业务操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 释放锁
            boolean lockReleased = DistributedLockUtils.releaseDistributedLock(jedis, lockKey, requestId);
            if (lockReleased) {
                System.out.println("成功释放锁!");
            } else {
                System.out.println("释放

锁失败!");
            }
        } else {
            System.out.println("获取锁失败!");
        }

        // 关闭 Redis 连接
        jedis.close();
    }
}

6. 关键点总结

  • 锁的唯一性requestId 确保每个请求对锁的唯一标识,避免锁被其他请求误释放。
  • 过期时间:使用过期时间 (EX) 防止死锁。
  • 原子性:通过 Redis 的 Lua 脚本保证锁的获取和释放操作是原子的,避免竞态条件。
  • 可扩展性:此工具类适合使用 Redis 部署的分布式系统,适用于多节点并发访问的场景,如订单处理、库存扣减等。

7. 扩展功能

  • 锁续期机制:可以在锁快到期时自动续期,防止长时间任务执行时锁意外过期。
  • 可重入性支持:可以扩展为可重入锁,即同一请求可以多次获取锁,不需要重复释放。
  • 超时机制:可以为业务操作设置超时,防止获取锁后由于系统故障未能及时释放锁。

8. 总结

DistributedLockUtils 是一个基于 Redis 的轻量级分布式锁实现,适用于 Java 应用中需要处理多进程或多服务并发访问共享资源的场景。通过 Redis 的 SETNXEXPIRE 操作,确保锁的获取、续期和释放的原子性和可靠性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Flying_Fish_Xuan

你的鼓励将是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值