项目总结(2023.11.6)

项目的总结:11.6

一、在线店铺点评App

项目介绍: 基于SpringBoot+Redis 的点评店铺App,实现了找店铺 => 写点评 => 看评论 => 点赞关注 的完整业务流程

后端技术选型:SpringBoot 框架 + MySQL数据库 + MyBatis-Plus + Redis缓存 +Redission分布式锁 +HuTool工具库

主要工作:

  • 短信登录:使用Redis实现分布式Session存储验证码解决集群间登录状态同步问题;并使Redis+Token机制实现单点登录,选择Hash代替String 来存储用户信息,节约内存并便于单字段的修改。
  • 店铺查询:使用Redis对高频访问的店铺进行缓存,降低DB压力的同时,提升了查询性能从961ms降低到了25ms。
  • 为了方便其他业务后续使用缓存,使用泛型+函数式编程实现了通用缓存访问静态方法,并解决了缓存雪崩,缓存穿透问题。
  • 使用常量类全局管理Redis Key 前缀、TTL,保证了键空间的业务隔离,减少冲突。
  • 优惠卷秒杀:使用Redisson分布式锁来实现操作互斥,解决同一用户重复领卷,领取优惠卷数量超卖的问题,实现一人一单。
  • 对于项目中复杂的集合处理,使用Java 8 Stream API和Lambda表达式来简化代码。
  • 使用List数据结构存储用户点赞信息,并基于Redis的ZSet结构实现 TopN 点赞排行,提高DB查询性能

Redis基础

Redis基础:

1. 什么是Redis?

  • Redis是一个基于C语言开发的开源数据库(BSD许可),与传统的数据库不同的是Redis数据是存在内存中的(内存数据库),读写速度非常快,被广泛应用于缓存方向,并且,Redis存储的是KV键值对数据。
  • 为了满足不同的业务场景,Redis 内置了多种数据类型实现(比如 String、Hash、Sorted Set、Bitmap、HyperLogLog、GEO)。并且,Redis 还支持事务、持久化、Lua 脚本、多种开箱即用的集群方案(Redis Sentinel、Redis Cluster
  • Redis没有外部依赖,Linux和OS X是Redis开发和测试最多的两个操作系统,官方推荐生成环境使用Linux部署Redis

2. Redis为什么这么快?

Redis 内部做了非常多的性能优化,比较重要的有下面 3 点:

  1. Redis基于内存,内存的访问速度是磁盘的上千倍
  2. Redis内置了多种优化过后的数据结构实现,性能非常高
  3. Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用Redis 线程模式后面会详细介绍到

Redis的理解:

1. 缓存穿透,击穿,雪崩

缓存穿透:

查询一个Redis和MySQL中都不存在的数据,由于缓存中没有数据,请求会直接穿透到数据库,从而引起数据库压力过大,严重影响系统的性能

  • 解决方案:
  1. 空对象也进行缓存
  2. 使用不漏过滤器

缓存击穿:(热点key过期)

指的是一个非常热点的数据,在缓存中过期之后,正好在这个时间内有大量的请求访问该数据,这些请求会直接穿透到数据库中

  • 解决方案:
  1. 设置热点key永不过期
  2. 增加一个分布式锁

缓存雪崩:

缓存雪崩指的是当缓存中的大量数据,在同一时间失效,导致大量数据直接访问数据库

  • 解决方案:
  1. 在原有的失效时间的基础上加一个随机值,这样每一个缓存的过期时间的重复率就会降低

2. Redis的常用数据结构

  • String:字符串
  • List:列表
  • Set:集合
  • Hash:散列
  • Zet:有序集合

String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)

3. Redis和MySQL如何保证数据的一致性

一般情况下Redis是用来实现应用和数据库和数据库之间的一个读操作的缓存层.主要目的是为去减少数据的IO,可提升数据库的IO性能

当应用程序需要去读取某个数据的时候,先尝试去redis中加载,如果命中了就直接返回,如果没有命中就直接从数据库里面查询,查询到数据之后,在数据缓存到Redis里面

会存在的问题:一份数据同时保存在数据库和Redis里面,当数据发生变化的时候,需要同时去更新Redis和Mysql,由于更新操作是有先后顺序的并且不想Mysql中的多表事务操作有ACID的特性,所以就会出现一个数据一致性的问题,这个情况下能够选择的方法只有几种

  1. 先更新数据库再去更新缓存
  2. 先删除缓存再去更新数据库

