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

一、引言

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

二、方案选型

        其实一开始有两种方案,一种是传入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 爬…

disabled和hidden区别,attr和prop方法的区别

disabled和hidden区别 disabled 和 hidden 在HTML中都是用来控制元素可用性和可见性的方式,它们的区别主要在于: disabled: 1,适用于表单元素(如input、select、textarea、button等)。 2,当一个表单元素被设置为 disabled 时,用户无法与该元素进行交互,包括但不限于:…

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

每年的节日像是生活中的小驿站&#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;已开启 …

JS_选择文件夹,选择文件夹下所有文件,选择多个文件

选择多个文件 <input type"file" multiple />选择文件夹 选择文件夹下所有文件及递归选择下面所有文件夹的文件 webkitdirectory&#xff1a;适配webkit内核浏览器 mozdirectory&#xff1a;适配火狐浏览器 odirectory&#xff1a;适配opera内核浏览器 IE浏览…

Qt 二维数组的访问与应用

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

ActivityResultLauncher获取Activity返回数据

ActivityResultLauncher(活动结果启动器)是 Android 中用于启动活动并接收结果的新 API。它是在 AndroidX 框架中引入的&#xff0c;用于简化之前使用 startActivityForResult()方法的流程。 在使用 ActivityResultLauncher 之前&#xff0c;你需要选择适当的合同&#xff08;…

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

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

深入了解 Jetpack Compose 中的 Modifier

Jetpack Compose 是 Android 中用于构建用户界面的现代化工具包。其中&#xff0c;Modifier 是一个非常重要的概念&#xff0c;它允许我们对 UI 组件进行各种样式和布局的调整。在本篇博客中&#xff0c;我们将深入了解 Modifier&#xff0c;以及如何在 Compose 中使用它。 什…