1.BlockingQueue的使用场景,原理?
使用场景: BlockingQueue常用于生产者-消费者模型,也可以用于线程池中的任务队列。
实现: 阻塞入队(put):当队列已满时,调用 put() 方法的线程会被阻塞,直到队列有空闲位置可以插入元素为止。例如,ArrayBlockingQueue 会在队列已满时,阻塞生产者线程,直到有消费者从队列中取出元素,释放出空间。
阻塞出队(take):当队列为空时,调用 take() 方法的线程会被阻塞,直到队列中有元素可以被取出为止。例如,在生产者-消费者模型中,消费者线程会在队列为空时被阻塞,直到生产者线程添加数据。
可选的超时操作:BlockingQueue 还提供了带超时的阻塞方法,如 offer(E e, long timeout, TimeUnit unit) 和 poll(long timeout, TimeUnit unit),这些方法允许线程在等待一定时间后超时,避免无限期阻塞。
线程安全:BlockingQueue 提供了线程安全的入队和出队操作,所有的操作(如 put()、take()、offer()、poll())在多线程环境中是安全的,可以在不同线程间并发操作队列而不需要额外的同步控制。
2.HashSet为什么可以做到元素不重复?
HashSet通过底层使用HashMap来保证元素不重复。具体来说,HashSet内部维护一个HashMap,其中元素存储在HashMap的key上,而所有的value都指向同一个共享的内部对象。在存储元素时,HashSet会根据元素的hashCode值来确定其在HashMap中的存储位置,同时也会比较equals方法来确保元素不重复。
3.HashMap在转换时不安全问题?
4.SpringBoot自动装配原理?
Spring Boot 实现自动装配,在引入第三方 Starter 时,Spring 是如何加载并配置这些组件的。
第一步:引入 Starter 和配置类
在 Spring Boot 中,Starter 是一种约定的命名方式,用来简化引入一些常见的库或功能。例如,spring-boot-starter-web 是一个包含 Web 开发功能的 Starter,它包含了很多我们通常需要的配置和组件。如果我们想要引入某个第三方组件,通常也会用到类似的 Starter。
Starter : Starter 是一个包含相关依赖和配置的 jar 包。Spring Boot 会自动为你配置这些依赖,只需要引入这个 Starter 依赖即可。
@Configuration 配置类和 @Bean 注解的作用 每个 Starter 通常都会提供一个配置类,这个配置类通过 @Configuration 注解标识。这个类里面会有一些 @Bean 注解,表明需要创建哪些 bean(即对象)并将它们注入到 Spring 的 IoC 容器中。
第二步:配置类放在第三方 JAR 包中
Spring Boot 的核心理念之一是 约定优于配置,也就是说,Spring Boot 默认提供了很多自动配置的规则,你不需要写很多配置文件来声明如何加载组件。Spring Boot 会根据一些约定来自动加载这些配置。
spring.factories 文件: Spring Boot 使用一个叫做 spring.factories 的文件来管理不同 jar 包中的自动配置类。当我们在引入一个第三方 jar 包(比如某个 Starter)时,Spring Boot 会在该包中查找 META-INF/spring.factories 文件,这个文件里面记录了第三方配置类的全路径。
例如,假设你引入了一个 MyStarter,它的 spring.factories 文件可能会包含如下内容:org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.example.MyStarterConfiguration
这样,Spring Boot 就知道在启动时要加载 com.example.MyStarterConfiguration 这个配置类。
第三步:使用 SpringFactoriesLoader 和 ImportSelector 加载配置类
Spring Boot 会通过 SpringFactoriesLoader 来加载所有的 spring.factories 文件,并获取其中列出的配置类。然后,Spring Boot 使用 ImportSelector 这个接口来动态地加载这些配置类,完成自动装配的过程。
SpringFactoriesLoader: 这是一个 Spring 提供的工具,用来加载 spring.factories 文件中的配置内容。它会扫描 classpath 中的所有 jar 包,找出并加载所有声明的自动配置类。
ImportSelector: ImportSelector 是一个接口,它的作用是动态地将指定的配置类加载到 Spring 容器中。通过 ImportSelector,Spring Boot 可以灵活地加载不同的配置类。
@EnableAutoConfiguration 注解背后就是通过 ImportSelector 来动态加载所有的自动配置类,完成自动装配。
5.Mybatis的一级缓存和二级缓存?
先知道SqlSession和mapper的概念: SqlSession:它是客户端和数据库之间的会话,类似于浏览器和用户之间维持的session会话,它负责与数据库进行交互,执行 SQL 语句、获取映射器(Mapper)对象、管理缓存、事务等。 mapper:Mapper 接口通常定义了数据库操作的方法,例如查询、插入、更新等。方法的名称和参数与对应的 SQL 语句映射。通俗的讲mapper将java方法与sql语句做映射。
6.RabbitMQ如果消息投递失败了怎么办?
消息队列(如 RabbitMQ)扮演着重要的角色,能够解耦生产者与消费者,提升系统的可扩展性和可靠性。然而,消息的投递失败、消费者无法处理的消息、消息过期等问题是常见的挑战。
1. 使用 Confirm 模式确保消息投递成功
RabbitMQ 提供了 Confirm 模式(消息确认模式),这是确保生产者消息被正确投递到交换机(Exchange)并最终进入队列的一种机制。 Confirm 模式的工作原理: 在 Confirm 模式 中,生产者在发布消息时,RabbitMQ 会在消息成功到达交换机后,向生产者发送一个确认信号(ACK)。 生产者可以通过设置回调机制来接收这个确认信号。 如果消息投递失败(例如,消息无法到达交换机),生产者将收到失败的通知,进而可以执行重试或其他补救措施。
2. 使用 ReturnListener 确保消息成功路由到队列
即使消息成功发送到交换机,依然可能因为路由规则不匹配或队列不可达而无法到达目标队列。为了解决这一问题,RabbitMQ 提供了 ReturnListener。 ReturnListener 的工作原理:如果消息在投递到交换机时,无法找到匹配的队列,RabbitMQ 会将该消息返回给生产者。 生产者可以注册一个 ReturnListener 来监听这些未能成功路由的消息,从而对这些消息进行处理(例如,记录日志、重试发送、报警等)
3. 使用手动消息确认机制处理消费者无法处理的消息
在 RabbitMQ 中,消费者通常会自动确认消息(即消费后自动向 RabbitMQ 发送 ACK)。但在某些情况下,消费者可能无法成功处理消息(例如,业务逻辑异常、系统资源问题等),这时可以使用 手动确认机制 来保证消息的可靠消费。
手动消息确认的工作原理: 在手动确认模式下,消费者必须显式地调用 channel.basicAck 方法来确认消息已经成功处理。否则,RabbitMQ 会认为消息未被成功消费,消息将重新进入队列并由其他消费者重新消费。 如果消费者无法处理消息,可以调用 channel.basicNack 或 channel.basicReject 来拒绝消息,并根据需要重新将消息放回队列或丢弃
4. 配置死信队列处理无法处理或过期的消息
死信队列(Dead Letter Queue, DLQ)是 RabbitMQ 中的一种特殊队列,用于存储那些无法被消费的消息,例如:消息被拒绝、消息过期、队列已满等情况。 死信队列的工作原理: 当消息因为某些原因无法被消费者处理时,可以将该消息转发到死信队列。 通过配置死信交换机(Dead Letter Exchange, DLX)和死信路由键(Dead Letter Routing Key, DLRK),消息会被自动路由到指定的死信队列。 死信队列可以用来记录异常消息、进行后续的人工干预、重新投递等
7.JVM逃逸分析?
1 逃逸分析 JVM中较前沿的优化技术,它与类型继承关系分析一样,并非直接优化代码,而是为其他优化措施提供依据的分析技术。
1.1 基本原理 分析对象动态作用域,当一个对象在方法里面被定义后,它可能
被外部方法所引用 例如作为调用参数传递给其他方法,称为方法逃逸 被外部线程访问 譬如赋值给可以在其他线程中访问的实例变量,称为线程逃逸 从不逃逸 =》方法逃逸 =》线程逃逸,称为对象由低到高的不同逃逸程度。
如果能证明一个对象不会逃逸到方法或线程外(即别的方法或线程无法通过任何途径访问到该对象),或逃逸程度较低(只逃逸出方法而不逃逸出线程),则可能为这个对象实例采取不同程度的优化,如:
2 栈上分配(Stack Allocations) 由于复杂度等原因,HotSpot中目前暂时还没有做这项优化,但一些其他的虚拟机(如Excelsior JET)使用了该优化。
JVM中,Java堆上分配创建对象的内存空间是常识,Java堆中的对象对各线程共享可见,只要持有该对象的引用,就可访问到堆中存储的对象数据。 虚拟机的GC子系统会回收堆中不再使用的对象,但回收动作无论是标记筛选出可回收对象,还是回收和整理内存,都需耗费大量资源。 如果确定一个对象不会逃逸出线程,那让该对象在栈上分配内存是个不错主意,对象所占用内存空间就可随栈帧出栈而销毁。 在一般应用中,完全不会逃逸的局部对象和不会逃逸出线程的对象所占的比例很大,如果能使用栈上分配,那大量对象就会随方法结束而自动销毁,GC子系统压力会下降很多。栈上分配可支持方法逃逸,但不能支持线程逃逸。
3 标量替换(Scalar Replacement) 若一个数据已经无法再分解成更小数据来表示,JVM中基础数据类型都不能再进一步分解,这些数据可被称为标量。 相对的,如果一个数据可以继续分解,那它就被称为聚合量(Aggregate),Java 中的对象就是典型的聚合量。如果把一个Java对象拆散,根据程序访问的情况,将其用到的成员变量恢复为原始类型来访问,该过程就称为标量替换。 假如逃逸分析能够证明一个对象不会被方法外部访问,并且该对象可被分解,那么程序真正执行时将可能不去创建该对象,而改为直接创建它的若干个被这方法使用的成员变量代替。 将对象拆分后,除可让对象的成员变量在栈上 (栈上存储的数据,很大机会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写外,还可为后续进步优化创建条件。 标量替换可视作栈上分配一种特例,实现更简单(不用考虑对象完整结构的分配),但对逃逸程度的要求更高,它不允许对象逃逸出方法范围内。
4 同步消除(Synchronization Elimination) 线程同步本身是一个相对耗时过程,如果逃逸分析能确定一个变量不会逃逸出线程,无法被其他线程访问,那么该变量读写肯定不会有竞争, 对该变量实施的同步措施也可安全消除。 关于逃逸分析的研究论文早在1999年就已经发表,但到JDK 6,HotSpot才开始支持初步逃逸分析,到现在这优化技术尚未足够成熟。 不成熟的原因主要是逃逸分析的计算成本非常高,甚至不能保证逃逸分析带来的性能收益会高于它的消耗。要百分之百准确地判断一个对象是否会逃逸,需要进行一系列复杂的数据流敏感的过程间分析,才能确定程序各个分支执行时对此对象的影响。 前面介绍即时编译、提前编译优劣势时提到了过程间分析这种大压力的分析算法正是即时编译的弱项。可以试想一下,如果逃逸分析完毕后发现几乎找不到几个不逃逸的对象, 那这些运行期耗用的时间就白白浪费了,所以目前虚拟机只能采用不那么准确,但时间压力相对较小的算法来完成分析。 C和C++原生支持栈上分配(不使用new即可),而C#也支持值类型,可以自然做到标量替换(但并不会对引用类型做这种优化)。 在灵活运用栈内存方面,确实是Java的弱项。 在现在仍处于实验阶段的Valhalla项目里,设计了新的inline关键字用于定义Java的内联类型, 目的是实现与C#中值类型相对标的功能。有了这个标识与约束,以后逃逸分析做起来就会简单很多。
8.怎样让一个线程停止工作?
在java中有以下3种方法可以终止正在运行的线程:
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
使用interrupt方法中断线程。 blog.csdn.net/2301_776902…
9.Spring的注入方式?
在Spring框架中,常用的三种依赖注入方式包括构造器注入、Setter方法注入和字段注入。
构造器注入:通过在构造方法中声明依赖对象的参数列表来实现注入。这种方式要求类有一个无参构造函数,并且在构造方法中声明依赖对象的参数。如果类只有一个构造方法,@Autowired注解可以省略;如果有多个构造方法,则需要明确指定使用哪个构造方法。
Setter方法注入:通过添加setter方法将依赖对象设置到被注入对象。这种方法需要在类中定义一个无参构造函数,并通过@Autowired注解的方法来设置依赖对象。Setter注入在Spring 3.0中被推荐使用,因为它可以避免构造方法过于臃肿,特别是当属性是可选的时候。
字段注入:通过在类中直接声明依赖对象并使用注解(如@Autowired、@Resource、@Inject)来实现注入。字段注入是最简单的注入方式,但它在IOC容器以外的环境中无法复用该实现类,因为该类没有提供相应的构造方法或setter方法来初始化依赖