Java 细粒度锁使用小提示

14 阅读1分钟

Redis 加锁

如果要使用redis给一个创建订单的方法添加分布式锁,该如何做呢?

就是,只需要创建一个固定的key,使用这个key去加锁即可。但是这样锁的粒度就太粗了,会影响别的订单的创建。

然后就会考虑创建一个细粒度锁,在设计key时,有个固定的前缀,后面拼接订单号。以此来加锁,基本就可以了。

Java 加锁

那么,当使用Java锁时,如何实现细粒度锁呢?参考代码如下:

private static Map<String, Lock> locks = new ConcurrentHashMap<>();

public void createOrder(String orderNo) {
    Lock lock = locks.computeIfAbsent("lock:order:"+orderNo, k -> new ReentrantLock());
    lock.lock();
    try {
        // do something
    } finally {
        lock.unlock();
    }
}

以上代码的写法,既实现了细粒度锁,又同时保证了获取锁的原子性。看起来功能实现得挺好。

是否还有问题呢?

有经验的你可能很快就看出来了,何时销毁锁的实例呢?

由于订单号是无限的,锁的实例也会创建很多,且基本上锁使用一次就不再使用了。由于锁实例一直被引用,导致无法回收。

既然想要回收,能否像Redis加锁一样,类似缓存过期,有个机制自动销毁不用的锁实例呢。

一般有两种方案:

  • 方案一:手搓,就是自己写代码控制锁实例的过期,不推荐。
  • 方案二:借助本地缓存工具,比如 Caffeine。根据实际情况调整缓存参数配置。

方案二,示例代码如下:

private static Cache<String, Lock> lockCache = Caffeine.newBuilder()
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .maximumSize(1000)
            .build();

public void createOrder(String orderNo) {
        String lockKey = "lock:order:" + orderNo;
        
        Lock lock = lockCache.get(lockKey, k -> new ReentrantLock());
        
        lock.lock();
        try {
            // do something
        } finally {
            lock.unlock();
        }
}