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

【Redis】Redis缓存击穿

1. 概述

缓存击穿:缓存击穿问题也叫热点key问题,一个高并发的key或重建缓存耗时长(复杂)的key失效了,此时大量的请求给数据库造成巨大的压力。如下图,线程1还在构建缓存时,线程2,3,4也来查询缓存,未命中目标到达数据库中查询数据并重建缓存。

 2. 解决方案

针对缓存击穿,有两种解决方案。分别是互斥锁和逻辑过期。

2.1 互斥锁

思想:在众多的线程中,只有一个线程可以获得锁,获得锁的线程才能够重建缓存,再释放锁。没有获得锁的线程让其休眠一段时间后再次查询缓存,如果命中目标就返回数据了,如果还是没有命中目标,就再次尝试获得锁,如果获得锁就可以重建缓存,否则再休眠一段时间去缓存中查询,查看是否能命中目标,一直这样循环直到获得目标数据。

获得锁:这个锁不是我们常用的lock, synchronized锁,这两种锁拿到了就会执行,没拿到就等待。但我们这里的锁需要自定义拿到锁和未拿到锁需要干什么。这学习redis基本语法的时候,redis中有个命令和上诉的功能类似,那就是 setnx,如果key不存在就添加,返回结果是1,否则不添加,返回结果是0。

释放锁:释放锁直接将其删除就好了。del setnx

注意:为了避免锁没有被释放而造成死锁原因,最好设置一个有效期作为兜底,即便没有释放锁,有效期过后自动删除,就不会造成死锁了。

这种方式有一个缺点,如果重建缓存比较久,因为加了锁的原因,重建缓存的这段时间其它线程只能等待,性能不高。万一某个因素导致锁没有释放,会发生死锁的情况。 

2.2 逻辑过期

逻辑过期,顾名思义,不是真正意义的过期,也可简单理解为永不过期。出现缓存击穿的问题也是key失效导致的,那么我们就不给缓存设置过期时间ttl了。不设置过期时间怎么维护这些缓存呢?总不能一直存在缓存中吧?当然不是了,我们可以在存储数据时再额外存入一个过期时间,后续我们只要维护这个额外的过期时间就好了。

但是换一个角度来看,这个过期时间是由开发人员添加的,redis并不会帮我们管理这些数据,也就是说,这些数据一旦存入redis中,在某种意义上这些数据是持久性的。

一般来说,这些热点key都是在商品做活动的时候用的多,我们会提前把这些高并发数据导入到缓存中,导入数据时就为它们添加逻辑过期时间,等活动结束后,将它们移除即可。另外,查询这些数据理论上来说是一定能命中的,如果没有命中,说明这个数据不是活动数据。所以说只需要判断这些数据是否逻辑过期即可。

那逻辑过期了,也就是说缓存中的是旧数据,需要重建缓存,为了解决线程安全问题,这里也是需要加锁的,但值得一提的是,获得锁的线程(线程1)并不会自己去重建缓存,而是重开一个线程(线程2),委托新线程(线程2)去重建缓存,线程1会先凑合使用旧数据。如果线程2在重建缓存期间,来了一个线程3,因为缓存过期了,必然会尝试获取锁,但锁已经被线程2获取了,所以线程3肯定是获取锁失败的,此时线程3知道了有人帮我们做缓存更新了,于是线程3也拿到过期的数据返回了。就在这时,线程2已经重建好了缓存,并把锁释放了。刚好来了一个线程4,在缓存中命中了目标数据,并返回了最新的数据。

 2.3 总结

 互斥锁就是在缓存重建的过程,让其他线程进行等待,从而确保数据一致性,但线程需要等待,如果锁没有释放,还会导致服务阻塞,甚至不可用的状态。

逻辑过期是保证在缓存重建期间服务依然可用,但不能保证数据一致性。

   3. 实现

3.1 基于互斥锁解决缓存击穿