如果先更新数据库再去更新缓存,如果缓存更新失败了,就会导致数据库和Redis中的数据是不一致的,如果先删除缓存再去更新数据库,理想情况下应用下次再访问Redis的时候,发现Redis中的数据是空的,那么就会从数据库加载,保存到Redis中,也就是说数据库理论上是一致的.但是在极端的情况下,由于删除Redis和更新数据库这两个操作并不是原子操作,所以在这个过程中如果出现其他线程来访问,还是会存在数据不一致的问题.

如果要从极端情况下去保证Redis和MySQL数据一致性,就只能采用一个最终一致性的方案:

  • **比如基于RocketMQ的可靠性消息通信,来实现数据的最终一致性,还可以通过Canal组件去监控Mysql中的binlog的日志,把更新后的数据同步到Redis里面,**因为这是基于最终一致性来实现的.如果业务场景不能去接受,数据短期的不一致性,那么久不能使用这么一个方案

注意技术是为了业务服务的,所有不同的业务场景,对于技术的选择和方案设计都是不同的,一个技术方案不可能解决所有场景!

4. Redis持久化AOF和RDB区别,分别解决什么场景问题

问题:Redis是内存数据库性能非常高,数据存储在内存,服务器宕机数据可能会丢失

解决:Redis提供了两种持久化策略

  • RDB持久化:**在指定的时间间隔内,把内存中的数据快照写入磁盘,只保留某个时间点的数据.**他的时间操作过程是先fork一个子进程,先把数据写入到一个临时文件,写入成功以后再替换之前的文件,用二进制进行压缩存储
  • AOF:会记录服务器接收的所有写操作命令,并且把这些命令追加到一个文件里面,持久化到磁盘上,在服务器启动的时候,通过重写执行这些命令来还原数据

这是考察的什么呢?

  • 考察的是对Redis持久化机制的理解程度
  • 对不同持久化策略在实际应用中的选择和优化,为了保证数据的安全性和一致性

RDB持久化和AOF持久化有各自的优缺点,适用于不同的场景RDB

  • RDB的优缺点是**:恢复大数据集的速度比AOF快,对cpu和内存的影响比较小**,更加适用于做冷备份,或者对数据恢复要求不高,因为是间隔一段时间进行持久化,如果在这个时间段内Redis发生宕机,那么这些数据就会被丢失
  • AOF的优缺点是:更加稳定,数据的完整性更好,因为他记录了每一个写操作,但是随着数据的增加AOF的文件会越来越大可能会影响redis性能,所以AOF更适用于 对数据安全性要求较高的场景(购物车,订单等关键业务)
  • 所以在实际应用中也可以同时开启RDB和AOF 进行持久化以便结合两者的优点,具体采用哪种持久化策略要根据业务需求和系统环境

如何优化AOF持久化

  • 每秒同步一次
  • 每修改一次数据就同步一次
  • 或者不同步

AOF (Append Only File) 追加文件. Redis处理的每一个文件都会记录在AOF文件中,可以看做是命令日志文件 对数据要求型比较高的场景

RDB 全称(Redis数据备份文件 Redis Database Backup file),也叫作Redis数据快照.简单来说就是把内存中的所有数据都记录到磁盘中,当Redis实例故障重启后,从磁盘读取文件,恢复数据. 恢复大数据集的速度比AOF快,对CPU和内存的影响比较小,更加适用于做冷备份,会哦这对数据恢复要求不高

image-20230911143244775

面试官:redis做为缓存,数据的持久化是怎么做的?

候选人:在Redis中提供了两种数据持久化的方式:1、RDB 2、AOF

面试官:这两种持久化方式有什么区别呢?

候选人:RDB是一个快照文件,它是把redis内存存储的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据。

AOF的含义是追加文件,当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据

面试官:这两种方式,哪种恢复的比较快呢?

候选人:RDB因为是二进制文件,在保存的时候体积也是比较小的,它恢复的比较快,但是它有可能会丢数据,我们通常在项目中也会使用AOF来恢复数据,虽然AOF恢复的速度慢一些,但是它丢数据的风险要小很多,在AOF文件中可以设置刷盘策略,我们当时设置的就是每秒批量写入一次命令

5. 分布式锁Redisson

redis分布式锁是如何实现的?

  • 先按照简历上的业务场景进行描述分布式锁的使用场景
  • 我们使用redisson实现的分布式锁,底层是 lua脚本和setnx 来保证原子性

Redisson实现分布式锁如何合理的控制锁的有效时长?

