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

news/2025/10/31 9:38:42/文章来源:https://www.cnblogs.com/LinKinSJ/p/19178886

该业务适用场景:

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

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

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

一. 背景

在开发过程中需要使用到分布式锁的时候(以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.mzph.cn/news/951426.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年智能照明系统定做厂家权威推荐榜单:智能照明控制/智能灯控十大品牌/智能家居照明源头厂家精选

随着智能化技术在各领域的深度融合,智能照明系统已成为提升能效、优化空间管理的关键基础设施。根据行业数据显示,2024年中国智能照明市场规模突破950亿元,年均增长率达18.5%,其中定制化解决方案需求占比超过35%。…

2025年靠谱的橱衣柜全屋五金厂家最新权威实力榜

2025年靠谱的橱衣柜全屋五金厂家最新权威实力榜在当今家居装修市场中,五金配件作为橱柜、衣柜等家具的"心脏",其质量直接影响着产品的使用寿命和用户体验。随着消费者对家居品质要求的不断提升,高端五金配…

《刚刚问世》系列初窥篇-Java+Playwright自动化测试-31- 操作日历时间控件-上篇(详细教程) - 北京

1.简介 我们在实际工作或者生活中,有可能遇到有些web产品,网页上有一些时间选择,然后支持按照不同时间段范围去筛选数据,例如:我们预定火车票或者预定酒店,需要选择发车日期或者酒店的入住与退房时间。宏哥早在之…

2025年评价高的WHB系列筛土机最新TOP品牌厂家排行

2025年评价高的WHB系列筛土机最新TOP品牌厂家排行 在建筑、矿业和农业领域,筛土机是不可或缺的重要设备,能够高效分离土壤中的杂质,提高土质均匀性,广泛应用于地基处理、土壤改良等工程。随着技术的进步,2025年市…

2025年企业级制品库选型指南:国产化替代与高效协同新选择

数字化转型与信创战略下,国产制品库因适配本土软硬件、保障数据安全、控制成本,成为企业制品管理新选择。选型需从功能覆盖、安全合规、跨地域协同、成本等维度考量,企业需结合自身技术栈与合规要求,选适配的国产制…

Day9字符修饰--字体族与font复合属性

1.字体族<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0&q…

前端入门资料

ES6 入门教程 - ECMAScript 6入门 ECMAScript 2015 Language Specification – ECMA-262 6th Edition Vite | 下一代的前端工具链 容器化部署 | 前端工程化详解

2025年口碑好的碳钢超微粉碎机厂家推荐及采购指南

2025年口碑好的碳钢超微粉碎机厂家推荐及采购指南在工业粉碎设备领域,碳钢超微粉碎机因其优异的耐磨性、高强度和相对经济的成本,成为众多行业首选的粉碎设备。随着2025年新材料、新能源等产业的快速发展,对超微粉碎…

keycloak~关于iframe方式对接keyclock的注意事项

keycloak作为统一的认证中心,提供了单点登录的能力,一般可以通过超链的方式打开keycloak登录页,这对于不同域名来说,是没有任何问题的;第二种对接方式是通过iframe方式,当你的网站与keycloak不同域名时,在ifram…

25.09.22

2146E 考虑求出区间 \(\operatorname{MEX}\),然而发现没有什么好的作用。 注意到右端点固定,转而考虑值域,发现左端点会被不同的 \(\operatorname{MEX}\) 分段。 显然一个段只考虑最长的,因此考虑维护 \(\operator…

25.10.16

ARC204D 首先是注意到我们不希望操作过于混乱,而一直操作末尾可以稳定删除末尾,否则呢?错一位之后再做删除末尾就可以删除开头。 那么就可能会想我们先做一段删除末尾到 R,再删前面某个位置,就可以一直删开头到 L…

25.10.10

P13725 采取神人的方法:考虑求 \(q(s,t)\) 表示一个 \(f(P)=s,f(Q)=t\) 的答案,然后做容斥。 答案肯定是长成 \(\sum q(s,t)\times val(s,t)\) 的样子,然后你可以高斯消元打出容斥系数(?)或者有理有据地说明它就…

25.10.06

AGC029B 直接做是一般图匹配,那么想这个能不能是二分图。 把相同的值缩到一起,发现确实没有奇环,但还是不能跑。 想了想较大的数貌似更不容易满足,然后写了个从大取到小的贪心就对了。 为什么呢?哦,这是一棵树!…

25.10.03

QOJ13509 这个题加强到求 \(L\sim R\) 的每个数,虽然其实差不大。 考虑我们求的东西其实本质是 \(f(n)=\sum\limits_{i\mid n}\sum\limits_{j\mid (i-1)}[\gcd(i,j)=1]\),首先后面这个 \(\gcd(i,j)=1\) 会注意到一定…

四个月,AI为主,人为辅,一款产品两个知识库!

独立开发四个月,我与AI不得不说的故事。有句老话:人人都是产品经理;有句新话:AI加持下,人人可做独立开发一、引子 2023年开始接触ChatGPT,当时只能算是一个聊天机器人。如今两年过去了,AI这个赛道已经百花齐放。…

25.09.17

QOJ10354 比较机械地写个求和,容易看出做法应当是先定黑点,然后算这个局面下黑边的贡献。 如果一条合法边染黑,那么贡献 \(m+1\),否则 \(1\)。 发现不合法边一定是链,于是枚举链的端点计算,但是需要容斥。 我不太…

政府机构跨网文件交换案例分享:构建跨网文件交换统一通道

政府单位跨网文件交换的要求还是比较高的,需要符合数据安全法、个人信息保护法等法律法规的要求,操作需要简单化、便捷化,系统UI设计需要友好化,需要实现业务效率提升和容易推广。今天我们就来分享一个政府机构的跨…

25.09.15

今日份收获是什么呢?两个做法阈值分值,增量维护路径,保证一步控制,外部乘兄弟的换根,以及早睡晚起可以逃一次早自习(划掉)。 感知了一下,交互这一块反而像二分这样的东西用得少一点,增量这种更多,因为信息更…

2025年优秀的煤炭化验设备最新TOP厂家排名

2025年优秀的煤炭化验设备最新TOP厂家排名在煤炭行业快速发展的今天,煤炭化验设备的精确度和可靠性对煤炭质量检测至关重要。随着技术进步,2025年的煤炭化验设备市场涌现出一批技术领先、服务优质的厂家。本文将为您…

P10281 [USACO24OPEN] Grass Segments G

P10281 [USACO24OPEN] Grass Segments G洛谷 15pts 判断样例即可。 25pts \(O(n^2)\) 的暴力枚举即可。 55pts 满足条件为 \(\min(r_i,r_j)-\max(l_i,l_j) \ge k_i\) 时,第 \(i\) 个可获得一个品种。 由于被减数取最小…