思想:利用redis的setnx方法来表示获取锁,该方法含义是如果redis中没有这个key,则插入成功,返回1。但是在spring中它帮我们转为了Boolean,因此在stringRedisTemplate中返回true, 如果有这个key则插入失败,则返回0,在stringRedisTemplate返回false,我们可以通过true,或者是false,来表示是否有线程成功插入key,成功插入的key的线程我们认为他就是获得到锁的线程。

// tryLock:尝试获取锁。锁就是redis中的一个key,所以key由使用者传给我们,我们就不在这写死了
private boolean tryLock(String key) {// 执行setnx,ctrl + p查看参数,可以发现它在存的时候是可以同时设置有效期的// 有效期的时长跟你的业务有关,一般正常你的业务执行时间是多少,你这个锁的有效期就比它长一点,长个10倍20倍(避免异常情况),例如这里就设置为10秒钟Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);// 这里不要直接将flag返回,因为直接返回它是会做拆箱的,在拆箱的过程中是有可能出现空指针的,因此这里建议大家使用一个工具类BooleanUtil,是hutool包中的,它可以帮你做一个判断(isTrue、isFalse方法),返回的是一个基本数据类型;或者它也可以直接帮你拆箱(isBollean方法)return BooleanUtil.isTrue(flag);
}// unlock:释放锁
private void unlock(String key) {// 之前分析过了,方法锁就是将锁删掉stringRedisTemplate.delete(key);
}

缓存击穿和缓存穿透的逻辑非常相似,可以在缓存穿透的基础上按照上面的流程图修改。

实现类

public Shop queryWithMutex(Long id)  {String key = CACHE_SHOP_KEY + id;// 1、从redis中查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2、判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}//判断命中的值是否是空值if (shopJson != null) {//返回一个错误信息return null;}// 4.实现缓存重构,缓存重建业务比较复杂,不是一步两步就能搞定的// 4.1 获取互斥锁,是一个keyString lockKey = "lock:shop:" + id;Shop shop = null;try {boolean isLock = tryLock(lockKey);// 4.2 判断否获取成功if(!isLock){// 4.3 失败,则休眠并重试// 休眠不要花费太长时间,这里可以先休眠50毫秒试一试,这个方法有异常,最后解决它Thread.sleep(50);// 重试就是递归即可return queryWithMutex(id);}// PS:获取锁成功应该再次检测redis缓存是否存在,做DoubleCheck。如果存在则无需重建缓存。但是这里先不检查了。// 4.4 成功,根据id查询数据库shop = getById(id);// 5.不存在,返回错误 // 这个是解决缓存穿透的if(shop == null){//将空值写入redisstringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);//返回错误信息return null;}// 6.写入redisstringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_NULL_TTL,TimeUnit.MINUTES);// 最后ctrl + T用try-catch-finally将代码包起来} catch (Exception e){// 这里异常我们就不去做处理了,因为sleep是打断的异常,直接往外抛即可throw new RuntimeException(e);}finally {// 7.释放互斥锁,因为抛异常的情况下,也是需要执行unlock的,因此需要放到unlockunlock(lockKey);}// 返回return shop;
}

 根据上面的逻辑,为空直接返回null, 为了给用户一个良好的操作体验,查询数据时对返回结果做一个非空判断,给用户一个提示。

@Override
public Result queryById(Long id) {// 缓存穿透// Shop shop = queryWithPassThrough(id);// 互斥锁解决缓存击穿Shop shop = queryWithMutex(id);if (shop == null) {return Result.fail("店铺不存在!");}return Result.ok(shop);
}

3.2 基于逻辑过期解决缓存击穿

 

思想:当用户请求数据时,首先到redis缓存中查询,理论上讲这个是不会出现未命中的情况,因为现在key是不会过期的,因此我们可以认为,一旦这个key添加到了缓存里面,它应该会是永久存在的,除非活动结束,然后我们再删除。像这种热点key往往是一些参加活动的一些商品,我们会提前给它们加入缓存,在那个时候就会给它设置一下逻辑时间。但是在为了健壮性考虑,还是判断一下它有没有命中,真的未命中我们也不需要去做一些击穿、穿透这样的一些解决方案,我们直接给它返回空即可。