在redisson的分布式锁中,提供了一个 watchDog (看门狗),一个线程获取锁成功之后WatchDog会给持有锁的线程 续期(默认10s续期一次)

Redisson的这个锁,可以重入吗?

可以重入,多个锁重入需要判断是否是当前线程,在redis中进行存储的时候使用hash结构,来存储线程信息和重入的次数

Redisson锁能够解决主从数据一致的问题吗?

**不能解决 **,但可以是用Redisson提供的 红锁来解决,但是这样的话 性能就比较低,如果业务中需要 保证数据的强一致性建议采用 zookeeper 实现的分布式锁

6.使用 Redis 实现一个排行榜怎么做?

Redis 中有一个叫做 sorted set 的数据结构经常被用在各种排行榜的场景,比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜、话题热度排行榜等等。

相关的一些 Redis 命令: ZRANGE (从小到大排序)、 ZREVRANGE (从大到小排序)、ZREVRANK (指定元素排名)。


image-20230926144146553

#判断元素是否存在 元素存在返回点赞数量 不存在返回空
ZCORE
#添加
ZADD
#查询排行榜
ZRANG key min max
#查询前5名
ZRANGE z1 0 4

ZRANGE(从小到大排序)

ZREVRANGE (从大到小排序)

ZREVRANK (指定元素排名)。

二、伙伴匹配系统

项目介绍:基于SpringBoot的移动端网站,实现了用户管理,按标签搜索用户,推荐相似用户(心动模式),组队等功能。

后端技术选型:SpringBoot框架 + MySQL + Mybatis + MyBatis Plus + Easy Excel(数据导入)+ FASTJSON(JSON序列化库) + Spring Scheduler定时任务 + 相似度匹配算法 + Redis缓存 + Redission分布式锁

主要工作:

  • 为了明确接口的返回,自定义统一的错误码,并封装了全局异常处理器,从而规范了异常返回,屏蔽了项目的报错细节。
  • 使用Easy Excel读取收集来的基础用户信息,并通过自定义线程池+CompletableFuture并发编程提高批量导入数据库的性能。
  • 为解决首次访问系统的用户主页加载过慢的问题,使用Spring Scheduler定时任务来实现缓存预热,并通过分布式锁保证多机器部署时定时任务不会重复执行。
  • 为解决同一用户重复加入队伍,入队人数超限的问题,使用Redisson分布式锁来实现操作互斥。
  • 使用编辑距离算法实现了根据标签匹配最相似用户的功能。

模拟面试

1. 项目中使用Redis,有哪些优势,为什么选择使用Redis实现分布式Session

Redis首先是一个开源的 基于内存K/V存储中间件。基于内存存储所以读写的性能非常的搞。此外Redis还支持多种数据结构、各类编程语言的客户端、支持持久数据,其生态也是非常的广泛。

本项目中,我使用Redis分布式Session来代替Tomcat本地的Session存储,能够再分布式多机场景下保证获取登录用户信息的一致性。使用Redis分布式Session的优点是非常简单方便,只需要 引入Redis和Spring-session-data-redis 依赖,然后在配置文件中指定Redis的地址和session的 store-type为redis,即可自动生效不需要自己额外编码。

2.登录功能的时候使用Hash代替String 存储用户信息有什么好处? 实际应用中Hash和String 存储方式有说明区别?

Redis 的Hash结构采用 key/value 键值对的形式存储数据,使用Redis和Hash来存储用户信息后,能够很方便地对每一个用户属性进行独立的更新和查询操作,而不是返回整个JSON 字符串,性能会非常高;如果采用String结构整体存储,网络传输数据时会把所有的用户信息都返回出来,增加传输开销。比如只需要修改用户的昵称但是你把整个用户都传过来了,这样网络传输的内容就非常大

3.请解释一下Java 8 Stream API 和 Lambda 表达式的作用,以项目中具体的应用场景(结合实际开发情况说明)

Stream API 和Lambda 表达式时JDK8 提供的语法糖,都是为了时集合处理更加简洁、易读和高效 Lambda表达式是一种匿名函数,允许你以更紧凑的方式传递代码块,简化代码的编写举例
Stream API 是一种流式操作集合的方法,提供了丰富的集合处理操作,比如过滤、映射、排序等,还支持延时加载和并行处理,简化代码,并提高编码效率

