Java案例怎么实现分布式锁?

wen java案例 16

本文目录导读:

Java案例怎么实现分布式锁?

  1. 方案一:基于 Redis(推荐,性能高)
  2. 方案二:基于 ZooKeeper(强一致性,适合对可靠性要求高的场景)
  3. 三种方案对比总结
  4. 最佳实践建议

分布式锁的核心目标是在分布式系统中,保证多个进程(或线程)对共享资源的互斥访问,在Java中实现分布式锁,通常需要依赖一个外部协调组件(如Redis、ZooKeeper、数据库等)。

以下是使用 RedisZooKeeper 两种最主流方式实现的详细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) 强一致 对数据一致性要求极高(配置中心、调度任务)
数据库(行锁/乐观锁) 强一致(取决于隔离级别) 无需额外组件、依赖业务数据库(简单场景)

最佳实践建议

  1. 首选 Redis + Redisson:大多数业务场景下性能最优,功能完善(自动续期、可重入)。
  2. 锁粒度要细:锁的 key 应该包含业务 ID(如 lock:order:123),而不是全局一个锁。
  3. 设置合理超时时间:避免因程序异常导致锁一直未释放。
  4. 必须有防误解锁机制:加锁和解锁使用相同的 requestId(或 UUID),防止误删其他线程的锁。
  5. 考虑高可用:Redis 使用哨兵/集群模式,Zookeeper 使用集群。

抱歉,评论功能暂时关闭!