Java面试题

Js

变量是用于存储和表示数据的一个名字,可以用var、let或const关键词来声明
Js常见的数据类型 string、number、boolean、object、array和null和Undefined

Jsp

Jsp的基础概念:
Jsp是一种基于Java技术的服务器端编程技术
Jsp的生命周期加载:
初始化、执行、销毁
Jsp与servlet的关系:
servlet主要用于处理请求并生成数据模型,然后将数据模型传递给JSP页面进行展示,而Jsp主要用于表示层,即展示数据给用户。在实际的开发中,servlet通常用于处理请求并生成数据模型,然后将数据模型传递给jsp页面进行展示。Jsp可以通过内置对象(如request、session等)访问servlet传递过来的数据
Jsp中的指令和动作标签,jsp中有三种指令:page指令、include指令和taglib指令。其中page指令用于设置页面相关的属性(如语言、缓存等);include指令用于当前页面包含其他文件内容;taglib指令用于导入自定义标签库。JSP中的动作标签用于在JSP页面中执行特定的操作,如转发请求、包含其他内容、设置JavaBean属性值等。

Spring

Spring bean 生命周期
1、生产:加载和创建 Bean 定义和实例。
2、使用:在应用程序中管理和使用 Bean 实例。
3、销毁:适当地销毁和清理 Bean 实例。

Spring架构基础
Spring是一个开源的Java应用开发框架,旨在简化企业级应用的开发。它通过提供依赖注入(DI)和面向切面(AOP)等特性,帮助开发者可以更好的管理对象之间的依赖关系,提高代码的可重用性和可维护性。

Spring解决循环依赖
1、一级缓存(Singleton Objects):存储完全初始化好的bean,对于单例bean,这个缓存中的bean是已经可用的。

2、二级缓存(Early Singleton Objects):也称为早期对象暴露,这个缓存存储的是还没有完成初始化(属性填充和初始化方法还未执行)的bean的原始对象(即对象已经创建,但还没有进行属性填充和初始化)。这个缓存主要是为了解决循环依赖问题。

3、三级缓存(Singleton Factories):存储的是ObjectFactory对象,用于按需生成bean的早期引用。这个ObjectFactory能够生成bean的早期引用,并将这个引用加入到二级缓存中。
总结
Spring通过三级缓存机制有效地解决了单例作用域下的bean的循环依赖问题。然而,对于原型作用域下的bean,由于每次请求都会创建新实例的特性,Spring并不支持循环依赖的自动解决。在实际开发中,应尽量避免设计出现循环依赖的情况,因为这可能会增加系统的复杂性和维护成本。

Spring IOC容器
Spring Ioc容器是Spring框架的核心组件之一,负责创建、配置和管理应用对象其依赖关系。
它通过读取配置文件或注解信息,自动装配对象的依赖关系,实现对象的创建和初始化。Spring Ioc容器采用了一种反转控制(IOC)的设计模式,将对象的创建和管理权交给容器,
而不是由对象自己控制生命周期和依赖关系。有利于降低代码之间的耦合度,提高代码的可测试性和可维护性。

Spring Aop

Spring Aop是spring框架的另一个核心特性,它允许开发者在不修改现有代码的情况下,为应用添加新的功能和行为。Aop通过定义切点、通知和切面等概念,将横切关注点(如日志、事务管理等)从业务逻辑中分离出来,实现代码的模块化和复用。常见的aop使用场景包括日志记录、性能监控、事务管理、安全性控制等。
Spring事务管理
Spring事务管理允许开发者在应用程序定义和管理事务的边界和属性(如隔离级别、传播行为等)。支持声明式事务管理和编程式事务管理两种方式。声明式事务管理通过配置文件和注解方式实现,编程式事务管理通常与spring和ioc容器和aop技术结合使用,实现事务的自动提交和回滚

Spring MVC

Spring MVC是基于java的web开发框架,它采用了mvc的设计模式来组织代码结构。
Springmvc的主要组件包括前端控制器、处理器映射器、处理器适配器、控制器、模型和试图、视图解析器等。当用户发起一个http的请求时,前端控制器会根据请求的信息和处理器映射器找到对应处理器控制器,然后通过处理器映射器的相应方法来处理请求。处理器处理完请求后返回一个模型和视图对象给前端控制器,前端控制器再根据视图解析器的配置找到对应的视图进行渲染并返回给用户。

Spring boot

Springboot是一个快速构建spring应用的脚手架工具。它简化了spring应用的初始搭建和开发过程,通过提供自动配置和简化依赖管理等特性,使得开发者可以更快的创建和运行spring应用。

springboot的自动装配原理
Spring Boot通过分析项目的依赖和配置,自动配置Spring应用程序所需的组件,而无需手动编写大量的XML配置文件或Java代码。

Springboot的核心注解@SpringBootApplication
这个注解是@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个注解的组合。
– @SpringBootConfiguration:标识该类是Spring Boot应用程序的配置类,用于替代传统的XML配置文件。
– @EnableAutoConfiguration:自动配置注解,通过扫描classpath中的jar包,根据引入的依赖自动配置Spring框架。
– @ComponentScan:自动扫描注解,用于自动扫描并注册组件(Bean),包括@Controller、@Service、@Repository和@Configuration等注解。

Spring cloud

Springcloud是一个基于springboot的微服务架构解决方案。它提供了一系列用于构建微服务应用的工具和组件,包括服务发现(eureka、consul等)、负载均衡(ribbon、fegin等)、断路器(hystrix)配置管理(config server)、消息总线(bus)等。

注册中心能用哪些
1、eureka
2、Zookeeper
3、Nacos
4、Consul

eureka和nacos之间的区别
1、原生支持的模式:
Eureka:主要实现了AP(可用性和分区容错性)模式。Eureka的设计哲学是首先确保可用性,接受在某些极端情况下可能出现的服务注册信息的不一致。
Nacos:既支持AP模式也支持CP(一致性和分区容错性)模式。用户可以根据业务需求选择不同的模式,这使Nacos在使用场景上更为灵活。

2、功能范围:
Eureka:主要提供服务注册和发现功能。
Nacos:不仅提供服务注册和发现功能,还提供了动态服务配置管理(类似于Spring Cloud Config)和服务元数据管理的能力。因此,Nacos可以被看作是一个更全面的服务治理和配置管理解决方案。

3、配置管理:
Eureka:不提供配置管理功能。
Nacos:提供丰富的动态服务配置管理功能,如配置自动刷新、版本管理等。

4、数据一致性机制:
Eureka:采用客户端负载均衡的方式,客户端缓存注册表信息,通过心跳机制与服务端同步状态。不保证强一致性,而是采用最终一致性。
Nacos:根据选择的模式不同(AP或CP),采用不同的数据一致性策略。在CP模式下,Nacos使用基于Raft协议来确保集群数据的一致性。

5、部署和维护成本:
Eureka:相对简单,易于部署和维护。
Nacos:由于功能更丰富,因此相较于Eureka,其部署和维护可能稍显复杂。

6、连接方式:
Eureka:使用定时发送和服务进行联系,属于短连接
Nacos:使用的是netty和服务直接进行连接,属于长连接