核心逻辑其实就是默认它命中了,在命中的情况下,我们需要判断的是它有没有过期,也就是它的逻辑过期时间,这个结果有两种:过期和不过期。如果没有过期,则直接返回redis中的数据,如果过期,那就说明它需要重新加载,去做缓存处理。但是不是任何线程都可以重建,因此这里需要有一个争抢,即它需要先尝试去获取互斥锁,然后判断获取是否成功,如果获取失败,说明在这之前有线程去获取数据库数据,那这个更新我们就不用管了,直接返回旧的即可。而获取锁成功的线程,就需要执行缓存重建,但是也不是自己去执行,而是开启一个独立的线程,由这个线程去执行缓存重建,它自己也是返回旧的数据先用着。

1. 设置逻辑过期时间

由于这个字段是我们为了解决缓存击穿才出现的,所以这个字段在实体类中必然是不存在的,有以下3中方式添加字段。

方式一:在实体类中添加字段,修改了原有代码,具有代码侵入性。(不推荐)

方式二:另外创建一个实体类存放逻辑过期字段,然后在实体类中继承新创建的类,也修改了原有代码,具有代码侵入性。(不推荐)

方式三:在 RedisData 中添加一个Object属性,也就是 RedisData 它自己带有过期时间,并且它里面带有数据,这个数据就是你想存进redis的数据,例如Shop、或者其他的数据,因此它是一个万能的存储对象。这种方案就完全不用对原来的实体类做任何修改

package com.hmdp.utils;@Data
public class RedisData {// 设置的逻辑过期时间private LocalDateTime expireTime;private Object data;
}

2. 缓存预热

这种热点数据,是需要提前将缓存导入进去的,实际开发中可能会有一个后台管理系统,可以把某一些热点提前在后台添加到缓存中,但由于我们现在没有一个后台管理的系统,因此基于单元测试方式来把数据加入到缓存中,充当是提前做一个缓存的预热。

下面这个方法将查询的数据写入到了缓存中,并为其封装了逻辑过期时间

