架构(十六)本地方法缓存

一、引言

        作者需要在底层公共包里面加一个方法反射的工具类,看起来很简单的事,问题也不少,这里讲讲过程。在结合同事的思维误区聊聊本地加锁块的问题。

二、方案选型

        其实一开始有两种方案,一种是传入Function和入参,一种是传入实例、方法名、入参

        两种都可以,但是一开始想着尽量让使用方少操作,而且反射有性能损耗,所以还是先研究了传入Function和入参

1、传入Function

public static <T, R> R invoke(Function<T, R> f, T request) {return f.apply(request);}

        这样就很简单,用的时候还是从spring里面拿出来

@Resourceprivate ExecuteProcess executeProcess;executeProcess::execute.request

        这其实是可以的,但是对于作者的需求来说不行,因为作者需要通过传入类的实例拿到他注解上的一些属性,但是通过Function是拿不到的

2、反射

        反射需要传入类的实例,参数和方法名,也就多了一个参数

        这里要说一下request就可以了,随着目前的规范化代码,重载方法在业务系统里面基本是会被骂的,大家都是放一个对象,入参有增加了,对象加一个参数就可以,而不是把重载方法都搞一遍        

public static <T, R> R invoke(T instance, T request, String methodName) {try {Class<?> clazz = instance.getClass();Method method = clazz.getMethod(methodName, request.getClass());// 执行方法R result = (R)method.invoke(instance, request);return result;} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {// 异常处理throw new SoaInvokeException(e);}}

三、优化

        选型之后就要对这个实现进行优化了,那么反射可以在哪里进行优化呢,method.invoke肯定是少不了的,但是执行方法可以被缓存,而不是每次都要反射遍历那就先看看chatGpt有没有什么建议

1、chatGpt建议

        他先是让用方法名字作为key进行缓存,这有问题,但是不同的类之间没有同名方法吗

        然后再问了一下,又说用pair.of把类和方法作为有序对,但是这也有问题,这相当于每次调用这个方法都在创建pair对象

cbac50e4e970426fa9b11c3b71c30e99.png

3ca2604904f04bb1a0061cae86a31da6.png

2、自优化 

        上面chatGpt的两种方法不予采纳,那就自己进行优化吧

        这里其实有一个疑问点,那就是第一次加锁的时候,到底是锁methodCache还是clazz呢,锁methodCache有点大,锁clazz又怕别人也有在锁的,这时候就要根据实际情况了

        我们使用的时候基本不会加这个clazz,框架倒是有可能,但是分析了这一类clazz,至少我们传入的应该是不会被锁的,所以最终选了clazz

/*** map的大小*/private static final int INIT_SIZE = 16;/*** 缓存类实例、方法名对应的方法*/protected static ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, Method>> methodCache =new ConcurrentHashMap<>(INIT_SIZE);/*** 获取执行方法* * @param instance 类* @param request 请求参数* @param methodName 接口名称* @param <T>* @return* @throws NoSuchMethodException*/private static <T> Method getMethod(T instance, T request, String methodName) throws NoSuchMethodException {Class<?> clazz = instance.getClass();Method method;ConcurrentHashMap<String, Method> meMap = methodCache.get(clazz);String meKey = methodName + request.getClass().getName();if (meMap == null) {synchronized (clazz) {meMap = methodCache.get(clazz);if (meMap == null) {method = clazz.getMethod(methodName, request.getClass());meMap = new ConcurrentHashMap(INIT_SIZE);meMap.put(meKey, method);// 将方法对象存入缓存methodCache.put(clazz, meMap);return method;}}}method = meMap.get(meKey);if (method == null) {synchronized (meMap) {method = meMap.get(meKey);if (method == null) {method = clazz.getMethod(methodName, request.getClass());meMap.put(meKey, method);}}}return method;}public static <T, R> R invoke(T instance, T request, String methodName) {try {Method method = getMethod(instance, request, methodName);// 执行方法R result = (R)method.invoke(instance, request);return result;} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {// 异常处理throw new SoaInvokeException(e);}}}

四、拓展

        在这个过程中,同事和作者产生过一个技术的分歧,他觉得锁本地静态变量methodCache会导致整个工具类被锁,其他线程会卡在invoke(T instance, T request, String methodName)外面进不来

        作者认为他锁的是局部代码块,也就是invoke进得去,但是其他线程会卡在if (meMap == null) {,有争论就做个实验好了,虽然说也没有准备锁methodCache,但是技术理念不能错,不然一定会在别的地方踩坑

        作者在加锁的方法做了延迟,然后开了两个线程,第一个抢到的线程会等第二个线程进来,如果第二个线程的日志被挡在invoke外面就是同事说的对,不然就是作者对的

         事实证明作者是对的,这里作者也不把截图贴上去了,有兴趣的可以自己试试,多动手,少动嘴


/*** 缓存类实例、方法名对应的方法*/protected static ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, Method>> methodCache =
new ConcurrentHashMap<>();public static void main(String[] args) {
ServiceServerExtensionUtil util = new ServiceServerExtensionUtil();ServiceServerExtensionUtilTwo utilTwo = new ServiceServerExtensionUtilTwo();// 反射方法执行成功, 方法被缓存Thread a = new Thread(() -> {
System.out.println("thread:" + Thread.currentThread().getName());ServiceServerExtension.invoke(util, "test", "invokeUtil");});Thread b = new Thread(() -> {
try {
Thread.sleep(1000);} catch (InterruptedException e) {
e.printStackTrace();}
System.out.println("thread:" + Thread.currentThread().getName());ServiceServerExtension.invoke(utilTwo, "test", "invokeUtil");});a.start();b.start();}/*** 获取执行方法* * @param clazz 类* @param request 请求参数* @param methodName 接口名称* @param <T>* @return* @throws NoSuchMethodException*/private static <T> Method getMethod(Class<?> clazz, T request, String methodName) throws NoSuchMethodException {System.out.println("thread:" + Thread.currentThread().getName() + "getMethod start");Method method;ConcurrentHashMap<String, Method> meMap = methodCache.get(clazz);if (meMap == null) {
synchronized (methodCache) {
try {
Thread.sleep(10000);} catch (InterruptedException e) {
e.printStackTrace();}
meMap = methodCache.get(clazz);if (meMap == null) {
method = clazz.getMethod(methodName, request.getClass());meMap = new ConcurrentHashMap(16);meMap.put(methodName, method);// 将方法对象存入缓存methodCache.put(clazz, meMap);System.out.println("thread:" + Thread.currentThread().getName() + "getMethod end");return method;}
}
}
method = meMap.get(methodName);if (method == null) {
synchronized (meMap) {
method = meMap.get(methodName);if (method == null) {
method = clazz.getMethod(methodName, request.getClass());meMap.put(methodName, method);}
}
}
System.out.println("thread:" + Thread.currentThread().getName() + "getMethod end");return method;}public static <T, R> R invoke(T instance, T request, String methodName) {
try {
Class<?> clazz = instance.getClass();Method method = getMethod(clazz, request, methodName);// 执行方法R result = (R)method.invoke(instance, request);return result;} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// 异常处理throw new SoaInvokeException(e);}
}

 

五、总结

        看起来很简单的东西做起来其实有很多细节要考虑,很多技术细节大家也都不是那么确定的,千万不要在自己有疑惑的时候听别人的技术理念,有疑惑就要自己去尝试验证。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/723489.shtml

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

相关文章

selenium_001基本学习

第 1 章webdriver 环境搭建好了&#xff0c;我们正式学习 selenium 的 webdriver 框架&#xff0c;它不像 QTP 之类的有 GUI 界面的可视化工具&#xff0c;是webdriver 框架的 API。 2.1.1 打开网页 注解&#xff1a;我们用谷歌浏览器 # from selenium import webdriver …

ue4.27 发现 getRandomReachedLocation 返回 false

把这个玩意儿删掉&#xff0c;重启工程&#xff0c;即可 如果还不行 保证运动物体在 volum 内部&#xff0c;也就是绿色范围内确保 project setting 里面的 navigation system 中 auto create navigation data 是打开的(看到过博客说关掉&#xff0c;不知道为啥) 如果还不行&…

小兴教你做平衡小车-stm32程序开发(PWM)

1 程序分享 main.c文件。 #include "stm32f10x.h" #include "led.h" #include "delay.h" #include "usart.h" #include "key.h" #include "tim.h" #include "pwm.h" #include "stdio.h"int…

专业的安全数据交换系统,如何进行网间数据安全交换?

网络隔离是企业网络安全管理的重要组成部分&#xff0c;它有助于提高网络的整体安全性&#xff0c;保护企业资产和客户数据&#xff0c;同时满足法规合规要求。很多企业为了防止内部核心数据泄露&#xff0c;都实施了网络隔离&#xff0c;比如划分成内网、外网&#xff0c;有的…

关于爬虫发展历史,价值,问题和应对恶意爬虫的策略

作为一个互联网的技术开发&#xff0c;爬虫不管是自己写的还是所负责的网站被爬&#xff0c;都是挺常见的。 但是一个很常见的东西&#xff0c;却一直没有系统梳理过&#xff0c;今天我们从发展历史&#xff0c;价值&#xff0c;问题和应对恶意爬虫的策略来聊一聊爬虫。 1 爬…

三八妇女节放假么 妇女节放假安排备忘录提醒别忘记

每年的节日像是生活中的小驿站&#xff0c;给我们带来了休息和欢乐。而当三八妇女节临近时&#xff0c;你是否也在期待着那半天的假期呢&#xff1f; 想象一下&#xff0c;公司的走廊里&#xff0c;同事们都在窃窃私语&#xff1a;“三八妇女节会放假吗&#xff1f;”这个问题…

Javaweb之SpringBootWeb案例之自动配置案例的自定义starter实现的详细解析

3.2.4.2 自定义starter实现 自定义starter的步骤我们刚才已经分析了&#xff0c;接下来我们就按照分析的步骤来完成自定义starter的开发。 首先我们先来创建两个Maven模块&#xff1a; 1). aliyun-oss-spring-boot-starter模块 创建完starter模块后&#xff0c;删除多余的文件…

PHP设计模式初探 以前写的完整PPT!!!!!

幻灯片 1: 初探PHP设计模式 copyright CSDN 白毛大侠 幻灯片 2: 我们说别人代码写的烂&#xff0c;烂在哪&#xff1f; 反思我们平时是怎么写代码的&#xff1f; 非开发者如何转开发&#xff08;业务&#xff09; &#xff1f; 一.过程与对象 幻灯片 3: <?…

AFL havoc_stage

AFL fuzz_one函数&#xff0c;有个地方判断skip_deterministic &#xff0c;而AFLNet的例子里面&#xff0c;定义了-d参数&#xff0c;也就是skip_deterministic1&#xff0c;直接进入到havoc_stage&#xff0c;所以这里想分析下havoc_stage。 if (skip_deterministic || queue…

Ollama内网离线部署大模型

为了演示方便&#xff0c;我这里选用参数较小的Qwen1.5-0.5B-Chat模型。 下载GGUF模型 访问huggingface下载qwen1_5-0_5b-chat-q5_k_m.gguf模型。 https://huggingface.co/Qwen/Qwen1.5-0.5B-Chat-GGUF/tree/main注意&#xff1a; huggingface访问不到&#xff0c;可以选择…

华为智慧教室3.0的晨光,点亮教育智能化变革

“教室外有更大的世界&#xff0c;但世界上没有比教室更伟大的地方。” 我们在求学阶段&#xff0c;都听说过这句话&#xff0c;但往往是在走出校园之后&#xff0c;才真正理解了这句话。为了让走出校园的孩子能够有能力&#xff0c;有勇气探索广阔的世界。我们应该准备最好的教…

@德人合科技|公司数据防泄漏软件,防止内部文件数据资料外泄!

现如今&#xff0c;企业都普遍面临数据安全问题的挑战&#xff0c;随着数据泄漏事件不断增加&#xff0c;企业需要强有力的数据防泄漏系统来保护机密信息。 www.drhchina.com 德人合科技 | 公司数据防泄漏软件&#xff0c;防止内部文件数据资料外泄&#xff01; 公司数据防泄漏…

openGauss环境搭建 | 新手指南

一、搭建准备 openGauss开发需要使用linux环境&#xff0c;先下载远程连接工具Xshell/MobaXterm 。 1. 使用工具连接远程linux服务器&#xff0c;使用root账号远程登录&#xff0c;创建个人账号。 useradd -d /home/xxx -m xxx 2. 设置密码。 passwd xxx 3. 切换到个人账…

【中国电信】光猫 PT632 使用超管权限修改 IP 地址租期时间

背景 由于光猫默认设置的动态 IP 租期是 24 小时&#xff0c;所以每天都会断网一次&#xff0c;严重影响用网体验&#xff0c;所以打算通过修改动态 IP 租期为 一周&#xff08;最长就一周&#xff0c;没有永久的选项&#xff09;来改善。 需求 一台电脑&#xff08;已开启 …

Qt 二维数组的访问与应用

配色方案有多种类型&#xff0c;可以根据不同的需求和应用场景来选择合适的配色方法。在柱状图、饼状图中都会用到不同的配色&#xff0c;本文将配色方案使用二维数组进行存储&#xff0c;对常用的配色进行了整理&#xff1a; 效果图 示例代码 void MainWindow::InitUI() {QS…

java 二分查找(迭代与递归)

二分搜索被定义为一种在排序数组中使用的搜索算法&#xff0c;通过重复将搜索间隔一分为二。二分查找的思想是利用数组已排序的信息&#xff0c;将时间复杂度降低到O(log N)。 二分查找算法示例 何时在数据结构中应用二分查找的条件&#xff1a; 应用二分查找算法&#xff1a…

Android中的传感器类型和接口名称

本文将介绍传感器坐标轴、基础传感器和复合传感器&#xff08;动作传感器、姿势传感器、未校准传感器和互动传感器&#xff09;。 1. 传感器坐标轴 许多传感器的传感器事件值在相对于设备静止的特定坐标系中表示。 1.1 移动设备坐标轴 Sensor API 仅与屏幕的自然方向相关&a…

【每日一题】1976. 到达目的地的方案数-2024.3.5

题目: 1976. 到达目的地的方案数 你在一个城市里,城市由 n 个路口组成,路口编号为 0 到 n - 1 ,某些路口之间有 双向 道路。输入保证你可以从任意路口出发到达其他任意路口,且任意两个路口之间最多有一条路。 给你一个整数 n 和二维整数数组 roads ,其中 roads[i] = […

JavaScript的for循环与双重for循环,妈妈再也不用担心我找工作了

学习路线 第一阶段&#xff1a;网页制作 HTML&#xff1a;常用标签&#xff0c;锚点&#xff0c;列表标签&#xff0c;表单标签&#xff0c;表格标签&#xff0c;标签分类&#xff0c;标签语义化&#xff0c;注释&#xff0c;字符实体 CSS&#xff1a;CSS介绍&#xff0c;全局…

解决MySQL 5.7在Redhat 9中启动报错:libncurses.so.5和libtinfo.so.5缺失问题

在使用Linux系统搭建MySQL数据库的过程中&#xff0c;我们往往会遇到各种依赖库的问题&#xff0c;尤其是在安装较旧版本的MySQL时。最近&#xff0c;在RedHat 9&#xff08;rocky linux 9&#xff09;系统上安装MySQL 5.7版本时&#xff0c;我遇到了一个典型的依赖库缺失错误&…