7、保护机制:
Eureka:当在短时间内,统计续约失败的比例,如果达到一定阈值,则会触发自我保护的机制,在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制。自我保护开关(eureka.server.enable-self-preservation: false)
Nacos:当域名健康实例 (Instance) 占总服务实例(Instance) 的比例小于阈值时,无论实例 (Instance) 是否健康,都会将这个实例 (Instance) 返回给客户端。这样做虽然损失了一部分流量,但是保证了集群的剩余健康实例 (Instance) 能正常工作。

Tomcat

Tomcat是什么?
Tomacat是一个广泛使用的java servlet容器和web服务器用于运行一个servlet和jsp应用程序。它也可以作为一个独立的web服务器使用,或apache HTTP 服务器等其他web服务器一起使用。
如何在tomcat中配置ssl/tls以实现https的通信?
在tomcat中配置ssl/tls以实现https通信需要生成ssl证书并将其导入tomcat的密钥库中。然后在conf/ server.xml文件中配置 标签启用ssl/tls支持,并指定密钥库的位置和密码等相关参数。
Tomcat的性能调优参数有那些?
-xmn:设置堆新生代大小
-xss:设置每个线程堆栈的大小
-xx:NewRatio:设置年轻代(包括eden和survivor区)与年老代比值。
连接池大小、缓存策略等

Redis

Redis是什么?
Reid是一个开源的,内存中的数据结构存储系统,它可以用于做数据库、缓存和消息中间件。Redis支持多种类型的数据结构,如String、hashes、list、sets、sorted sets等类型
redis数据 String、hashes、list、sets、sorted sets 使用场景
1、String(字符串)使用场景
– 缓存功能
– 计数器功能
– 会话管理
– 分布式锁
– 消息队列
2、Hashes(哈希)
– 对象储存
– 数据聚合
3、List(列表)
– 任务队列
– 日志记录
– 消息通知
– 分页查询
4、Sets(集合)
– 去重
– 交集、并集、差集操作
– 分布式锁
5、Sorted Sets(有序集合)
– 排序
– 带权重
– 时间轴
– 范围查询
Redis缓存的作用是什么吗?
Redis主要用于存储常用的或重要的数据,通过缓存技术降低数据访问的延时,并提升系统的读取速度。通过将热点数据缓存到redis中,可以显著提高系统的访问速度,提升用户体验。
Redis持久化?
– RDB:在指定时间间隔内,定时的将redis存储的数据生成Snapshot快照并存储到磁盘等介质上。
– AOF:将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些指令从前到后重复执行一遍,就可以实现数据恢复了。
Redis缓存有哪些常见的使用场景?
– 缓存会话信息,如登录状态、购物车数据等
– 缓存热点数据,如网站首页的轮播图、商品详情页的数据等。
– 缓存一些计算量较大或查询较慢的数据,如数据库查询结果,计算结果等。
– 用作消息队列,实现分布式系统中的消息传递。
Redis的安全机制有哪些?
配置文件保护:使用reids的配置文件,将redis的访问权限设置为只允许本机访问。
访问控制:redis提供了auth命令,可以控制客户端的访问,设定特定的密码,只允许认证成功的客户端进行访问。
使用ssl加密:为reids提供ssl加密,将redis的传输信息加密,以防止数据被未授权的人获取到。
Redis缓存可能遇到的问题包括:
– 缓存击穿:一个存在于缓存中的热点数据,在某个时刻失效的情况下,同时有大量的请求访问这个数据。当缓存中的数据失效后,新的请求访问这个数据,发现缓存中没有这个数据,于是会将请求转发到后端数据库进行查询。这时由于大量请求同时访问数据库,导致数据库负载增加。解决方法包括把不存在的数据null存在缓存,或者布隆过滤器等技术。
– 缓存雪崩:如果大量请求查询的数据都不存在于缓存中,就会导致大量的请求到后端数据库,增加了数据库的负载,降低了系统性能。解决方法设置缓存的失效时间不要相同,或者对缓存进行预热等。
Redis的性能优化可以从多个方面入手
– 使用合适的数据类型
– 调整redis的配置参数:如调整内存分配策略、持久化策略等。
– 分布式部署redis集群,提高系统的处理能力和可扩展性。
– 监控和调优:使用redis的监控工具对系统进行实时监控,根据监控进行调优
Redis哨兵
Redis哨兵的作用和概念:
redis哨兵是一个独立的进程,用于监控redis主从服务器的运行状态。它的主要作用是自动发现和监控redis服务器的运行状态,并在主服务器宕机时自动进行故障转移,将从服务器切换为新的主服务器,从而保证redis集群的高可用性。
Redis哨兵如何监控redis集群:
Redis哨兵通过不断地向集群中的redis节点发送心跳检测来监控节点的健康状态。当哨兵检测到主节点失效时,会进行故障转移,选举新的主节点,并通知其他节点更新配置。
Redis哨兵如何实现故障转移:
– 哨兵之间会进行通信,确认主节点是否真的失效
– 选举出一个哨兵作为领导者来执行故障转移操作
– 领导者哨兵会从节点中选取一个作为新的主节点
– 领导者哨兵会更新集群的配置信息,并将新的主节点通知给所有其他哨兵和redis节点
Redis与主从复制的关系:
Redis哨兵与主从复制是相互依赖的。主从复制是redis实现数据备份和高可用性的基础,而哨兵则是用于监控和管理redis主从集群的进程。通过哨兵监控和管理,可以在主节点失效时自动进行故障转移,从而保证redis集群的高可用性
Redis哨兵的配置和使用:
在配置redis哨兵时需要指定哨兵监控的redis主节点地址和端口号,以及哨兵之间的通信方式(如IP地址和端口号)。同时,还需要配置哨兵进行故障转移时的相关参数,如选举新的主节点的规则、故障转移的超时时间等。
Reids哨兵的优势和限制:
Reids哨兵的优势在于能够自动监控和管理reids主从集群,实现故障转移和数据备份。但是,它也存在一些限制,如需要额外的资源来运行哨兵进程、在故障转移时可能会丢失部分数据等。
我和你是好友,我和另一个人是好友,我们的共同好友,如果推荐好友如何做?
Set(集合)和Sorted Set(有序集合)可能是最合适的选择,具体取决于你的具体需求。
对于“共同好友”和“推荐好友”这样的需求,Set 提供了基本的集合操作功能,适用于简单的共同好友查找和基于数量的推荐。而Sorted Set 则在Set的基础上增加了排序功能,适用于需要更复杂排序逻辑的推荐场景。具体选择哪种数据类型,需要根据你的具体需求和推荐逻辑来决定。
简历存redis缓存中用数据类型好一点?

  1. Hash
    优点:
    可以将简历的各个部分(如姓名、职位、经验等)作为Hash的字段来存储,便于按字段查询。
    易于更新简历的特定部分,而不需要重新加载整个简历。
    缺点:
    如果需要频繁地执行复杂查询(如全文搜索或跨多个字段的查询),则Hash可能不是最佳选择。
  2. JSON String
    优点:
    可以将整个简历序列化为一个JSON字符串,并存储在String类型的缓存中。
    便于在客户端(如Web浏览器)中解析和处理。
    缺点:
    更新简历的特定部分需要重新加载整个JSON字符串,进行反序列化、修改和重新序列化。
    查询性能可能不如Hash或更专门的数据结构。
    结论
    对于大多数情况,如果你需要按字段查询简历,并且希望更新操作尽可能高效,那么使用Redis的Hashes可能是最佳选择。如果你更关心于将整个简历作为一个整体来处理,并且不需要频繁的更新操作,那么将简历序列化为JSON字符串并存储在Redis的String类型中可能更简单。然而,如果你需要同时拥有这两种能力(即,既能按字段查询又能高效地处理整个简历),那么模拟一个Document Store可能是最佳的折衷方案。