// saveShop2Redis:将shop添加到redis中
public void saveShop2Redis(Long id, Long expireSeconds) {// 1.查询店铺数据Shop shop = getById(id);// 2.封装逻辑过期时间RedisData redisData = new RedisData();redisData.setData(shop);// 过期时间由参数传进来redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));// 3.写入RedisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}

这里直接调用上面封装好的代码,模拟热点数据写入缓存中

@Test
void testSaveShop() {shopService.saveShop2Redis(1L, 10L);
}

 3. 处理缓存击穿实现代码

3.1 设置一个常量类存放key, 和锁的过期时间

public static final String LOCK_SHOP_KEY = "lock:shop:";  // 店铺获取的锁(key)的前缀
public static final Long LOCK_SHOP_TTL = 10L; // 锁的过期时间

 3.2 缓存穿透核心代码块

@Override
public Result queryById(Long id) {// 缓存穿透// Shop shop = queryWithPassThrough(id);// 互斥锁解决缓存击穿// Shop shop = queryWithMutex(id);// 逻辑过期解决缓存击穿Shop shop = queryWithLogicalExpire(id);if (shop == null) {return Result.fail("店铺不存在!");}return Result.ok(shop);
}private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
public Shop queryWithLogicalExpire( Long id ) {String key = CACHE_SHOP_KEY + id;// 1.从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);// 2.判断是否命中if (StrUtil.isBlank(json)) {// 3.未命中,直接返回nullreturn null;}// 4.命中,需要先把json反序列化为对象RedisData redisData = JSONUtil.toBean(json, RedisData.class);// redisData.getData()返回的是Object类型,因为RedisData中的data类型是Object,所以使用JSON工具在做反序列化的时候,它并不知道你的类型是不是店铺Shop。此时redisData.getData()的返回值的本质其实是JSONObject,因此这里可以直接强转JSONObject data = (JSONObject) redisData.getData();// 当拿到JSONObject类型后,依旧使用JSON工具类,toBean除了可以接收JSON字符串以外,还可以接收JSONObject,然后告诉它我的实际类型是店铺,此时它就能返回给你一个店铺结果了Shop shop = JSONUtil.toBean(data, Shop.class);// 当然上面两步有点多余,完全可以放一步,但这里为了方便理解,依旧分为两步// Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);LocalDateTime expireTime = redisData.getExpireTime();// 5.判断是否过期:过期时间是不是在当前时间之后?if(expireTime.isAfter(LocalDateTime.now())) {// 5.1.未过期,直接返回店铺信息return shop;}// 5.2.已过期,需要缓存重建// 6.缓存重建// 6.1.获取互斥锁String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 6.2.判断是否获取锁成功if (isLock){// 获取锁成功应该再次检测redis缓冲是否过期,做DoubleCheck。如果存在则无需重建缓存。// 6.3 成功,开启独立线程实现缓存重建。建议:使用线程池,不要自己去写一个线程,那一定话性能不太好,经常的创建和销毁。// 提交任务,这个任务我们可以写成一个Lambda表达式的形式CACHE_REBUILD_EXECUTOR.submit(()->{try {// 重建缓存,直接调用之前封装好的方法即可。// 这里过期时间准确来讲应该设置为30分钟,但是我们为了等一会测试,就先设置成20秒,我们期待的是缓存到底了,然后看看它会不会触发缓存重建的线程安全问题,因此设置短一点,方便我们观察效果this.saveShop2Redis(id, 20L);} catch (Exception e){throw new RuntimeException(e);} finally {// 重建缓存一定要释放锁,并且释放锁的动作最好写到finally中unlock(lockKey);}});}// 6.4.返回过期的商铺信息return shop;
}

为了模拟重建缓存有延迟,这里休眠200毫秒。休眠时间越长,越容易引发线程安全问题。

相关文章:

【Redis】Redis缓存击穿

1. 概述 缓存击穿:缓存击穿问题也叫热点key问题,一个高并发的key或重建缓存耗时长(复杂)的key失效了,此时大量的请求给数据库造成巨大的压力。如下图,线程1还在构建缓存时,线程2,3&…...

QILSTE H8-316QFO高亮橙光LED灯珠 发光二极管LED

在当今电子技术领域,H8-316QFO型号的LED以其卓越的性能和可靠性 脱颖而出。本文将深入探讨这款LED的关键参数,以期为工程师和技术人员提供详尽的技术参考。 首先,H8-316QFO的物理特性不容忽视。其外观尺寸为3.2x1.5x0.8mm,小巧的…...

pytest入门三:setup、teardown

https://zhuanlan.zhihu.com/p/623447031 function对应类外的函数,每个函数调用一次 import pytest def setup_module():print(开始 module)def teardown_module():print(结束 module)def setup_function():print(开始 function)def teardown_function():print(结…...

MySQL有哪些高可用方案?

大家好,我是锋哥。今天分享关于【MySQL有哪些高可用方案?】面试题。希望对大家有帮助; MySQL有哪些高可用方案? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 MySQL 高可用方案旨在确保数据库系统的高可靠性、低宕机时间、以及在硬件故障…...

IT 行业的就业情况

当前,IT 行业的就业情况呈现出以下特点: 1. 需求持续增长:随着数字化转型的加速,各个行业对信息技术的依赖程度不断提高,推动了对 IT 人才的持续需求。特别是在云计算、大数据、人工智能、物联网等新兴领域&#xff…...

[SWPU 2019]漂流记的马里奥

[SWPU 2019]漂流记的马里奥 解压安装包,里面有一个exe程序,运行后得到一个1.txt的文件 打开1.txt文件发现里面有给flag.txt 在这里的话可以用windows中的命令来打开falg.txt文件 notepad是一个用于打开Windows系统自带的记事本程序的命令 输入 notepa…...

如何在Android设备上复制整个目录到另一个位置?

在Android设备上复制整个目录到另一个位置,通常需要通过adb工具(Android Debug Bridge)来进行操作,因为它提供了文件系统级别的访问权限。以下是步骤: 打开命令行终端:首先,你需要连接你的Andro…...

人工智能在医疗健康领域的革命:从早期诊断到个性化治疗

引言:人工智能如何改变医疗健康 人工智能(AI)正在以惊人的速度渗透到医疗健康领域,从自动化诊断到个性化治疗,AI技术为提高医疗服务效率、降低成本和提升治疗效果提供了革命性的解决方案。本文将探讨人工智能在医疗健…...

MPQ3364调试异常异常问题

问题 MPQ3364_FAULT脚不报异常? 分析思路 首先排除硬件环境 1)把BL PWM接到3.3V(相当于PWM100%),FAULT脚和MCU断开,拔掉屏幕,FAULT 可以报异常 2.保持相同的输入环境,测试差异: 软…...

