当前位置: 首页 > news >正文

Java-函数式编程-实现分布式锁工具

该业务适用场景:

  • 分布式环境下的资源互斥访问

  • 防止重复执行(如定时任务)

  • 库存扣减等需要强一致性的操作

一. 背景

在开发过程中需要使用到分布式锁的时候(以redisson为例). 一般会通过初始化RedissonClient配置, 然后在需要的地方使用. 当使用的多的时候会发现, 代码中充斥着相似的代码结构, 例如

String lockKey = RedisBaseKeyEnum.APP_USER_REGISTRY_VERIFY_CODE_KEY_LOCK.buildKey(account);
RLock lock = redissonClient.getLock(lockKey);
try {if (lock != null && !lock.isLocked() && lock.tryLock(0, 60L, TimeUnit.SECONDS)) {// some biz}
} catch (InterruptedException e) {LOG.error("加锁失败,key:{}, account:{}", lockKey, account, e);Thread.currentThread().interrupt();throw new BizException(BizErrorCode.VERIFY_CODE_SENDER_ERROR);
} finally {if (lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();}
}

简单的加锁过程类似于上面的代码. 所以为了提升代码的可复用性, 降低重复代码, 提高系统可维护性. 我们可以通过Java中的函数式编程, 将重复的代码块抽象出一个模板. 然后具体的业务通过作为方法入参 来实现.

二. 核心点

  1. 抽象分布式锁业务

  2. 使用@FunctionalInterface注解将需要注入的业务抽象出来

  3. 注入使用

三. 实现方式

  1. 定义函数式接口 @FunctionalInterface

============================= 加锁业务 有返回值 ===============================
package com.sj.utils.lock.func;@FunctionalInterface
public interface LockTask {void run() throws Exception; // 没有入参的方法
}
============================= 加锁业务没有返回值 ===============================
package com.sj.utils.lock.func;@FunctionalInterface
public interface LockTask {void run() throws Exception; // 没有入参的方法}
  1. 定义Redisson分布式锁类型枚举

package com.sj.utils.lock;public enum RLockType {REENTRANT,       // 可重入锁FAIR,            // 公平锁READ,            // 读锁WRITE,           // 写锁TRY,             // 尝试获取锁TRY_WITH_FAIL    // 尝试失败锁 tryLock中waitTime为0
}

然后

===========================RLockSimpleUtil========================
public RLock getLock(String lockKey, RLockType lockType) {switch (lockType) {case TRY:case REENTRANT:case TRY_WITH_FAIL:return redissonClient.getLock(lockKey);case FAIR:return redissonClient.getFairLock(lockKey);case READ:return redissonClient.getReadWriteLock(lockKey).readLock();case WRITE:return redissonClient.getReadWriteLock(lockKey).writeLock();default:throw new IllegalArgumentException("Unknown lock type: " + lockType);}
}
  1. 定义RLockSimpleUtil

package com.sj.utils.lock;import com.sj.utils.lock.func.LockException;
import com.sj.utils.lock.func.LockTask;
import com.sj.utils.lock.func.LockTaskWithResult;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Component
public class RLockSimpleUtil {private final Logger log = LoggerFactory.getLogger(RLockSimpleUtil.class);private final RedissonClient redissonClient;// 锁默认参数private final long DEFAULT_WAIT_TIME = 5;       // 秒private final long DEFAULT_LEASE_TIME = 30;     // 秒private final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;public RLockSimpleUtil(RedissonClient redissonClient) {this.redissonClient = redissonClient;}/*** 执行带返回值的任务,默认使用可重入锁** @param lockKey 锁键* @param task    任务* @param <T>     任务返回值类型* @return 任务返回值*/public <T> T executeWithResult(String lockKey, LockTaskWithResult<T> task) {return executeWithResult(lockKey, RLockType.REENTRANT, DEFAULT_WAIT_TIME, DEFAULT_LEASE_TIME, DEFAULT_TIME_UNIT, task);}/*** 执行带返回值的任务,支持指定锁类型和超时时间** @param lockKey   锁键* @param lockType  锁类型* @param waitTime  等待时间* @param leaseTime 租约时间* @param unit      时间单位* @param lockTask  任务* @param <T>       任务返回值类型* @return 任务返回值*/public <T> T executeWithResult(String lockKey,RLockType lockType,long waitTime,long leaseTime,TimeUnit unit,LockTaskWithResult<T> lockTask) {RLock lock = getLock(lockKey, lockType);boolean acquired = false;try {acquired = lock.tryLock(waitTime, leaseTime, unit);if (!acquired) {throw new LockException("Failed to acquire lock: " + lockKey);}return lockTask.runWithResult();} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new LockException("Lock acquisition interrupted", e);} catch (Exception e) {throw new RuntimeException(e);} finally {unlockQuietly(lock, acquired);}}/*** 执行无返回值的任务,默认使用可重入锁** @param lockKey  锁键* @param lockTask 任务*/public void execute(String lockKey, LockTask lockTask) {execute(lockKey, RLockType.REENTRANT, DEFAULT_WAIT_TIME, DEFAULT_LEASE_TIME, DEFAULT_TIME_UNIT, lockTask);}/*** 执行无返回值的任务,支持指定锁类型和超时时间** @param lockKey   锁键* @param lockType  锁类型* @param waitTime  等待时间* @param leaseTime 租约时间* @param timeUnit  时间单位* @param lockTask  任务*/public void execute(String lockKey, RLockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit, LockTask lockTask) {RLock lock = getLock(lockKey, lockType);boolean acquired = false;try {acquired = lock.tryLock(waitTime, leaseTime, timeUnit);if (!acquired) {throw new LockException("Failed to acquire lock: " + lockKey);}lockTask.run();} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new LockException("Interrupted while trying to acquire lock: " + lockKey);} catch (Exception e) {throw new RuntimeException(e);} finally {unlockQuietly(lock, acquired);}}/*** 获取指定锁类型的锁实例** @param lockKey  锁键* @param lockType 锁类型* @return 锁实例*/public RLock getLock(String lockKey, RLockType lockType) {switch (lockType) {case TRY:case REENTRANT:case TRY_WITH_FAIL:return redissonClient.getLock(lockKey);case FAIR:return redissonClient.getFairLock(lockKey);case READ:return redissonClient.getReadWriteLock(lockKey).readLock();case WRITE:return redissonClient.getReadWriteLock(lockKey).writeLock();default:throw new IllegalArgumentException("Unknown lock type: " + lockType);}}/*** 安静地解锁锁实例** @param lock     锁实例* @param acquired 是否成功获取锁*/private void unlockQuietly(RLock lock, boolean acquired) {try {if (acquired && lock.isLocked() && lock.isHeldByCurrentThread()) {lock.unlock();}} catch (Exception e) {if (lock != null && lock.isHeldByCurrentThread()) {log.warn("Error unlocking: {} ", e.getMessage(), e);lock.unlock();}}}}

四. 使用方式

  1. 在具体的业务中注入RLockSimpleUtil

  2. 根据具体业务, 直接调用util中的具体方法, 例如一个简单的加锁业务:

@Override
public ShortAddressResult acquire(ShortAddressTypeEnum type, Long householdId, Integer count) {String key = RedisBaseKeyEnum.DEVICE_SHORT_ADDRESS_LIST_CACHE_KEY_LOCK.buildKey(householdId.toString());return rLockSimpleUtil.executeWithResult(key,RLockType.FAIR,30,3,TimeUnit.SECONDS,// 下方执行具体的加锁业务() -> {String cacheKey = getCacheKey(type, householdId);return shortAddressPoolService.acquire(type, cacheKey, householdId, count);});
}

五. 补充

  1. Java内置的函数式接口:

  • Runnable - 无参数无返回值

  • Supplier - 无参数有返回值

  • Consumer - 有参数无返回值

  • Function<T,R> - 有参数有返回值

  • Predicate - 有参数返回布尔值

  1. 潜在问题

  2. Redis 单点依赖

    1. 依赖 Redis 可用性

    2. Redis 故障会影响所有锁操作

  3. 锁超时风险

    1. 固定租约时间可能不适合所有场景

    2. 长时间任务可能导致锁提前释放

  4. 性能考虑

    1. 每次锁操作都需要网络通信

    2. 高并发场景下可能成为瓶颈

  5. 异常处理可能过于宽泛

  6. 缺少监控指标

    1. 没有锁获取成功率统计

    2. 没有锁持有时间监控

http://www.jsqmd.com/news/27149/

相关文章:

  • 2025年智能照明系统定做厂家权威推荐榜单:智能照明控制/智能灯控十大品牌/智能家居照明源头厂家精选
  • 2025年靠谱的橱衣柜全屋五金厂家最新权威实力榜
  • 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-31- 操作日历时间控件-上篇(详细教程) - 北京
  • 2025年评价高的WHB系列筛土机最新TOP品牌厂家排行
  • 2025年企业级制品库选型指南:国产化替代与高效协同新选择
  • 前端入门资料
  • 2025年口碑好的碳钢超微粉碎机厂家推荐及采购指南
  • keycloak~关于iframe方式对接keyclock的注意事项
  • 25.09.22
  • 25.10.16
  • 25.10.10
  • 25.10.06
  • 25.10.03
  • 四个月,AI为主,人为辅,一款产品两个知识库!
  • 25.09.17
  • 政府机构跨网文件交换案例分享:构建跨网文件交换统一通道
  • 25.09.15
  • 2025年优秀的煤炭化验设备最新TOP厂家排名
  • P10281 [USACO24OPEN] Grass Segments G
  • 2025年诚信的316L不锈钢带最新TOP厂家排名
  • Java 类加载机制 面试题(一)
  • 2025年优秀的舟山注塑螺杆厂家最新推荐排行榜
  • 2025年专业的工业型无线测力称重变送器高评价厂家推荐榜
  • C# Avalonia 17- ControlTemplates - GradientButtonTest
  • 2025年专业的液压式矫平机优质厂家推荐榜单
  • KMP 学习笔记
  • CentOS7进入单用户模式
  • 实例方法实际上也是类属性,这个说法对吗?——实例的命名空间和类的命名空间详解
  • 2025年靠谱的升降机TOP实力厂家推荐榜
  • CSP-S 2025游寄