redis 一些key 过期策略?
1、定期删除
2、惰性删除
3、内存淘汰

redis 核心单线程为什么速度快?
1、基于内存的操作:
Redis将数据存储在内存中,而内存的读写速度远快于磁盘。因此,与磁盘存储的数据库相比,Redis的数据访问速度非常快。
2、单线程模型:
Redis采用单线程模型来处理客户端的请求。这一模型避免了多线程带来的上下文切换开销和锁竞争,使得CPU资源能够更充分地用于数据处理,而不是线程管理。
3、非阻塞I/O和事件驱动机制:
Redis使用了事件驱动的非阻塞I/O机制。它通过一个事件循环来处理来自客户端的请求,在等待数据I/O时并不会阻塞主线程,而是继续处理其他请求。这种机制允许Redis以高效的方式处理大量的并发连接,提供高吞吐量和低延迟的性能。
4、高效的数据结构和算法:
Redis内置了多种高效的数据结构,如字符串、哈希表、列表、集合和有序集合等。这些数据结构经过优化,能够在单线程下提供高效的数据操作。此外,Redis还使用了一些高效的算法,如快速列表、跳跃表和布隆过滤器等,以提高查询和存储的效率。
5、优化的系统调用:
Redis对系统调用进行了优化,减少了频繁的系统调用开销。例如,Redis使用了自己的内存分配器,减少了内存分配和释放的系统调用次数。
6、并发控制和事务支持:
尽管Redis是单线程的,但它通过使用乐观锁和CAS(Compare and Swap)操作来实现并发控制。这种并发控制机制使得多个客户端的请求可以并行执行,而不会相互干扰。此外,Redis还提供了简单的事务支持,允许将多个命令作为一个原子操作进行执行,保证了数据的一致性和完整性。
7、基于Reactor模式的网络事件处理器:
Redis基于Reactor模式开发了自己的网络事件处理器——文件事件处理器。这个处理器使用I/O多路复用程序来同时监听多个socket,并根据socket目前执行的任务来为socket关联不同的事件处理器。这种设计使得Redis能够高效地处理大量的并发连接,提高了整体性能。
综上所述,Redis核心单线程之所以速度快,是因为其基于内存的操作、单线程模型、非阻塞I/O和事件驱动机制、高效的数据结构和算法、优化的系统调用、并发控制和事务支持以及基于Reactor模式的网络事件处理器等多个因素共同作用的结果。这些设计和优化使得Redis在处理大量并发请求时能够保持高性能和低延迟。

Mysql和mycat

mysql的常见索引
1、b-树索引
特点:平衡树形结构,适合范围查询。MySQL中的大部分索引(如PRIMARY KEY、UNIQUE、INDEX)都是使用B-树或其变种(如B+树)来实现的。
应用场景:适用于全键值、键值范围和键值前缀查询,也可以对查询结果进行ORDER BY排序。
2、哈希索引
特点:基于哈希表实现,可以快速查找特定值。MySQL中,只有MEMORY/HEAP存储引擎支持哈希索引,且将其视为默认索引。
应用场景:适用于等值比较,如“=”、“IN()”查询,但不支持范围查询和排序操作。

3、常规索引
特点:最基本的索引类型,允许在定义索引的列中插入重复值和空值。
应用场景:主要用于加快系统对数据的访问速度。

索引:
– 主键索引:索引列中的值必须是唯一的,不允许有空值
– 普通索引:基本索引类型可以有空值
– 唯一索引:索引列中的值必须是唯一的,但是允许为空值
– 复合索引

虽然B+树是MySQL中索引的常用数据结构,但MySQL也支持其他类型的索引,以满足不同场景下的需求。在选择索引时,需要综合考虑数据的特性、查询的需求以及存储引擎的支持情况

MySQL 在使用like的情况下什么时候使用索引什么时候不使用索引?
使用索引的情况:
1、通配符在字符串末尾
2、无通配符
3、前缀索引
不适用索引的情况:
1、通配符在字符串开头
2、通配符在字符串中间
3、索引失效的其它情况
– 如果查询条件中使用了 OR 运算,并且不是所有参与运算的字段都存在索引,那么索引可能不会被使用。
– 如果查询中使用了函数或表达式对索引列进行操作,那么索引也可能失效。
– 如果 MySQL 认为全表扫描比使用索引更快(例如,当查询的数据量占表总数据量的比例较高时),它可能会选择不使用索引。

一张表有两个索引 一个是 abc三个字段 ,i字段索引,abc构成的索引,有执行哪个

1、查询条件只涉及i字段:如果查询条件只涉及到i字段,那么优化器很可能会选择使用i字段的索引,因为这是最直接且高效的。

2、查询条件涉及a、b、c字段的任意组合:

如果查询条件只涉及到a、b、c中的某个字段(假设是a),并且优化器认为使用复合索引比全表扫描更高效,那么它可能会使用复合索引的前缀部分(即a字段)。但是,请注意,如果查询条件只涉及c字段,而复合索引是a, b, c的顺序,那么优化器可能不会选择这个复合索引,因为它需要跳过a和b字段来访问c字段,这通常不是最高效的。
如果查询条件涉及a、b、c字段的任意连续组合(如a和b,或a、b和c),那么优化器可能会选择复合索引,因为它可以更有效地定位数据。

3、查询条件同时涉及i字段和a、b、c字段:

在这种情况下,优化器会评估使用哪个索引更有效率。如果查询条件使得通过i字段索引过滤后的数据量远小于通过a、b、c复合索引过滤后的数据量,那么它可能会选择i字段索引。反之,如果a、b、c字段的过滤条件非常具有选择性,那么它可能会选择复合索引。

4、查询优化器决策:最终的决定由查询优化器根据统计信息和成本估算来做出。它可能会选择使用索引,也可能会决定不使用索引(例如,当表非常小,或者索引过滤后的数据量接近全表数据量时)。

结论
因此,对于“有执行哪个”的问题,没有一个固定的答案。它取决于查询的具体内容、表的统计信息以及数据库优化器的决策。在实际应用中,你可以通过查看查询的执行计划(在MySQL中可以使用EXPLAIN或EXPLAIN ANALYZE语句)来了解优化器是如何选择索引的。

这个表有一个i字段索引where有i条件是否一定走索引?
索引可能走的情况:
1、简单查询
2、覆盖索引
索引可能不走的情况:
1、索引选择性低
2、查询条件复杂
3、优化器策略