Flutter中GetBuilder 和 GetX 的区别

在 GetX 框架中,GetBuilder 和 GetX 都是用来构建响应式 UI 的,但它们在使用方式和适用场景上有一些不同。 GetBuilder 作用: 用于监听控制器中的特定变量变化并重新构建部分 UI。 特点: 只有当 update() 方法被调用时,才会触发 UI 的重建。…...

vue2+element-ui实现多行行内表格编辑

效果图展示 当在表格中点击编辑按钮时:点击的行变成文本框且数据回显可以点击确定按钮修改数据或者取消修改回退数据: 具体实现步骤 1. 行数据定义编辑标记 行数据定义编辑标记 当在组件中获取到用于表格展示数据的方法中,针对每一行数据添加一个编辑标记 this.list.f…...

Vue3+TypeScript+AntVX6实现Web组态(从技术层面与实现层面进行分析)内含实际案例教学

摘要 用Vue3+TypeScript+AntVX6实现Web组态(从技术层面与实现层面进行分析),包含画布创建、节点设计、拖拽实现(实际案例)、节点连线、交互功能,后续文章持续更新。 注:本文章可以根据目录进行导航 文档支持 AntVX6使用文档 https://x6.antv.antgroup.com/tutorial…...

【卷积神经网络】LeNet实践

模型建立 数据初始化根据模型搭建前向传播打印模型结构 前向传播数据初始化 def __init__(self):super(LeNet, self).__init__()# 第一层卷积层:# 输入:灰度图像 (1通道,大小 28x28)# 输出:6个特征图 (大小 28x28, 通过padding2保…...

FPGA实现GTP光口数据回环传输,基于Aurora 8b/10b编解码架构,提供2套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的 GT 高速接口解决方案 3、工程详细设计方案工程设计原理框图用户数据发送模块基于GTP高速接口的数据回环传输架构GTP IP 简介GTP 基本结构GTP 发送和接收…...

Tongweb8命令行使用收集(by lqw)