本项目中:

  1. 匹配相似用户时,将存储用户和匹配度的List转化为Stream流,用Sorted方法进行编辑距离由小到大排序,用limit 方法取出流的前N项,用collect终结操作将处理好的流转化为集合List
  2. 使用Steam API 的 map方法对用户列表中的每个用户信息进行脱敏
  3. 使用ParallelStream 实现并发流等

4.你提到使用Easy Excel 进行批量导入数据库,介绍一下他的使用方法和优势

我使用Easy Excel从Excel 表格中批量导入用户信息到MySQL,之所以使用Easy Excel是因为他操作简单,性能优越,更多的是可以节约内存,解决大文件内存溢出问题

在项目中,对于数据量小的文件,我采用同步模式一次性获取所有表格数据并存储到List中;对于数据量比较大的文件,我采用自定义的Listener的方式异步逐行读取Excel 并分批插入到数据库中.

5.你是如何使用自定义线程池的?如何设置线程池的参数?

在项目中我使用ThreadPoolExecutor实现灵活的自定义线程池,并通过ArrayBlockingQueue 存放任务.针对每类不同的业务,分别定义不同的线程池,让它们互不影响.

自定义线程池的核心参数:

  • 核心线程数(corePoolSize)线程池中一直保持活动的线程数.可以使用corePoolSize 方法进行设置.一般情况下,根据系统的资源情况和任务的特性来设置合适的值.
  • 最大线程数(maxiumPoolSize): 线程池中允许存在的最大线程数.可以使用maxiumPoolSize方法来设置.如果所有线程都处于活动状态,而此时又有新的任务提交,线程池会创建新的线程,直到达到最大线程数.
  • 空闲线程存活时间(keepAliveTime):当线程池中的线程数量超过核心核心线程数时,如果这些线程在一定时间内没有执行任务,则这些线程会被销毁.可以使用keepAliveTime和TimeUnit方法来设置.
  • 阻塞队列(workQueue) :存放等待执行的任务的阻塞队列.可以根据任务的特性选择不同类型的队列,如LinkedBlockQueue,ArrayBlockingQueue等.默认情况下,使用无界阻塞队列,即LinkedBlockQueue,但也可以根据需要设置有界队列.
  • 线程工厂(threadFactory):用于创建线程池的工厂.可以通过实现ThreadFactory 接口自定义线程的创建逻辑
  • 拒绝策略:(rejectedExecutionHandler) :当前线程池无法接受新的任务时,会根据设置的拒绝策略进行处理.场景的几种拒绝策略有AbortPolicy,DiscardPolicy,DiscardOldestPolicy 和 CallerRunsPolicy
  • 我们是根据任务的类型以及消耗资源的情况来调整线程池的参数.
  • CPU密集型任务,核心线程数设置为N或者N+1
  • IO密集型任务,核心线程数设置为2N

6.使用Redis缓存高频访问用户信息的时候提到自定义序列化器,为什么需要自定义序列化器,以及自定义序列化器的实现方式?

设置序列化器

7.在项目中如何实现Redis缓存的?选用了哪种Redis数据结构?

具体的实现方式:

  • 对于登录用户信息的存储,直接使用spring-session-data-redis依赖开启对Redis分布式Session的支持.
  • 对于主页用户推荐列表的存储,我使用Spirng Data Redis整合Redis,并且通过RedisTemplate 来操作Redis,根据业务类型设计了缓存key的规则,选用了string 数据结构来存储推荐用户列表.

8.解决首页加载过慢的问题中,使用了Spring Scheduler 定时任务和分布式锁,请解释一下定时任务的执行原理和此处分布式锁的作用.

项目中使用了Spring Scheduler 定时任务,将每个任务定义为独立的Job 类,并且给实际需要定时执行的任务增加@Scheduled 注解来开启定任务.同时在启动类上开启对@EnableScheduling支持

在@Spring Scheduler 注解,我们使用crontab表达式来定义执行任务的时间周期,Spring Scheduler(SpringBoot默认整合了) 会根据这些定义,在时机到达时开启独立的线程来执行任务.

在分布式的场景下,可能有多个服务器实例同时执行同一个定时任务,导致并发问题或者重复执行,所以用分布式锁来保证定任务执行的唯一性.当定时任务执行时,先去抢锁,只有抢到锁的服务器实例才会执行定时任务.

9.简介Redisson分布式锁的使用场景和执行原理

Redisson是一个基于Redis的数据网格,它提供了开箱即用的分布式锁功能,用于解决分布式环境下的并发控制问题.
比如在项目中,使用Redisson分布式锁保证接口幂等性,防止多个用户同时操作或重复提交带来的数据不一致.