讲讲mysql有几个事务隔离级别?
– 读未提交
– 读已提交
– 可重复读
– 串行化
什么是回表?
在MySQL中,“回表”是指在执行查询时,需要通过索引查找到数据行的位置,然后再回到原始的表中获取完整的数据行。

聚簇索引和非聚簇索引到底有什么区别?
主键索引即为聚簇索引,一个表只有一个,而非聚簇索引数量不限。聚簇索引查询效率较高,非聚簇索引需要额外的回表操作。

为什么mysql选可重复读作为默认的隔离级别?
– 避免脏读:在较低的隔离级别,如读未提交(Read Uncommitted),一个事务可以读取到另一个未提交事务修改的数据,这可能导致数据的不一致性和不可预测性。可重复读及以上级别则避免了这种情况。
– 避免不可重复读:在可重复读级别,当事务重新读取之前读取过的数据时,所看到的数据与之前的数据是一致的,即使其他事务在此期间提交了更新。这确保了同一事务内多次读取同样记录的结果是一致的,从而避免了不可重复读的问题;
– 平衡一致性与性能:虽然更高的隔离级别(如串行化Serializable)能提供更严格的数据一致性保证,但也会引入更高的性能开销,因为需要更多的锁和更复杂的事务调度。可重复读在提供足够的数据一致性保护的同时,相比串行化而言,其性能开销较小,更适合大多数应用场景;
– MySQL的实现特性:MySQL的InnoDB存储引擎在可重复读级别下使用了一种称为“多版本并发控制(MVCC)”的技术。MVCC允许数据库读取操作不加锁,从而提高了并发性能。在可重复读级别下,MVCC能够确保即使在有大量并发事务的情况下,也能保持数据的一致性和隔离性;
– 历史原因和兼容性
数据库优化:
Sql语句的优化:
避免使用explain关键字分析sql语句的执行计划,确保它们按预期使用索引。
避免使用select *。而是指定需要查询的列
使用批量操作以减少数据库连接的开销
索引优化:
– 创建并合理使用索引以加速查询
– 定期优化和删除不必要的索引
数据库连接池:
使用数据库连接池以减少连接和断开的开销
根据负载调整连接池大小
分库分表后的优化:
确保分片策略合理,避免跨库或者跨表查询
监控分片后的数据库性能,并根据需要进行调整
主从复制的性能调优
监控主从复制的延迟,确保数据同步的实时性。
监控主从复制的配置参数,如binlog_format和sync_binlog等,以提高复制性能。
缓存的使用:
使用redis等缓存系统来缓存热点数据,减少对数据库的访问
合理配置缓存的过期时间和淘汰策略
硬件和配置优化:
根据数据库的负载调整硬件资源,如cpu、内存、磁盘等。
调整数据库配置参数,如inndb_buffer_pool_size、max_connections等,以适应实际的负载情况
Mysql主从复制
主从复制的配置:
在主服务器上启用二进制日志,并配置一个唯一的server-id.
在服务器上配置主服务器的连接信息,并指定要复制的主服务器上的数据库和表
Mysql分库分表
分库分表的策略:
垂直切分:按业务和功能进行拆分,不同的业务数据放到不同的数据库中。
水平切分:按某个字段(如用户ID)的某个规则(如哈希值)将数据拆分到不同的数据库或表中。
Mycat
mycat的特点:
支持多种分片策略,如基于范围、哈希、枚举等。
支持读写分离,自动将读请求路由到从服务器
支持全局事务,确保跨库事务的一致性
Rabbitmq
高并发处理方案:
1、数据重复
– 消息去重:在消费者端维护一个已处理消息的唯一ID集合,当接收到消息时,先检查其ID是否存在于集合中,如果存在则直接丢弃,避免重复处理。
– 幂等性设计:确保无论消息被处理多少次,对系统的影响都是相同的。这通常需要在业务逻辑层面进行设计和实现
– rabbitmq的确认机制:rabbitmq支持消费者手动消息,消费者在处理完消息后发送确认,rabbitmq才会将消息从会将消息队列中删除。这可以确保消息只被处理一次
2、消息丢失
– 持久化消息:在rabbitmq中,可以通过将队列和消息都设置为持久化来确保消息不会丢失。这样,即使rabbitmq服务器重启,消息也能保存下来
– 消费者确认机制:如前所述,rabbtimq支持消费者手动确认消息。消费者在处理完消息后发送确认,rabbitmq才会将该消息从队列中删除。如果消费者在处理消息时崩溃,rabbitmq会将消息重新放入队列,等待其他消费者处理。
– 生产者重试机制:在生产者发送消息时,可以配置重试机制。如果发送失败(如网络问题),生产者会在一段时间后重试发送消息
3、高并发处理
– 增加消费者数量:在高并发场景下,可以通过消费者的数量来提高消息的处理能力。Rabbitmq支持多个消费者同时消费同一个队列中的消息。
– 消息预取:rabbitmq允许消费者预取一定数量的消息进行并发处理。这样可以减少消费者与rabbitmq之间的网络交互次数,提高处理效率
– 负载均衡:在分布式环境中,可以使用负载均衡算法均匀地分发给不同的消费者节点,以实现负载均衡和分流
4、其他的注意事项
– 监控和告警:对rabbitmq集群进行实时监控,包括队列长度、消费者数量、消息处理速度等指标。当发现异常情况时,及时发出告警并采取相应的处理措施
– 资源限制与监控:合理设置rabbitmq节点所能够处理的最大连接数、最大通道数和最大队列数等资源限制。同时,通过监控工具实时监测系统使用情况,及时发现并解决潜在的性能问题

Mybatis-plus

Mybatis-plus的主要优点是什么?
– 提高开发效率:通过代码生成、分页、查询构建等功能
– 简化操作:提供常用api和工具,简化crud操作,批量操作等
– 提高代码可读性:提供lambda表达式API,使代码更简洁易读。
Mybatis-plus的主要api有有些?
– Querywrapper:用于构建查询条件
– Page:用于分页查询
– BaseMapper:提供基础CRUD操作接口

Elasticsearch(ES)

1、倒排索引:Elasticsearch底层依赖于Lucene库,它使用倒索引是一种将文档中的词项映射到包含这些词项的文档的列表的数据结构
2、增量更新:当B2C平台上的商品数据发生变化时(如新增商品、商品信息更新或删除商品),es能够通过增量更新的方式快速构建新的倒排索引。
3、自定义分词器和过滤器

多线程