文章目录 声明对应版本修改thanos用户密码部署应用到默认实例节点相关操作新增节点(一般一个服务器ip只能装一个节点)启动节点(需确认节点没有运行)停止节点删除节点节点新增应用节点查看应用节点启动应用节点停止应用节点卸载应用(谨慎操作,卸载后应用就没有了,建议备份后…...

基于STM32的火灾烟雾报警器设计开题报告

开题报告 题目:基于STM32的火灾烟雾报警器Proteus仿真设计 一、研究背景与意义 随着现代城市化进程的加快,火灾安全问题日益凸显,火灾的早期预警对于减少人员伤亡和财产损失至关重要。传统的火灾报警系统往往依赖于烟雾或温度的单一检测&a…...

go 语言zero项目设置后台运行的几种方式

在 Go 项目中将程序设置为后台运行通常有几种方法。你可以使用 systemd、nohup 或者将 Go 程序作为服务启动。以下是几种常见的方法,分别适用于不同的场景。 ### 1. 使用 nohup 命令将 Go 程序后台运行 nohup 是一个常用的 Linux 工具,它可以将命令行程…...

数据库中的代数运算

这些代数基本运算通常被封装在数据库查询语言中,如SQL中的SELECT、FROM、WHERE等子句,使得用户可以更方便地对数据库进行查询和处理。 下面的介绍基于以下两个关系来说明: 传统的集合运算 并(∪) 合并两个关系中的元组…...

【机器学习】机器学习的基本分类-无监督学习-核密度估计(Kernel Density Estimation, KDE)

核密度估计(Kernel Density Estimation, KDE) 核密度估计(KDE)是一种非参数化方法,用于估计数据的概率密度函数(PDF)。与直方图相比,KDE 能够生成平滑的概率密度曲线,是…...

语义分割——DeeplabV3plus

DeeplabV3plus 是一种先进的用于语义分割任务的深度学习模型。DeepLabV3plus模型采用了编码器-解码器(Encoder-Decoder)结构,通过编码器提取图像特征,再通过解码器将这些特征映射回原始图像尺寸,实现像素级的分类。具体…...

【Leetcode 每日一题 - 扩展】50. Pow(x, n)

问题背景 实现 p o w ( x , n ) pow(x, n) pow(x,n)&#xff0c;即计算 x x x 的整数 n n n 次幂函数&#xff08;即&#xff0c; x n x_n xn​&#xff09;。 数据约束 − 100.0 < x < 100.0 -100.0 \lt x \lt 100.0 −100.0<x<100.0 − 2 31 ≤ n ≤ 2 31 −…...

富士相机基本参数学习

一色彩 富士相机视频调色入门课&#xff3b;上&#xff3d;&#xff5c;胶片模拟&#xff0c;白平衡与色彩&#xff5c;全是样片哦_哔哩哔哩_bilibili 步骤&#xff1a; 1设置曝光模式&#xff1a; 自动模式下拍摄降低难度 2设置白平衡&#xff1a;自动 不满意可以设置 3色彩&…...

MySQL:表的约束

目录 一. 表的约束和约束的目标 二. 空属性 三. 默认值default 四. 列描述 五. zerofill 六. 主键 6.1 建表时定义主键 6.2 去掉主键 6.3 建表后添加主键 6.4 复合主键 七. 自增长 八. 唯一键 九. 外键 一. 表的约束和约束的目标 表…...

Python鼠标轨迹算法(游戏防检测)

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…...

Elasticsearch 架构及 Lucene 索引结构原理入门

文章目录 Elasticsearch 整体架构Lucene 索引结构Lucene 倒排索引核心原理倒排索引倒排表&#xff08;Posting List&#xff09; Elasticsearch 整体架构 一个 ES Index 在集群模式下&#xff0c;有多个Node&#xff08;节点&#xff09;组成&#xff0c;每个节点就是ES的 inst…...

vue3项目通过修改虚拟dom实现向页面上追加内容

前言&#xff1a; demo效果&#xff1a; 1、刚进页面&#xff0c;渲染welcome.vue组件内容。2、向输入框中输入数据按回车&#xff0c;页面上追加text.vue组件内容&#xff0c;输入框中的数据也被传递给text.vue组件&#xff0c;并在页面上进行了展示。3、点击text.vue组件中的…...

Java:集合(List、Map、Set)

文章目录 1. Collection集合1-1. 迭代器遍历方式1-2. 通过for循环进行遍历1-3. forEach遍历 2. List集合2-1. ArrayList底层实现原理2-2. LinkedList底层实现原理 3. Set集合3-1. HashSet 底层实现3-2. LinkedHashSet 底层实现3-3. TreeSet 4. Collection集合->总结5. Map集…...

208-Base Camera Link 图像信号模拟器

&#xff11;、板卡概述 该板卡是基于Altra FPGA处理芯片EP3C40F484C8&#xff08;兼容EP3C16F484C8&#xff09;&#xff0c;模拟产生自定义分辨率的Camera Link 图像信号&#xff0c;并以base Camera Link输出。主要用于作为Camera Link 图像信号模拟器&#xff0c;模拟各类C…...

【创建模式-蓝本模式(Prototype Pattern)】

目录 Overview应用场景代码演示JDK Prototype pattern 更优实践泛型克隆接口 https://doc.hutool.cn/pages/Cloneable/#%E6%B3%9B%E5%9E%8B%E5%85%8B%E9%9A%86%E7%B1%BB The prototype pattern is a creational design pattern in software development. It is used when the t…...

(一)强化学习基础概念及学习路径

目录 前言 一、强化学习是什么&#xff1f; 二、强化学习中的基本概念 1.状态 2.动作 3.奖励 4.策略 5.智能体 6.环境 7.智能体与环境交互 三、强化学习路径 总结 前言 强化学习&#xff08;Reinforcement Learning, RL&#xff09;是机器学习的范式和方法论之一&a…...

Android 好的开源库

1. 权限请求框架 GitHub - getActivity/XXPermissions: Android 权限请求框架&#xff0c;已适配 Android 14 2. 下载框架 GitHub - lingochamp/okdownload: A Reliable, Flexible, Fast and Powerful download engine....

Spring中xxAware接口和InitializingBean接口的作用

question&#xff1a;Aware接口和InitializingBean接口的作用都可以用例如Autowired PostConstruct注解来实现&#xff0c;那么其相比较于注解的实现&#xff0c;优势是什么呢&#xff1f; 早期的Spring中并不存在注解开发&#xff0c;注解开发是在后期的Spring中引入&#xf…...

Nginx WebDAV扩展模块安装与配置完全指南

Nginx WebDAV扩展模块安装与配置完全指南 nginx-dav-ext-module nginx WebDAV PROPFIND,OPTIONS,LOCK,UNLOCK support [这里是图片001] 项目地址: https://gitcode.com/gh_mirrors/ng/nginx-dav-ext-module 项目基础介绍 Nginx WebDAV扩展模块(nginx-dav-ext-module)是由a…...

算法刷题Day16: BM41 输出二叉树的右视图

题目链接 描述 思路&#xff1a; 递归构造二叉树在Day15有讲到。复习一下&#xff0c;就是使用递归构建左右子树。将中序和前序一分为二。 接下来是找出每一层的最右边的节点&#xff0c;可以利用队列层次遍历。 利用队列长度记录当前层有多少个节点&#xff0c;每次从队列里…...

1.2 计算机网络的分类和应用(重要知识点)

1.2.1 计算机网络的分类 计算机网络的定义&#xff1a; 由通信线路互相连接的、能自主工作的计算机构成&#xff0c;强调各计算机&#xff08;工作站&#xff09;拥有独立的计算资源和任务能力。与多终端分时系统不同&#xff0c;后者终端仅作为主机接口&#xff0c;不具备计…...

Xcode

info.plist Appearance Light 关闭黑暗模式 Bundle display name 设置app名称&#xff0c;默认为工程名 Location When In Use Usage Description 定位权限一共有3个key 1.Privacy - Location When In Use Usage Description 2.Privacy - Location Always and When In U…...

HarmonyOS 5.0应用开发——属性动画

【高心星出品】 文章目录 属性动画animateTo属性动画animation属性动画 属性动画 属性接口&#xff08;以下简称属性&#xff09;包含尺寸属性、布局属性、位置属性等多种类型&#xff0c;用于控制组件的行为。针对当前界面上的组件&#xff0c;其部分属性&#xff08;如位置属…...

Freertos任务切换

一、操作系统进行任务切换的时机&#xff1a; 采用信号量实现任务的互斥&#xff1a; 二、FreeRTOS 任务切换场合 PendSV 中断的时候提到了上下文(任务)切换被触发的场合&#xff1a; ● 可以执行一个系统调用 ● 系统滴答定时器(SysTick)中断。 1、执行系统调用 执行系统…...

【汇编】思考汇编中的两个基本问题

1. 若干年前的疑问 几年前还在大学学习汇编时&#xff0c;不管是考试还是课程设计&#xff0c;其实都很顺利。但是心里一直对什么时候使用哪个寄存器存在疑惑&#xff0c;编写汇编时&#xff0c;没有十足的把握&#xff0c;都是抱着试一试的心态去完成了课程任务。 工作八年有…...

开发环境服务器 vs 生产环境服务器:开发与生产须分明

【背景】作为开发者&#xff0c;我们在不同的阶段都与两种服务器环境打交道——开发环境服务器和生产环境服务器。虽然听起来名字相似&#xff0c;但它们的职责和工作方式简直是天差地别&#xff01; 不知道朋友们有没有跟我一开始刚了解的时候的一些疑惑&#xff0c;因为刚开始…...

Tomcat的安装即使用

Tomcat的概念 Tomcat服务器是Java语言开发的&#xff0c;免费的开放源代码的Web应用服务器。 Tomcat处理静态HTML的能力远不及Apache或者Nginx&#xff0c;通常是作为一个Servlet和JSP容器&#xff0c;单独运行在后端。 Tomcat是由三个功能组合而成&#xff1a; java servlet&…...

外包干了9天,技术退步明显。。。。。

时光荏苒&#xff0c;转眼我已是一个拥有近四年功能测试经验的大专生。19年&#xff0c;我满怀激情地通过校招进入湖南某知名软件公司&#xff0c;期待在这里开启我的职业生涯。然而&#xff0c;长时间的舒适环境让我渐渐失去了前进的动力&#xff0c;技术停滞不前&#xff0c;…...

梳理你的思路(从OOP到架构设计)_基本OOP知识04

目录 1、 主动型 vs.基於被动型 API 1&#xff09;卡榫函数实现API 2&#xff09;API的分类 3&#xff09;回顾历史 4&#xff09;API >控制力 2、 结语&复习&#xff1a; 接口与类 1&#xff09;接口的表示 2&#xff09;Java的接口表示 1、 主动型 vs.基於被动…...

每天40分玩转Django:简介和环境搭建

Django简介和环境搭建 一、课程概述 学习项目具体内容预计用时Django概念Django框架介绍、MVC/MTV模式、Django特点60分钟环境搭建Python安装、pip配置、Django安装、IDE选择45分钟创建项目项目结构、基本配置、运行测试75分钟实战练习创建个人博客项目框架60分钟 二、Djang…...

OpenCV相关函数

一、Sobel算子函数 (cv2.Sobel) 功能 Sobel算子是一个梯度算子&#xff0c;用于边缘检测。通过计算图像中像素的梯度&#xff0c;Sobel算子可以检测出水平和垂直方向上的边缘。 参数 src&#xff1a;输入图像。 ddepth&#xff1a;输出图像的深度&#xff08;如cv2.CV_8U, cv…...

【理想汽车中科院】基于模仿学习的端到端自动驾驶数据缩放规律

论文: https://arxiv.org/pdf/2412.02689 项目: https://github.com/ucaszyp/Driving-Scaling-Law 0. 摘要 端到端自动驾驶范式因其可扩展性而最近吸引了大量关注。然而&#xff0c;现有方法受到现实世界数据规模有限的制约&#xff0c;这阻碍了对端到端自动驾驶相关扩展规律…...

重卡补能新未来:光储充换一体开启高效之路

《重卡补能新未来&#xff1a;光储充换一体开启高效之路》 一、光储充换一体重卡补能模式的兴起 重卡运输行业在电动化进程加速下迎来变革&#xff0c;光储超充快换一体化补能站成为新趋势。 随着国家 “双碳” 战略的持续推进&#xff0c;新能源汽车市场蓬勃发展&#xff0c…...

Mybatis Plus 3.0 快速入门

1、简介 MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 2、创建并初始化数据库 2.1、创建数据库 mybatis_plus 2.2、创建 User 表 其表结构如下: idnameageemail1Jone18test1@baomidou.com2Jack…...

ABAP:MB51字段增强

MB51所对应的程序&#xff1a;RM07DOCS 1.定义增强字段&#xff1a;RM07DOCS_GENERATED中定义 2.取值&#xff1a;RM07DOCS中detail_list中加取值逻辑 也有对ITAB取值做调整的&#xff0c;但是要写很多处&#xff0c;直接这里取值就很简单。 3.展示字段&#xff1a;RM07DOCS中…...

nVisual 定制化APP打包流程

一、下载打包软件 HBuilder X 下载地址&#xff1a;https://dcloud.io/hbuilderx.html 安装:此软件为绿色软件&#xff0c;解压即可使用。进入目录&#xff0c;双击exe启动。 此软件需要注册&#xff0c;打开时会提供跳转链接&#xff0c;通过邮箱注册账号。 注册成功后&#…...