Redisson分布式锁的实现是基于Redis的SETNX 命令和Lua 脚本,具体的实现原理如下:

  1. 获取锁 : 当客户端请求获取锁时,Redisson会向Redis发送一个SETNX命令,尝试将一个特定的键(锁的标识)设置为一个特定的值(客户端标识),并设置锁的超时时间.
  2. 争用锁: 如果多个客户端同时获取同一个锁,只有一个客户端能够成功设置键的值,其他客户端SETNX命令将失败,它们会继续尝试获取锁.
  3. 锁超时: 为了防止某个客户端获取锁后发生异常导致锁永远不会被释放,Redisson设置了锁的超时时间.当锁的超时时间到达之后,Redisson会自动释放锁,允许其他客户端获取锁.
  4. 释放锁: 当客户端执行完锁保护的操作后,可以主动去释放锁,这将删除锁的标识键,或者锁的自动超时也会导致锁的释放.
  5. 锁的可重入性: Redisson 支持可重入锁,允许同一客户端多次获取同一个锁,然后多次释放锁.只有所有获取锁的次数都释放后,锁才会被完全释放.
  6. 锁的续期:如果一个客户端在持有锁时,锁的超时时间即将到期,Redisson会自动为锁续期,防止锁在操作的过程中被自动释放.

10.什么是编辑距离算法?在伙伴匹配系统中起到的作用?解释一下原理

编辑距离算法是一种用于度量两个字符串之间的相似度或差异度性的算法,常用于字符串相似度的比较,拼写检查等场景
在用户匹配系统中,我使用编辑距离算法来计算用户输入的搜索关键字与已有用户信息的匹配程度,并且按照相似度进行排序,从而实现最相似用户的推荐.

在这里插入图片描述

11.Knife4j 和 Swager自动生成后端接口文档,解释一下Swagger的作用,以及项目中使用Swagger的好处

使用Swagger 接口文档生成工具后,不需要在开发完项目之后手动编写一套接口文档,而是直接交由系统根据Controller 接口层的代码自动生成文档,大幅度节省时间.

使用Swagger生成的接口文档不仅能够分组查看请求参数和响应,还支持灵活的在线调试,可以直接通过界面发送请求来测试接口提高开发调试效率.

此外,引入Swagger后,可以得到基于OpenAPI规范的接口定义JSON ,可以配合其他第三方工具根据JSON自动生成前端代码,自动生成客户端调用SDK等.

12.在使用Redis缓存的时候,有哪些可能出现的常见问题?你又是如何解决的?

在这里插入图片描述
在这里插入图片描述

13.Docker的优势

可以把Docker镜像想象称为应用的安装包,我们通过编写Dockerfile制作了项目的安装包,开发者可以使用Docker镜像一键快速启动项目,无需手动安装Java等依赖项,并且手动输入启动jar包的命令,便于分发应用程序,并且提高应用部署效率.
此外.通过给Docker镜像打tag,可以控制应用程序的版本,便于项目的持续发布和回滚.

Easy Excel

image-20231105212111604

EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel (alibaba.com)

  1. 引入对应的依赖
  2. 确定读取的方式: 确定表头:建立对象 。 不确定表头:每一行数据映射为Map<String,Object>
  3. 创建一个监听器

两种读取模式:

  1. 监听器:先创建监听器、在读取文件时绑定监听器。

单独抽离处理逻辑,代码清晰易于维护;一条一条处理,适用于数据量大的场景。

  1. 同步读:无需创建监听器,一次性获取完整数据。

方便简单,但是数据量大时会有等待时常,也可能内存溢出。

CompletableFuture

新建一个异步的任务 CompletableFuture

并发要注意执行的先后顺序无所谓,不要用到非并发类的集合

什么是CompletableFuture?

是Java8引入的一个类,他可以解决异步执行任务处理异步任务的结果 是一个非常实用的组件,他可以优化系统的性能和响应速度

主要是为了考察是否能够设计和优化高性能,高并发的系统

并且能够使用CompletableFuture 工具实现优化

是Java8 里面引入的一个组件,提供了一种简单且强大的方式来处理异步任务和处理异步任务的结果**,在他之前我们只能使用callable/future**这样的一个机制来取获取异步线程的执行结果,但是Future是通过阻塞等待的方式来实现的,对性能不是很友好.