生命周期:新建、就绪、运行、阻塞
新建状态的线程由JVM分配内存并初始化;就绪状态的线程等待获取CPU资源;运行状态的线程正在执行代码;阻塞状态的线程因为某种原因(如等待I/O)而暂停执行;死亡状态的线程表示该线程已经执行完毕或者因为异常而终止。
线程和进程的区别?
– 线程:线程是进程内的一个小的执行单元,多个线程共享同一个进程的内存和资源。线程之间可以更容易地共享数据和通信,因为它们处于同一个地址空间内
– 进程:每个进程都有独立的内存空间和系统资源,创建和销毁进程的开销相对较大。
什么是死锁,死锁的四个条件?
死锁(Deadlock)是指两个或多个进程在竞争资源时,因彼此之间的互斥和等待而陷入无限等待的状态,导致它们都无法继续执行下去。死锁是一种程序设计或系统管理的错误,它会导致应用程序无响应或挂起,需要手动干预才能解决。
死锁通常具备以下四个必要条件,也被称为死锁条件:
1、互斥条件
至少有一个资源是独占的,即一次只能被一个进程或线程占用。如果多个进程或线程同时需要访问这个资源,就会出现互斥条件。
2、请求与保持条件
进程或线程在持有至少一个资源的同时,又请求其它资源,但无法立即获得所需资源。这就会导致持有资源的进程或线程等待其它资源的释放,形成循环等待。
3、不可剥夺条件
资源不能被强制性地从一个进程或者线程中抢占,只能由占用它的进程或线程主动释放。
4、循环等待条件
多个进程或线程之间形成一个资源的循环等待,每个进程或线程都在等待下一个进程或线程所持有的资源,最终导致所有进程或线程都无法继续执行。
要解决死锁问题,需要破坏其中任何一个必要条件,以防止死锁的发生。
什么是线程饥饿现象?
线程饥饿(Thread Starvation)是指在多线程应用中,某些线程由于竞争有限的资源而无法获取执行的机会,导致它们长时间处于等待状态,无法完成其工作现象。线程饥饿通常是由于不公平的资源分配、锁竞争、线程优先级设置不合理等因素引起的。
线程池核心参数:
1.corePoolSize:核心线程池(线程池内部运行起来之后,最少有多少个线程等活.核心线程是懒加载)

2.maximumPoolSize:最大线程数(当工作队列堆满了,再来任务就创建非核心线程处理)

3.keepAliveTime:最大空闲时间(默认非核心线程,没活之后,只能空闲这么久,时间到了干掉)

4.unit:空闲时间单位(上面的时间单位)

5.workQueue:工作队列(当核心线程数足够后,投递的任务会扔到这个工作队列存储.LinkedBlockingQueue)

6.threadFactory:线程工厂(构建线程的,根据阿里的规范,线程一定要给予一个有意义的名字,方便后期排查错误)

7.handler:拒绝策略(核心数到了,队列拉满了,非核心数到了,再来任务走拒绝策略…)

为什么创建线程池不建议使用JDK自带的线程池?
1、潜在的资源耗尽问题
– 无界队列问题:Executors.newFixedThreadPool和Executors.newSingleThreadExecutor默认使用无界队列(如LinkedBlockingQueue)。当任务提交速率超过处理速率时,任务会无限堆积在队列中,如果任务量过大,可能会导致内存溢出(OutOfMemoryError),因为队列没有容量限制。
– 线程数量无限制:Executors.newCachedThreadPool会无限制地创建新线程(直到达到系统允许的最大线程数,尽管实际中很少达到Integer.MAX_VALUE),在高并发场景下,这可能会迅速消耗大量系统资源(如内存和CPU),甚至导致服务崩溃。

2、配置固定且缺乏固定性
– 固定配置:JDK内置的线程池配置较为固定,可能无法满足特定应用场景的需求。例如,它们可能没有合适的监控、异常处理机制或自定义的拒绝策略等。
– 缺乏灵活性:这些线程池无法根据具体的应用场景进行细致的参数调整,如设置线程存活时间、选择不同类型的队列等。

3、默认的拒绝策略不明确
– 处理不明确:JDK内置的线程池对拒绝策略的处理往往不够明确,开发者可能不清楚在任务堆积严重时,线程池是如何处理新提交的任务的。这可能导致在任务被拒绝时,系统行为不符合预期。

4、监控和管理功能基础
– 监控不足:默认线程池的监控和管理功能相对基础,对于复杂应用来说,可能需要更高级的监控和控制机制,如动态调整线程池大小、查看线程池状态等。
解决方案:
为了避免上述问题,建议开发者直接使用ThreadPoolExecutor构造函数来创建线程池,并明确配置各项参数,如核心线程数、最大线程数、工作队列容量、线程存活时间以及拒绝策略等。这样可以更灵活地控制线程池行为,确保系统稳定、高效运行。同时,也需要注意对线程池进行有效的监控和管理,以便及时发现并解决问题。

最大线程数和核心线程数和线程队列创建线程,最大线程数5,核心线程数1,线程队列10,执行流程是什么?
1、当任务提交给线程池时:
– 首先,线程池会检查当前线程数是否少于核心线程数(1)。如果是,线程池将创建一个新线程来执行该任务,即使其它核心线程处于空闲状态。
2、如果核心线程数已满:
– 线程池会将任务添加到队列,直到队列满。在这个例子中,队列的容量为10,所以它可以额外存储10个任务。
3、如果队列也满了:
线程池会检查当前运行的线程数是否少于最大线程数(5),在这个场景中,即使只有一个核心线程在运行,并且队列已满,线程池仍会创建新线程(最多到最大线程数)来执行队列中的任务。

4、如果达到最大线程数:
如果此时线程池中的线程数已经到达最大(5),并且队列也已满(10)个任务,则线程池会根据配置的拒绝策略来处理新提交的任务。默认情况下,这是TreadPoolExecutor.AbortPolicy,它将抛出RejectedExecutionExcepotion。
产生线程饥饿有几个原因:
1、锁竞争
如果多个线程竞争获取某个锁,并且锁的分配不公平,某些线程可能会一直无法获得执行机会。这可能会导致低优先级线程长时间等待,无法执行。
2、线程优先级不均衡
如果线程的优先级设置不和理,高优先级线程可能会抢占资源,导致低优先级线程无法获得执行机会。这可能会导致低优先级线程长时间等待,无法长时间等待,无法执行。
3、资源瓶颈
当多个线程竞争有限的系统资源,如cpu时间、内存等时,某些线程可能会长时间等待资源的释放,导致线程饥饿。这种情况下,系统资源的不合理分配可能会导致线程无法平等地访问资源。
4、死锁
死锁是线程饥饿的一种极端状况,其中多个线程彼此等待对方释放资源,导致所有线程无法继续执行。
如何理解线程的同步与异步、阻塞和非阻塞?
同步(Synchronous)与异步(Asynchronous)
– 同步:在同步编程中,任务按照顺序执行,一个任务完成后,下个任务才会开始执行。这意味着任务之间的执行是相互依赖的,后一个任务通常会等待前一个任务完成才能执行。
– 异步:在异步编程中,任务可以并行执行,一个任务不必等待能一个任务完成九城继续执行。任务之间通常通过回调函数、事件处理或者异步操作来实现协作。异步编程有助于提高程序的响应性和效率。
阻塞(Blocking)与非阻塞(Non-Blocking)
– 阻塞:在阻塞模式下,当一个任务执行时,如果它需要等待某个资源或者事件完成,它会阻止其它任务的执行,直到资源或事件可用。这意味着任务会停滞在等待状态,无法执行其它工作。
– 非阻塞:在非阻塞模式下,任务可以继续执行。即使它需要等待某些资源和事件。任务会定期查询资源或事件资源状态,如果资源不可用,它可以执行其它工作而不会停滞。
这些概念组合在一起,产生四种不同的情况:
1、同步阻塞:任务按顺序执行,并等待资源时阻塞。
2、同步非阻塞:任务按顺序执行,但在等待资源时定期查询,可以执行其它任务
3、异步阻塞:任务可以并行执行,但在等待异步操作完成时阻塞。
4、异步非阻塞:任务可以并行执行,并且在等待异步时不会阻塞,可以继续执行其它任务

创建多线程有几种方式,如何实现?
1、继承Tread类创建线程类
2、通过Runnable接口创建线程类
3、通过Callable和Future创建线程
4、通过线程池创建线程
多线程容易出现的问题?
1、数据不一致:在多线程环境下,如果多个线程同时访问和修改共享数据,可能会导致数据不一致。例如:在库存扣减的场景中,如果没有适当的同步机制,可能会导致超卖或库存不准确。
解决方案:使用Synchronized关键字,Lock接口,ReetrantLock类,读写锁(ReadWriteLock)等同步机制来确保同一时间只有一个线程来确保同一时间只有一个线程可以访问和修改共享数据。另外也可以考虑数据库事务来保证数据的一致性。
2、死锁:当两个或更多的线程无限期地等待一个资源,而资源又被另一个线程持有,且该线程也等待其它线程释放资源时就会发生死锁。
解决方案:避免嵌套锁,按照一致的顺序获取多个锁,设置超时时间。同时,使用监控工具即使发现死锁并进行排查。
3、线程安全:在多线程环境中,如果代码没有正确地同步,就可能导致线程安全问题。例如:非线程安全地集合类(如ArrayList)在多线程环境下使用可能就会导致数据混乱。
解决方案:使用线程安全集合类(如ConcurrentHashMap、CopyOnWriteArrayList等),或者使用同步块和锁来确保线程安全。

什么是线程安全?
线程安全就是说多线程访问同一段代码,不会产生不确定的结果。

线程安全有几个级别?
(1)不可变
像String、Integer、Long这些都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境使用
(2)绝对线程安全
不管运行时环境如何,调用者都不需要额外的措施。要做到这一点通常需要付出额外的代价,Java中标注自己是线程安全的类,绝大多数都不是线程安全的,不过绝对的线程安全的类Java也有,例如CopyOnWriteArrayList,CopyOnWriteArraySet
(3)相对线程安全
相对线程安全也就是通常意义上所说的安全,像Vector这种,add,remove方法都是原子操作,不会被打断,但也仅限于此,如果某个线程在遍历vector,有个线程同时在add这个vector,99%的情况下会出现ConcurrentModificationException,也就是fail-fast机制。
虽然vector类是线程安全的,但着并不意味着它的所有操作组合在一起使用时都是线程安全的,或者它在所有使用场景下都能防止并发异常(ConncurrentModificationException)。
Vector类中的大部分方法(如add、remove等)都被设计为同步的,这意味着在单个方法调用期间,它们会锁定vector对象以确保原子性。但是这并不能保证在更加复杂的操作序列中,例如迭代和修改操作的组合,也能保证线程安全
ConncurrentModificationException通常是在使用迭代器(如Iterator或Lislterator)遍历集合时,如果在迭代过程中集合的结构被其它线程修改(比如添加和删除元素)而抛出的。这是因为迭代器在创建时保存了集合的一个快照,并在迭代过程中使用这个快照,并在迭代过程中使用这个快照来检查集合的修改。如果集合在迭代过程中被修改,并且这个修改没有被迭代器捕捉到(即没有通过迭代器的remove方法来删除元素),那么迭代器会抛出conncurentModificationException。
Vector对的fail-fast机制与conncurentModificationException是相关的,但fail-fast通常是迭代器在检测到并发修改时立即失败的行为,而不是指vector类本身的线程安全性。
(4)线程非安全
这个就没什么好说的了,ArrayList、LinkedList、HashMap等都是线程安全非安全的类。
4、资源竞争:当多个线程同时访问有限地系统资源(如cpu、内存、数据库连接等)时,可能会导致资源竞争和性能下降。
解决方案:合理地设计线程池和队列来限制并发数,使用缓存技术(如reids)减少对数据库地访问,对资源池进行池化优化(如数据库连接池,线程池等)。
线程池的四大拒绝策略
AbortPolicy(默认):当任务被拒绝时,会抛出“RejectedExecutionException”异常。
DiscardPolicy:当任务被拒绝时,不会抛出异常,而是直接丢弃任务。
DiscardOldestPolicy:当任务被拒绝时,线程池会丢弃队列中最旧的未执行的任务,然后将被拒绝的任务添加到等待队列当中。
CallerRunsPolicy:当任务被拒绝时,调用execute方法的线程将负责执行该任务。
在BtoC系统中,多线程多用于以下几种业务场景:
1、实时更新和查询:BtoC电商系统需要实时更新商品信息、价格、库存等,并允许消费者随时查询。通过使用多线程,系统可以同时处理多个更新和查询请求,确保数据的实时性和一致性,同时提高系统的响应速度。
2、订单处理和支付:在消费者下单和支付的过程中,系统需要同时处理多个订单和支付请求。通过使用多线程,可以并行处理这些请求,提高订单处理和支付的速度,减少消费者的等待时间。
3、个性化推荐服务:BtoC电商系统通常提供个性化的推荐服务,根据消费者的购买历史和浏览行为推荐相关商品。这个过程涉及大量的数据处理和分析,使用多线程可以并行处理不同的推荐请求,提高推荐的准确性和效率。
4、并发用户访问:在高峰时段,BtoC电商系统可能会面临大量的并发用户访问。通过使用多线程,系统可以同时处理多个用户的请求,确保系统的稳定性和可扩展性。
5、后台任务处理:除了实时处理和用户交互外,BtoC电商系统还需要执行一些后台任务,如数据备份、日志清理、统计报表生成等。这些任务通常不需要实时响应,但需要长时间运行。通过使用多线程,可以将这些任务分配给不同的线程并行执行,提高系统的整体性能。
并发用户访问是许多在线业务场景中的常见问题,为了应对高并发访问,可以采取以下技术方案
1、负载均衡
2、缓存技术
3、数据库优化
4、异步处理
5、限流与降级
– 在高并发场景下,为了保护系统稳定,需要对请求进行限流和降级。
– 限流技术可以通过控制请求的频率、数量等方式,防止系统过载。常见的限流算法漏桶算法、令牌桶算法等。

6、服务拆分与微服务架构
7、并发控制技术:
– 在数据库层面,可以采用并发控制技术来确保数据的一致性和完整性。
– 常用的并发控制技术包括封锁(Locking)、时间戳(TimeStamp)、乐观锁(Optimistic Locking)和多版本并发控制(MVCC)等。

限流算法