使用CompletableFuture可以让我们将一个耗时的任务提交给线程池来进行异步处理,然后可以继续执行其他任务,等到异步任务执行结束以后,会触发一个回调方法,我们可以在回调方法里面去处理异步任务的执行结果,相当于优化了Future的阻塞等待的问题,类似于一种响应式的编程方式,CompletableFuture提供了一些便捷的方法,比如说thenApply thenAccept thenRun等等可以让我们以链式的方式去处理异步任务的执行结果,从而更加灵活的去编写异步代码

使用CompletableFuture 可以将一个耗时的任务,提交给线程池来进行异步处理,然后可以继续执行其他任务,等到异步任务结束之后,会触发一个回调方法,我们可以在回调方法里面去处理异步任务的执行结果,相当于优化了Future的阻塞等待的问题,类似于一种响应式的编程方式CompletableFuture提供了一提供了一些便捷的方法,比如说thenApply thenAccept thenRun等等可以让我们以链式的方式去处理异步任务的执行结果,从而更加灵活的去编写异步代码

Swagger

@Api()用于类; 表示标识这个类是swagger的资源 ,@Api 注解用于标注一个Controller(Class) @ApiOperation()用于方法; 表示一个http请求的操作

线程池的核心参数/线程池的执行原理你知道吗?

7个核心参数

线程池核心参数主要参考ThreadPoolExecutor这个类的7个参数的构造函数

  • corePoolSize 核心线程数目

  • maximumPoolSize 最大线程数目 = (核心线程+救急线程的最大数目)

  • keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放

  • unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等

  • workQueue(阻塞队列) - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务

  • threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等

  • handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略

如何确定核心线程数

  • IO密集型任务: 文件读写 DB读写 网络请求 不需要占用过多的CPU 线程核心数相对多一点 2N+1
  • CPU密集型任务:**计算型代码 Bitmap 转换 Gson转换 **

image-20231026110058320

如何确定CPU的核数?

image-20231026110519371

如过是并发高,且执行时间长,这种类型的任务不在乎设置线程池中核心线程的多少, 而是哪里可以缓存 解耦 去优化之后再考虑核心线程池中核心数的设置

通常情况下都是IO密集型的任务

线程池的种类有哪些?

1.创建使用固定线程数的线程池 (newFixedThreadPool)

image-20231026111158470

核心线程数与最大线程数一样,没有救急线程

阻塞队列是 LinkedBlockingQueue ,最大容量为Integer.MAX_VALUE

使用于任务量已知,相对耗时的任务

2.单线程化的线程池 (newSingleThreadExecutor)

单个线程池,核心线程数和最大线程数都是1

阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE

适用于按照顺序执行的任务

image-20231026112259879

image-20231026112422880


3.可缓存线程池 (newCachedThreadPool)

没有核心线程数,都是用临时线程去执行的任务 临时线程存活时间有60s

核心线程数为0

最大线程数是Integer.MAX_VALUE

阻塞队列为SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

适合任务数比较密集,但每个任务执行时间较短的情况

image-20231026112948492

4. 提供了“延迟”和“周期执行”功能的 (ThreadPoolExecutor) newScheduledThreadPool

newScheduledThreadPool:

new DelayedWorkQueue

image-20231026113344387

提交任务的方式之前都是submit提交任务

这次用的是 schedule 里面有3个参数 1.任务 2.任务执行的时间 3.时间单位

image-20231026114339725


  • 线程池的种类有哪些?

newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO)执行

newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

newScheduledThreadPool:可以执行延迟任务的线程池,支持定时及周期性任务执行

image-20231026110801679


为什么不建议使用Executors创建线程池

主要还是因为 integer.MAX_VALUE有关 可能会导致堆积大量请求,从而导致OOM(内存溢出)

线程池不允许 使用Executors 去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式写的同学更加明确线程池的运行规则,避免资源耗尽的风险

我们用固定大小线程池和单例线程池的时候,阻塞对应用到的是LinkedBlockingQueue:基于链表结构(单向链表)的有界阻塞队列,FIFO(先进先出)。 **没有设置容量大小默认最大长度就是 **, integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM

而缓存的线程池 CachedThreadPool最大线程数是 integer.MAX_VALUE 也会创建大量的线程,也可能会导致OOM

image-20231026114727210

我们推荐使用ThreadPoolExecutor的方式

这里结合一下我们之前做的伙伴匹配系统去理解一下

详细的知道这7个参数的规则,并且根据自己的硬件和业务需求去设置合适的参数

image-20231026115504702

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值