1、漏桶算法:
– 原理:漏桶算法的主要目的是控制数据注入到网络的速率,平滑网络的突发流量。它可以将突发流量整形为稳定的流量,从而确保系统的稳定运行。
– 工作方式:漏洞算法可以看作是一个带有常量服务时间的服务器队列。当请求到达时,它们被看作是流入桶中的水。桶有一个固定的容量,这代表了系统能够处理的请求最大数量。桶底部有一个漏洞,水(即请求)以固定的速率从桶中漏出,这个速率代表的系统处理的请求速率。如果桶满了,新流入的水(请求)会被丢弃,直到桶中有足够的空间。
– 特点:漏桶算法能够强行限制数据的传输速率,确保系统处理速率不会超过设定的阈值。然而,在某些情况下,漏桶算法可能无法有效地使用网络资源,因为漏出速率是固定的,所以即使网络中没有发生拥塞,漏桶算法也不能使一个单独的数据流达到端口速率。
2、令牌桶算法
– 原理:令牌桶算法主要用于控制发送到网络上的数据的数目,并允许突发数据的发送。它通过限制流量的平均值来稳定输出,并在短时间内应对高额流量
– 工作方式:令牌桶算法以固定的速率往桶内放入令牌。当请求到达时,如果桶内的令牌足够多,请求就会立马被处理。这意味着令牌桶算法能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。
– 特点:与漏桶算法相比,令牌桶算法更加灵活。它能够满足具有突发特性对的流量需求,因为只要桶内有足够的令牌,就可以处理突发的大量请求。这使得令牌桶算法在某些情况下比漏桶算法更加高效。
总结:漏桶算法和令牌桶算法在限流算法中各有优点。漏桶算法能够强制限制数据的传输速率,确保系统的稳定性;而令牌桶算法则更加灵活,能够满足突发特性流量需求。在实际的应用中,可以根据具体的场景和需求选择合适的算法。
例子:
在BtoC的应用程序中,漏桶算法和令牌算法通常用于处理与流量和速率相关的业务场景。
漏桶算法:
1、流量控制:在高并发的BtoC应用中,为了保护系统免受过载的影响,可以利用漏桶算法对流量进行控制。通过限制每个请求进入系统的速率,确保系统能够平稳处理请求。这在访问频率限制,API接口限流等场景中非常常见。
2、平滑网络传输:漏桶算法还可以用于平滑网络传输中的数据流。在BtoC应用程序中,当用户访问网站或应用时,会产生大量的网络请求。通过漏桶算法,可以固定传输速率,使得数据包以均匀的速度发送,避免网络拥塞和突发流量对网络性能造成的影响。
3、缓解突发流量压力:当BtoC系统面对 突发的流量压力时,如促销活动、新品发布等,漏桶算法可以起到缓冲的作用。通过设定合理的桶容量和出水速率,可以使系统在短时间内处理过载的请求,并释放剩余的请求。
令牌桶算法:
1、网络流量控制:在BtoC应用中,令牌桶算法同样可以用于,控制网络流量,防止网络拥塞和过载。与网络带宽和流量负载等参数结合,令牌桶算法可以动态调整发送数据的速率和数量,保证网络的稳定性和可靠性。
2、服务器资源管理:BtoC应用通常需要处理大量的用户请求和数据。为了确保服务器的稳定运行和资源的合理利用,可以利用令牌桶算法控制对服务器资源的访问,如CPU和内存,磁盘和网络等。通过控制请求的处理速率和资源分配,防止服务器过载和崩溃。
3、个性化推荐服务:在BtoC应用中,个性化推荐服务是一个重要的组成部分。由于每个用户的兴趣和行为都不同,因此需要实时处理大量的用户数据和请求。通过令牌桶算法,可以确保个性化推荐服务的处理速率和稳定性,提高用户体验和满意度。
4、个性化推荐服务:在BtoC应用中,个性化推荐服务是一个重要的组成部分。由于每个用户的兴趣和行为都不同,因此需要实时处理大量的用户数据和请求。通过令牌桶算法,可以确保个性化推荐服务的处理速率和稳定性,提高用户体验的满意度。
总结:需要注意的是,漏桶算法和令牌桶算法并不是互相排斥的,而是可以根据业务场景进行选择和组合。在实际应用中,可以根据系统的特点、流量特性以及性能要求等因素来选择合适的限流算法,已达到最佳的效果。

【分布式】CAP理论详解

在分布式系统中,CAP是指一组原则,它们描述了在网络分区(Partition)时,分布式系统能够提供的保证。CAP代表Consistency(一致性)、Availability(可用性)和Partition Tolerance(分区容错性)。

一致性:代表数据在任何时刻、任何分布式节点中所看到的都是符合预期的;

可用性:代表系统不间断地提供服务的能力;

分区容忍性:代表分布式系统在面对网络分区(Network Partition)时仍然能够正常运行的能力。网络分区是指系统中的节点之间无法互相通信或通信延迟非常高的情况。

这个定理里描述了一个分布式的系统中,涉及共享数据问题时,以下三个特性最多只能同时满足其中两个。

集合

List
特点:
– 有序性:List集合中的元素是按照一定的顺序排列的,每个元素都有一个索引值,可以通过索引值访问元素。
– 可重复性:List集合允许存储重复的元素,即同一个元素可以在List中出现多次。
– 动态性:List集合的大小是动态变化的,可以根据需要添加或删除元素。

ArrayList和LindList 区别:
1、底层数据结构
ArrayList:基于动态数组实现。
LinkedList::基于双向链表实现。
2、性能特点
– 随机访问:由于ArrayList是基于数组的,它支持通过索引快速访问元素,时间复杂度为O(1)。而LinkedList则需要从头节点或尾节点开始遍历链表来查找元素,时间复杂度为O(n)。
– 插入和删除:在ArrayList中插入或删除元素时,如果操作位置不是数组末尾,则需要移动该位置之后的所有元素,因此时间复杂度为O(n)。而在LinkedList中,插入或删除元素只需要修改相邻节点的指针即可,时间复杂度为O(1)(在已知插入或删除位置的前提下)。然而,如果不知道插入或删除位置,需要先遍历链表找到该位置,时间复杂度则为O(n)。

3、内存占用
– ArrayList:由于ArrayList中的元素在内存中连续存储,因此它通常比LinkedList占用更少的内存(不考虑扩容时的额外开销)。但是,ArrayList在扩容时可能会产生较大的内存开销,因为需要复制整个数组到新的内存区域。
– LinkedList:LinkedList中的每个节点都需要额外的空间来存储指针(或引用),因此它在存储相同数量的元素时可能会占用更多的内存。但是,LinkedList不需要像ArrayList那样在扩容时复制整个数组,因此在某些情况下可能具有更好的内存使用效率。

与Set和Map的区别:
– List是有序的,而Set和Map是无序的(尽管Map的迭代顺序在Java 8及以后版本中得到了改进,但理论上仍然是无序的)。
– List允许存储重复元素,而Set不允许存储重复元素。
– List主要用于存储有序的数据列表,而Set和Map则用于不同的场景(Set用于存储不重复的元素集合,Map用于存储键值对)。

线程安全性:
ArrayList和LindList都是非线程安全的。这意味着如果多个线程同时修改它们,可能会导致数据不一致或其它并发问题。
Vector是线程安全的,但它通过在每个方法上添加同步来实现这一点,这可能导致性能下降。
解决方案:
使用Collections.synchronizedList()方法来包装一个非线程安全的List,以提供线程安全的访问。
使用并发集合,如CopyOnWriteArrayList,它适合用于读多写少的场景。
Set
特点:
– 无序性:Set集合中的元素是无序的,即不保证元素的添加顺序。
– 唯一性:Set集合中的元素是唯一的,不允许存储重复的元素。
– 动态性:Set集合的大小也是动态变化的,可以根据需要添加或删除元素。

与List和Map的区别:
– Set是无序的,而List是有序的。
– Set不允许存储重复元素,而List允许。
– Set主要用于存储不重复的元素集合,而List和Map则用于不同的数据结构需求。

线程安全性:
HashSet和TreeSet都是非线程安全的。
没有直接提供线程安全的Set实现,但可以实现使用Collections.synchronizedSet()方法来包装一个非线程安全的Set。
解决方案:
使用Collections.synchronizedSet()来包装一个非线程安全的Set。
使用并发集合CopyOnWriteArraySet(基于CopyOnWriteArrayList()),但要注意它只适合读多写少的场景。
使用ConcurrentSkipListSet,它是一个基于跳表的线程安全的Set实现。
Map
特点:
– 键值对映射:Map集合中的元素是以键值对(Key-Value Pair)的形式存储的,每个键都映射到一个唯一的值。
– 键的唯一性:Map集合中的键是唯一的,不允许重复。
– 无序性:Map集合中的键值对是无序的,即不保证键值对的添加顺序。

与List和Set的区别:
– Map存储的是键值对,而List和Set存储的是单个元素。
– Map的键是唯一的,而List允许重复元素,Set虽然不允许重复元素但存储的是单个元素而非键值对。
– Map主要用于存储键值对形式的内容,适用于需要根据键快速查找值的场景;而List和Set则分别适用于存储有序的数据列表和不重复的元素集合。

线程安全性:
HashMap和TreeMap都是非线程安全的。
Hashtable是线程安全的,但同样因为方法级别的同步而可能导致性能问题。
TreeMap是有序的,HashMap是无序的。
TreeMap底层是由树(红黑树)实现的,而HashMap是由哈希桶实现的。
由于哈希算法本身的优势,我们再进行增删查改的时候。
解决方案:
使用可以使用线程安全的Map实现,如ConcurrentHashMap,或者在访问HashMap时对其进行同步处理。
线程安全的集合
1、Vector
– 特点:Vector是Java早期提供的一个动态数组实现,与ArrayList类似,但其所有方法都添加了synchronized关键字,以确保线程安全。因此,Vector的每次操作都会进行线程同步,这虽然保证了线程安全,但降低了性能。
– 适用场景:适用于需要线程安全但并发量不高的场景。

2、HashTable
– 特点:Hashtable是Java早期提供的线程安全的哈希表实现,其所有public方法都添加了synchronized关键字。与HashMap相比,Hashtable是线程安全的,但同样因为频繁的线程同步而牺牲了性能。
– 适用场景:同样适用于并发量不高的场景,但由于性能原因,在现代Java应用中较少使用,推荐使用ConcurrentHashMap替代。

3、ConcurrentHashMap
– 特点:ConcurrentHashMap是专为并发环境设计的,它采用了分段锁(在Java 8及以后版本中改为CAS和synchronized的组合)来减少锁竞争,提高并发性能。ConcurrentHashMap不仅保证了线程安全,还具有较高的并发访问效率。
– 适用场景:适用于高并发的哈希表应用场景,如缓存、数据映射等。

4、CopyOnWriteArralyList
– 特点:CopyOnWriteArrayList是一种线程安全的List实现,其通过写时复制(COW)的策略来保证线程安全。在每次修改操作时,都会复制一份底层数组,并在新数组上进行修改,然后再将原数组引用指向新数组。这种方式避免了写操作时的线程同步,但读操作不需要加锁,因此读操作性能较高。然而,写操作的成本较高,因为每次修改都需要复制整个数组。
– 适用场景:适用于读多写少的场景,如缓存、白名单等。

5、ConcurrentLinkedQueue
– 特点:ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用非阻塞算法实现,支持高并发访问。ConcurrentLinkedQueue的节点之间通过非volatile的链接相连,但在入队和出队时通过CAS(Compare-And-Swap)操作来保证线程安全。
– 适用场景:适用于高并发的队列应用场景,如任务队列、消息队列等。

6、CopyOnWriteArraySet
– 特点:CopyOnWriteArraySet是Java中一种线程安全的Set实现,内部使用了CopyOnWriteArrayList来存储元素。它继承了CopyOnWriteArrayList的线程安全机制,因此也具有读多写少时的性能优势。但与CopyOnWriteArrayList不同的是,CopyOnWriteArraySet不允许包含重复元素。
– 适用场景:适用于需要唯一性且读操作远多于写操作的场景,如维护一组独特的订阅者或监听器。

7、BlokingQueue及其实现类
– 特点:BlockingQueue是一个支持阻塞的队列接口,它提供了多种阻塞操作,如put和take,以支持生产者-消费者模式。BlockingQueue的实现类(如ArrayBlockingQueue、LinkedBlockingQueue等)通常也是线程安全的。
– 适用场景:适用于生产者-消费者模式,以及需要阻塞操作的队列应用场景。

8、Collections工具类的线程安全集合
Java的Collections工具类还提供了一系列将非线程安全集合转换为线程安全集合的方法,如Collections.synchronizedList(List list)、Collections.synchronizedSet(Set s)、Collections.synchronizedMap(Map<K,V> m)等。这些方法通过在非线程安全集合的外部添加一层同步锁来实现线程安全,但需要注意的是,这样得到的线程安全集合在迭代时仍需要手动进行外部同步。

总结
线程安全的集合通过不同的机制来保证在多线程环境下的数据一致性和正确性。在选择线程安全集合时,需要根据实际的应用场景和性能要求来选择合适的集合类型。对于高并发的应用场景,推荐使用ConcurrentHashMap、ConcurrentLinkedQueue等专为并发设计的集合;对于读多写少的场景,可以考虑使用CopyOnWriteArrayList、CopyOnWriteArraySet等集合。同时,也可以利用Collections工具类将非线程安全集合转换为线程安全集合,但需要注意迭代时的同步问题。

事务

数据库事务
数据库事务的四大特性:
– 原子性:构成事务的所有操作,要么都执行完成,要么完全不起作用;
– 一致性:确保从一个正确的状态转换到另外一个正确的状态
– 隔离性:并发访问数据时,一个用户的事务不被其它事务所干扰,各并发事务之间数据库是独立的;
– 持久性:一个事务提交之后,对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

数据库事务隔离级别:
– 读未提交:最低的隔离级别,什么都需要做,一个事务可以读到另一个事务未提交的结果。所有的并发事务问题都会发生。
– 读已提交:只有事务提交之后,其更新结果才能被其它事务看到。可以解决脏读的问题。
– 可重复读:在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其它事务对这份数据进行操作,以及这个事务是否提交。可以解决脏读,不可重复读。
– 可序列化:事务串行化执行,隔离级别高,牺牲了系统的并发性。可以解决并发事务的所有问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米奇妙妙wu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值