Bootstrap

常见题目总结,题目加粗

1. synchronized 和 ReentrantLock 的区别?

  1. 实现方式:

o synchronized是Java的关键字,它在JVM层面由JVM解释器实现。

o ReentrantLock是Java的并发包java.util.concurrent.locks中的一个类,它提供了与synchronized类似的功能,但实现方式更为灵活。

  1. 公平性:

o synchronized没有提供公平性的选项,线程的执行顺序是不确定的。

o ReentrantLock提供了公平锁和非公平锁两种选择。公平锁会按照线程请求锁的顺序来获取锁,而非公平锁则没有这个保证。

  1. 阻塞与非阻塞:

o synchronized会阻塞线程,当一个线程进入一个synchronized代码块或方法时,如果锁被其他线程持有,该线程会被阻塞,直到获得锁。

o ReentrantLock同样会阻塞线程,但它提供了更为细致的控制。例如,你可以选择尝试获取锁,而不阻塞线程。

  1. 中断:

o synchronized不支持中断。当线程等待锁时,不能被中断。

o ReentrantLock支持中断。当线程等待锁时,可以通过中断线程来解除阻塞。

  1. 可响应性:

o synchronized是内置的语言,使用起来更为简单和直接。

o ReentrantLock提供了更多的控制和灵活性,但使用起来可能更为复杂。

  1. 锁定状态查询:

o synchronized不提供查询锁状态的方法。

o ReentrantLock提供了查询锁状态的方法,例如可以查询是否被锁定、是否被占用等。

  1. 适用场景:

o 当你需要一个简单的同步机制时,可以使用synchronized。例如,如果你只是需要保护一个代码块不被多个线程同时访问,那么使用synchronized就足够了。

o 当你需要更复杂的控制,例如需要中断等待的线程、需要查询锁的状态、需要公平锁等时,可以使用ReentrantLock。例如,当你实现一个无锁数据结构时,可能会使用到ReentrantLock

2. 什么是 CAS(比较并交换-乐观锁机制-锁自旋)?

CAS是Compare and Swap的缩写,中文意思是比较并交换。这是一种在多线程编程中常用的同步原语,用于实现无锁数据结构,以及解决原子性问题。

CAS操作包含三个操作数:内存位置V,预期的原值A和新值B。线程执行CAS操作时,会检查内存位置V的值是否与预期原值A相等,如果相等,则将该位置的值更新为新值B;如果不相等,则不做任何操作。CAS操作是一个原子操作,这意味着在多线程环境中,它要么完全执行成功,要么完全不执行。

CAS的优点在于它是一种无锁的解决方案,避免了锁的开销和死锁问题。它基于乐观锁的原理,即假设数据在读取后短时间内不会被其他线程修改,因此可以在不加锁的情况下进行读写操作。CAS的缺点在于它只能保证单个共享变量的原子操作,对于多个共享变量的操作,需要使用其他同步机制。

在Java中,CAS操作由JVM内部的指令实现,例如在AtomicInteger类中就使用了CAS操作来保证原子性。在Java并发编程中,CAS操作可以用于实现无锁数据结构、原子性操作等场景。

3. ABA 问题 ?

ABA问题主要出现在使用CAS(Compare and Swap)操作时。CAS操作是一种在多线程环境中安全地更新共享变量的原子操作。CAS操作会对比内存位置V的值与预期原值A,如果相等,则将该位置的值更新为新值B。

ABA问题是指,当一个线程在执行CAS操作时,另一个线程可能已经将内存位置V的值从A改为其他值,然后再改回A。由于CAS操作只对比预期原值和内存位置的值是否相等,而不关心值的变化过程,因此,当原来的线程再次读取内存位置V的值时,仍然为A,并继续执行CAS操作,而实际上共享变量的值已经发生了变化。

要解决ABA问题,需要使用更为复杂的同步机制,例如使用版本号或时间戳来跟踪共享变量的变化,或者使用其他类型的锁来保证共享变量的原子性。同时,需要注意在使用CAS操作时,应避免依赖变量的中间状态,尽量保证CAS操作在变量变化的全过程中只执行一次。

4. 什么是 AQS(抽象的队列同步器) ?

AbstractQueuedSynchronizer 类如其名,抽象的队列式的同步器,AQS 定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的 ReentrantLock/Semaphore/CountDownLatch。

5. Spring IOC 原理?

Spring IOC(Inversion of Control,控制反转)是Spring框架的核心原理之一。它通过将对象的创建和依赖关系的管理交给容器来实现,从而实现了解耦和可重用性。

Spring IOC的原理主要包括以下几个方面:

Bean的定义:在Spring IOC中,我们通过Bean的定义来描述需要被容器管理的对象。Bean的定义可以通过XML配置文件、注解或Java代码方式进行定义。

容器的实例化:当应用程序启动时,Spring IOC容器会根据配置信息实例化并初始化容器。容器负责创建、管理和协调Bean的生命周期。

对象的创建和依赖注入:容器根据Bean的定义,使用反射机制实例化对象,并将对象的依赖关系注入到相应的属性中。这样就不再需要显式地在代码中编写对象的创建和依赖关系的处理逻辑。

生命周期管理:Spring IOC容器负责管理Bean的整个生命周期,包括初始化、使用和销毁。可以通过配置初始化方法和销毁方法,在对象被创建和销毁时执行相应的操作。

依赖解析和自动装配:Spring IOC容器可以通过依赖解析和自动装配的方式,自动满足对象间的依赖关系。它支持按名称、按类型和按注解等多种方式进行依赖注入。

AOP集成:Spring IOC容器与AOP(面向切面编程)紧密集成,可以通过配置和代理技术实现横切关注点的提取和复用。这样可以将应用程序的核心逻辑与横切逻辑分离,提高代码的可维护性和可测试性。

总之,Spring IOC的原理是通过控制反转的方式,将对象的创建和依赖管理交给IOC容器来完成,从而实现了松耦合和可重用的设计。

6. 5 种不同方式的自动装配?

Spring 装配包括手动装配和自动装配,手动装配是有基于 xml 装配、构造方法、setter 方法等。自动装配有五种自动装配的方式,可以用来指导 Spring 容器用自动装配方式来进行依赖注入。 1. no:默认的方式是不进行自动装配,通过显式设置 ref 属性来进行装配。 2. byName:通过参数名自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byname,之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean。 3. byType:通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire 属性被设置成 byType,之后容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多 个 bean 符合条件,则抛出错误。 4. constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。 5. autodetect:首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式。

7. Spring AOP 原理?

“横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块, 并将其命名为"Aspect”,即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。 使用"横切"技术,AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。 AOP 主要应用场景有: 1. Authentication 权限 2. Caching 缓存 3. Context passing 内容传递 4. Error handling 错误处理 5. Lazy loading 懒加载 6. Debugging 调试 7. logging, tracing, profiling and monitoring 记录跟踪、优化、校准 8. Performance optimization 性能优化 9. Persistence 持久化 10. Resource pooling 资源池 11. Synchronization 同步 12. Transactions 事务

8. AOP 两种代理方式 ?

Spring 提供了两种方式来生成代理对象: JDKProxy 和 Cglib,具体使用哪种方式生成由 AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口, 则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。 JDK 动态接口代理 1. JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。 InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。Proxy 利用 InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象。CGLib 动态代理 2. :CGLib 全称为 Code Generation Library,是一个强大的高性能,高质量的代码生成类库, 可以在运行期扩展 Java 类与实现 Java 接口,CGLib 封装了 asm,可以再运行期动态生成新的 class。和 JDK 动态代理相比较:JDK 创建代理有一个限制,就是只能为接口创建代理实例, 而对于没有通过接口定义业务方法的类,则可以通过 CGLib 创建动态代理。

9. Spring MVC 原理?

Spring MVC的原理主要包括以下几个部分:

控制器(Controller):控制器是Spring MVC中的核心组件,负责接收并处理用户请求。控制器将处理结果返回给视图,并最终呈现给用户。在Spring MVC中,控制器通过@Controller注解进行标记,并通过@RequestMapping注解映射请求路径。

视图解析器(View Resolver):视图解析器用于解析视图名称,将其转换为具体的视图实现。Spring MVC内置了多种视图解析器,如InternalResourceViewResolver和FreeMarkerViewResolver等,可以根据需要选择不同的视图解析器。

模型(Model):模型是控制器处理业务逻辑后返回的数据结构,用于存储要呈现给用户的数据。在Spring MVC中,模型是一个Map对象,其中键是变量名,值是变量值。控制器将模型数据添加到模型对象中,然后将其传递给视图进行渲染。

请求处理流程:当用户发起请求时,请求被发送到DispatcherServlet。DispatcherServlet是Spring MVC中的核心组件,负责请求的转发和视图解析。DispatcherServlet将请求分发给相应的控制器进行处理。控制器处理完业务逻辑后,将模型数据添加到模型对象中,并返回给DispatcherServlet。DispatcherServlet根据视图解析器找到对应的视图进行渲染,最终将结果呈现给用户。

总之,Spring MVC通过控制器、视图解析器和模型等组件的协作,实现了对用户请求的处理和响应。其核心思想是控制反转(IoC)和面向切面编程(AOP),通过依赖注入和切面编程技术提高了代码的可维护性和可扩展性。

10. Spring Boot 原理?

Spring Boot其设计目的是用来简化新Spring应用的初始搭建以及开发过程。Spring Boot通过自动配置和启动器以及大量注解实现快速整合第三方依赖,简化了XML配置,全部采用注解形式,内置Http服务器(Jetty和Tomcat),最终以java应用程序进行执行。Spring Boot基于SpringMVC无配置文件(纯Java)完全注解化+内置tomcat-embed-core实现框架,Main函数启动。核心快速整合第三方框架原理是Maven继承依赖关系。

Spring Boot的工作原理主要基于自动配置和启动器。启动器就是我们在pom.xml文件中引入的带starter的依赖,Spring Boot框架会根据依赖加载与该启动器有关的所有jar包。自动配置则是根据用户的starter判断用户要使用的技术,根据JavaConfig进行框架之间的默认整合。例如,我们需要使用mybatis,会在pom文件中引入mybatis的启动器,该启动器会加载一个自动配置依赖,该jar中有配置类,该类中写的是一些默认的配置,将该配置类加载到classpath中。如果用户不想使用默认的配置,用户可以在application.properties配置文件中更改。

11. 分布式事务?

分布式事务是指涉及多个独立的事务资源(如数据库、消息队列等)的跨多个节点的事务处理。这些事务资源可能分布在不同的物理节点上,因此需要协同工作以完成整个事务。分布式事务需要确保数据的一致性、可靠性和完整性,同时还需要解决事务的并发控制和死锁等问题。

分布式事务的实现通常需要使用到分布式事务管理器和事务协调器等技术。这些技术可以确保跨多个节点的事务能够正确地执行,并保证数据的一致性和可靠性。同时,还需要注意解决事务的并发控制和死锁问题,以确保系统的可扩展性和高性能。

分布式事务在微服务架构、数据库集群等场景中非常常见。在微服务架构中,不同的服务可能由不同的团队开发和维护,因此需要使用分布式事务来确保服务之间的数据一致性和可靠性。在数据库集群中,不同的数据库实例可能分布在不同的物理节点上,因此需要使用分布式事务来保证数据的一致性和可靠性。

总之,分布式事务是确保跨多个节点的事务能够正确执行并保证数据一致性、可靠性和完整性的关键技术之一。

12. 实现分布式事务**?**

通常需要使用到以下几种技术:

分布式事务管理器:分布式事务管理器是负责协调和管理多个事务资源的管理器。它负责确保所有事务资源都能够正确地完成事务,并保证数据的一致性和可靠性。分布式事务管理器可以采用两阶段提交协议或三阶段提交协议等协议来协调和管理事务资源。

事务协调器:事务协调器是负责协调和管理分布式事务的组件。它负责确保跨多个节点的操作能够正确地执行,并解决并发控制和死锁等问题。事务协调器可以采用基于锁或基于消息的机制来实现协调和管理。

消息队列:消息队列是一种异步通信机制,可以用于实现跨多个节点的事务处理。通过将事务数据发送到消息队列中,可以异步地处理事务,并提高系统的可扩展性和可靠性。

全局事务ID:全局事务ID是一种唯一标识事务的ID,可以用于跟踪和管理分布式事务。通过全局事务ID,可以确保跨多个节点的事务能够被正确地识别和管理。

补偿事务:补偿事务是一种回滚机制,用于处理分布式事务失败的情况。当某个事务失败时,可以使用补偿事务来撤销已经执行的操作,以保证数据的一致性和可靠性。

总之,实现分布式事务需要综合考虑多种技术,并需要根据具体的应用场景和需求来选择适合的技术方案。同时,还需要注意解决并发控制和死锁等问题,以确保系统的可扩展性和高性能。

13. Zookeeper 概念**?**

Zookeeper 是一个分布式协调服务,可用于服务发现,分布式锁,分布式领导选举,配置管理等。 Zookeeper 提供了一个类似于 Linux 文件系统的树形结构(可认为是轻量级的内存文件系统,但只适合存少量信息,完全不适合存储大量文件或者大文件),同时提供了对于每个节点的监控与通知机制。

14. Zookeeper 工作原理(原子广播)?

\1. Zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式和广播模式。 2. 当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 的完成了和 leader 的状态同步以后,恢复模式就结束了。 3. 状态同步保证了 leader 和 server 具有相同的系统状态 4. 一旦 leader 已经和多数的 follower 进行了状态同步后,他就可以开始广播消息了,即进入广播状态。这时候当一个 server 加入 zookeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。待到同步结束,它也参与消息广播。Zookeeper 服务一直维持在 Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的 followers 支持。 5. 广播模式需要保证 proposal 被按顺序处理,因此 zk 采用了递增的事务 id 号(zxid)来保证。所有的提议(proposal)都在被提出的时候加上了 zxid。 6. 实现中 zxid 是一个 64 为的数字,它高 32 位是 epoch 用来标识 leader 关系是否改变, 每次一个 leader 被选出来,它都会有一个新的 epoch。低 32 位是个递增计数。 7. 当 leader 崩溃或者 leader 失去大多数的 follower,这时候 zk 进入恢复模式,恢复模式需要重新选举出一个新的 leader,让所有的 server 都恢复到一个正确的状态。

15. Kafka 概念?

Kafka 是一种高吞吐量、分布式、基于发布/订阅的消息系统,最初由 LinkedIn 公司开发,使用 Scala 语言编写,目前是 Apache 的开源项目。 1. broker:Kafka 服务器,负责消息存储和转发 2. topic:消息类别,Kafka 按照 topic 来分类消息 3. partition:topic 的分区,一个 topic 可以包含多个 partition,topic 消息保存在各个 partition 上 4. offset:消息在日志中的位置,可以理解是消息在 partition 上的偏移量,也是代表该消息的唯一序号 5. Producer:消息生产者 6. Consumer:消息消费者 7. Consumer Group:消费者分组,每个 Consumer 必须属于一个 group 8. Zookeeper:保存着集群 broker、topic、partition 等 meta 数据;另外,还负责 broker 故障发现,partition leader 选举,负载均衡等功能。

16. 索引原则?

1.选择唯一性索引 1.唯一性:索引的值是唯一的,可以更快速的通过该索引来确定某条记录。 2.为经常需要排序、分组和联合操作的字段建立索引: 3.为常作为查询条件的字段建立索引。 4.限制索引的数目: 越多的索引,会使更新表变得很浪费时间。 尽量使用数据量少的索引 6.如果索引的值很长,那么查询的速度会受到影响。 尽量使用前缀来索引 7.如果索引字段的值很长,最好使用值的前缀来索引。 7.删除不再使用或者很少使用的索引 8 . 最左前缀匹配原则,非常重要的原则。 10 . 尽量选择区分度高的列作为索引区分度的公式是表示字段不重复的比例 11 .索引列不能参与计算,保持列“干净”:带函数的查询不参与索引。 12 .尽量的扩展索引,不要新建索引。

17. 基于 Redis 分布式锁?

\1. 获取锁的时候,使用 setnx(SETNX key val:当且仅当 key 不存在时,set 一个 key 为 val 的字符串,返回 1;若 key 存在,则什么都不做,返回 0)加锁,锁的 value 值为一个随机生成的 UUID,在释放锁的时候进行判断。并使用 expire 命令为锁添加一个超时时间,超过该时间则自动释放锁。2. 获取锁的时候调用 setnx,如果返回 0,则该锁正在被别人使用,返回 1 则成功获取锁。 还设置一个获取的超时时间,若超过这个时间则放弃获取锁。 3. 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放。

18. 缓存雪崩?

由于原有缓存失效,新缓存未到期间所有原本应该访问缓存的请求都去查询数据库了,而对数据库 CPU 和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。一般有三种处理办法: 1. 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。 2. 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。 3. 为 key 设置不同的缓存失效时间。

19. 缓存穿透?// 第一天

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。 通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。

20. 面向对象和面向过程的区别?

面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一 一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发。

面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤, 而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。 但是性能上来说,比面向过程要低。

21. 重载和重写的区别?

重写 总结: 1.发生在父类与子类之间 2.方法名,参数列表,返回类型(除过子类中方法的返回类型 是父类中返回类型的子类)必须相同 3.访问修饰符的限制一定要大于被重写方法的访问修饰符 (public>protected>default>private) 4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常

重载 总结: 1.重载Overload是一个类中多态性的一种表现 2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序) 3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型作为重载函数的区分标准。

22. equals与==的区别?

==操作符:

==是Java中的基本比较操作符,用于比较两个变量的值是否相等。

当用于比较基本数据类型(如int,float等)时,它会比较它们的值。

当用于比较对象引用时,它会检查两个引用是否指向内存中的同一个对象。换句话说,它会检查两个引用是否完全相同(包括内存地址)。

equals()方法:

equals()方法是Java中Object类的一个方法,所有对象都继承自Object类,因此都可以调用这个方法。

默认情况下,equals()的行为与==相同,也是比较引用是否完全相同。

但是,许多类(如String,ArrayList等)已经重写了equals()方法,以便按其特定的逻辑进行比较。例如,String类的equals()方法比较的是字符串的内容,而不是内存地址。

重要区别:

当使用==操作符比较两个对象时,你实际上是在检查它们是否指向内存中的同一个位置。这对于比较对象的内容是否相等通常不是很有用。

当使用equals()方法比较两个对象时,你通常是在调用对象的特定实现版本的equals()方法(如果该类已经重写了这个方法的话),这样可以基于对象的实际内容或属性进行比较。

如何选择:

对于基本数据类型,通常使用==操作符进行比较。

对于对象引用,如果你想检查两个引用是否指向同一个对象(即检查它们的内存地址),则可以使用==操作符。

如果你想基于对象的某些属性或内容来比较对象,应该使用相应类的equals()方法。在重写equals()方法时,通常也应该重写hashCode()方法,以确保两个相等的对象具有相同的哈希码。

23. Hashcode的作用?

在Java中,hashCode()方法的主要作用是用于快速查找对象。它是基于哈希表的原理,将对象存储在散列结构中,通过hashCode()方法确定对象的存储地址。

在哈希表中查找对象:哈希表是一种数据结构,用于存储键值对。在哈希表中,每个对象都有一个唯一的哈希码,可以用来快速查找对象。

在集合中去重:在Java中,集合类(如List、Set、Map等)中存储的对象必须是唯一的。如果两个对象的哈希码相同,那么它们可能是相等的,需要使用equals()方法进一步比较。

在多线程环境下保证对象的唯一性:在多线程环境下,如果两个线程同时创建了一个对象,那么这两个对象的哈希码可能相同。为了保证对象的唯一性,可以使用synchronized关键字或者ConcurrentHashMap等线程安全的集合类。

总的来说,hashCode()方法可以提高查找效率,同时还能在集合中去重和保证对象的唯一性。

24. String、StringBuffer 和 StringBuilder 的区别是什么?

可变性:String类是不可变的,即每次对String对象进行修改时,都会生成一个新的String对象,原对象内容不会被修改。而StringBuffer和StringBuilder是可变的,它们在修改时不会生成新的对象。

线程安全:String类的所有操作都是同步的,这意味着它是线程安全的。而StringBuffer类也是线程安全的,但StringBuilder类不是线程安全的。

性能:对于大量字符串的修改操作,如拼接等,由于StringBuffer的线程安全特性,它可能会在多线程环境下产生额外的同步开销。相比之下,StringBuilder由于是单线程环境下的操作,其性能通常优于StringBuffer。

容量:当字符串大小没有超过容量时,StringBuffer不会分配新的容量,当字符串大小超过容量时,会自动增加容量。

综上所述,对于大量字符串的修改操作,建议使用StringBuilder类;对于单线程环境下的字符串操作,可以考虑使用StringBuilder类;对于需要线程安全的环境,可以使用StringBuffer类。

25. ArrayList和linkedList的区别?

ArrayList和LinkedList都是Java中的List接口的实现,但它们在底层数据结构、性能和自由度上有所不同。

底层数据结构:ArrayList是基于动态数组的数据结构,而LinkedList则是基于链表的数据结构。这意味着ArrayList在内存中是连续存储的,而LinkedList则通过节点来存储数据,每个节点包含数据和指向下一个节点的引用。

性能:当进行随机访问元素(get和set操作)时,ArrayList通常比LinkedList更快,因为ArrayList的内存是连续的,可以直接计算出元素的位置。然而,当进行添加或删除操作时,LinkedList通常更高效,因为它不需要移动大量元素。

自由度:ArrayList的自由度较低,因为它的容量是固定的,需要手动设置和调整。而LinkedList的自由度更高,可以动态地添加或删除元素。

综上所述,选择ArrayList或LinkedList主要取决于你的具体需求。如果你需要频繁地访问元素,并且对性能要求较高,可以选择ArrayList。如果你需要频繁地添加和删除元素,或者对元素的插入位置有严格的要求,那么LinkedList可能更适合你。

26. HashMap和HashTable的区别?

HashMap和Hashtable都是Java中提供的数据结构,用于存储键值对,但它们在实现和特性上有一些重要的区别。

线程安全性:Hashtable是线程安全的,因为它的所有操作都是同步的。这意味着在多线程环境下,Hashtable的性能可能会受到一些影响,因为每次只有一个线程能够执行操作。另一方面,HashMap是非线程安全的,如果多个线程同时修改HashMap,可能会导致不可预料的结果。因此,如果需要高并发环境下的性能,可以选择非线程安全的HashMap。

Null键和值:HashMap允许使用null作为键和值,而Hashtable不允许。

性能:在单线程环境下,由于没有同步的开销,HashMap通常比Hashtable有更好的性能。

继承:HashMap是Map接口的一个实现,而Hashtable是继承自Dictionary类。

综上所述,如果你需要一个线程安全的Map实现,可以选择Hashtable。如果你需要更好的性能或者允许使用null键和值,那么HashMap可能更适合你。。

27. Collection包结构,与Collections的区别?

Collection是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、 Set; Collections是集合类的一个帮助类, 它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的 Collection框架。

28. 泛型常用特点?使用泛型的好处?

以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型数据,而这并不是最重要的,因为我们只要把底层存储设置了Object即可,添加的数据全部都可向上转型为Object。 更重要的是我们可以通过规则按照自己的想法控制存储的数据类型。

29. Java创建对象有几种方式?

java中提供了以下四种创建对象的方式: new创建新对象 通过反射机制 采用clone机制 通过序列化机制

30. 有没有可能两个不相等的对象有相同的hashcode?

有可能.在产生hash冲突时,两个不相等的对象就会有相同的 hashcode 值.当hash冲突产生时,一般有以下几种方式来处理: 拉链法:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表进行存储. 开放定址法:一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入 List iniData = new ArrayList<>() 再哈希:又叫双哈希法,有多个不同的Hash函数.当发生冲突时,使用第二个,第三个….等哈希函数计算地址,直到无冲突.

31. 深拷贝和浅拷贝的区别是什么?

浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.

深拷贝:被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量将指向被复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的对象都复制了一遍.

32. final有哪些用法?

修饰类:当一个类被声明为final时,意味着这个类不能被继承。使用final来修饰一个类可以防止其他类继承它,这有助于保护类的实现,防止其他类修改它的意义和实现。

修饰方法:当一个方法被声明为final时,意味着这个方法不能被子类覆盖(override)。使用final来修饰一个方法可以防止子类修改该方法的实现,保证该方法的行为不会被子类改变。

修饰变量:当一个变量被声明为final时,意味着这个变量的值一旦被赋值后就不能改变。这种变量也称为常量。对于类的常量通常使用全大写字母命名,如果有多个单词可以用下划线分隔。

作为参数修饰符:在方法参数中,final可以用来修饰参数,表示该参数是只读的,不可以被修改。这可以防止方法内部的代码修改参数的值。

在匿名内部类中:在匿名内部类中,final可以用来修饰变量,表示该变量的值在匿名内部类中不能再被修改。

需要注意的是,final关键字不能用来修饰构造方法。另外,final只表示引用不能改变,而不是引用指向的对象不能改变。也就是说,final修饰的引用可以指向新的对象,但不能指向空。

33. static都有哪些用法**?**

这两个基本的用法:静态变量和静态方法.也就是被static所修饰的变量/ 方法都属于类的静态资源,类实例所共享. 除了静态变量和静态方法之外,static也用于静态块,多用于初始化操作: 此外static也多用于修饰内部类,此时称之为静态内部类. 最后一种用法就是静态导包,即 import static .import static是在JDK 1.5之后引入的新特性,可以用 来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名

34. 3*0.1 == 0.3返回值是什么**?**

false,因为有些浮点数不能完全精确的表示出来.

35. a=a+b与a+=b有什么区别吗?

+= 操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换.

36. try catch finally,try里有return,finally还执行么**?**

是的,即使在 try 块中使用了 return 语句,finally 块仍然会执行。这是因为在 Java 中,无论 try 块中的代码是否正常结束或由于异常而结束,finally 块都会被执行。

37. OOM你遇到过哪些情况?

OOM(Out of Memory)异常是Java中常见的异常之一,通常是由于程序在内存分配上出现了问题,导致无法继续分配更多的内存。以下是一些可能遇到OOM情况:

请求创建一个超大对象:当程序中创建了一个超出JVM堆内存限制的对象时,就会导致OOM异常。常见于大数组、大图片、大文件等。

超出预期的访问量/数据量:当程序需要处理的数据量超过了预期,比如大量的用户请求、数据流等,可能会导致内存溢出。

过度使用终结器(Finalizer):如果一个对象没有立即被垃圾回收,终结器会占用内存,如果过度使用终结器,可能会导致内存溢出。

内存泄漏(Memory Leak):当程序中存在内存泄漏问题时,比如大量对象引用没有被释放,JVM无法自动回收,就会导致OOM异常。内存泄漏可能是由于代码中的bug或者第三方库的问题导致的。

堆内存溢出(Heap Space):当堆内存不足以分配新对象时,就会抛出java.lang.OutOfMemoryError: Java heap space异常。这可能是由于堆内存设置过小或者存在内存泄漏问题导致的。

虚拟机栈和本地方法栈溢出:如果线程请求的栈深度超过了虚拟机所允许的最大深度,就会抛出StackOverFlowError异常。这可能是由于递归调用过深或者线程过多导致的。

运行时常量池溢出:如果向运行时常量池中添加内容,而JVM无法再分配更多内存时,就会抛出java.lang.OutOfMemoryError: PermGen space异常。

方法区溢出:方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。如果方法区无法分配更多内存时,就会抛出java.lang.OutOfMemoryError: Metaspace异常。

遇到OOM异常时,需要根据具体的异常信息和堆栈跟踪信息进行分析和排查,找出根本原因并进行修复。

38. SOF你遇到过哪些情况?

SOF(堆栈溢出StackOverflow): StackOverflowError 的定义:当应用程序递归太深而发生堆栈溢出时,抛出该错误。 因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。 栈溢出的原因:递归调用,大量循环或死循环,全局变量是否过多,数组、List、map数据过大。

39. 程序?

是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。

40. 进程?

是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。

41. 线程?

与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

42. 上述三者之间关系是什么**?**

系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。

43. Java 序列化中如果有些字段不想进行序列化,怎么办**?**

对于不想进行序列化的变量,使用 transient 关键字修饰。 transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化 时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。

44. Java 中 IO 流分为几种?

按照流的流向分,可以分为输入流和输出流; 按照操作单元划分,可以划分为字节流和字符流; 按照流的角色划分为节点流和处理流。

45. 说说List,Set,Map三者的区别?

List(列表):List是一个有序集合,元素之间的顺序是按照它们被添加到集合中的顺序排列的。List允许存储重复的元素,并且可以包含null值。List接口提供了对集合元素的索引访问,可以通过索引来获取和设置元素。常见的List实现类有ArrayList、LinkedList等。

Set(集合):Set是一个无序集合,元素之间的顺序是不确定的。Set不允许存储重复的元素,也就是说,集合中的每个元素都是唯一的。Set接口提供了对集合元素的快速查找操作,但不允许包含null值。常见的Set实现类有HashSet、LinkedHashSet等。

Map(映射):Map是一个键值对的集合,每个键都映射到一个值。Map中的元素没有顺序,键是唯一的,但值可以重复。Map接口提供了根据键来获取和设置值的方法。常见的Map实现类有HashMap、TreeMap等。

总的来说,List主要用于存储有序的元素集合,可以按照索引访问元素;Set主要用于存储唯一的元素集合,不允许存储重复的元素;Map主要用于存储键值对的集合,可以通过键来获取和设置值。选择使用哪种集合类型取决于具体的需求和应用场景。

46. Object 有哪些常用方法?大致说一下每个方法的含义?

equals 方法 该方法使用频率非常高。一般 equals 和 == 是不一样的,但是在 Object 中两者是一样的。子类一般都要重写这个方法。

hashCode 方法该方法用于哈希查找,重写了 equals 方法一般都要重写hashCode 方法,这个方法在一些具有哈希功能的 Collection 中用到。

notify 方法 配合 synchronized 使用,该方法唤醒在该对象上等待队列中的某个线程(同步队列中的线程是给 抢占 CPU 的线程,等待队列中的线程指的是等待唤醒的线程)。

notifyAll 方法 配合 synchronized 使用,该方法唤醒在该对象上等待队列中的所有线程。

clone 方法 保护方法,实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出 CloneNotSupportedException 异常,深拷贝也需要实现 Cloneable,同时其成员变量为引用类型的也需要实现Cloneable,然后重写 clone 方法。

finalize 方法 该方法和垃圾收集器有关系,判断一个对象是否可以被回收的最后一步就是判断是否重写了此方 法。

47. 获取一个类Class对象的方式有哪些?

第一种:通过类对象的 getClass() 方法获取,细心点的都知道,这个 getClass 是 Object 类里面的方法。第二种:通过类的静态成员表示,每个类都有隐含的静态成员 class。第三种:通过Class类的静态方法forName() 方法获取。

48. 有数组了为什么还要搞个 ArrayList 呢?

通常我们在使用的时候,如果在不明确要插入多少数据的情况下,普通数组就很尴尬了,因为你不知道需要初始化数组大小为多少,而 ArrayList 可以使用默认的大小,当元素个数到达一定程度后,会自动扩容。 可以这么来理解:我们常说的数组是定死的数组,ArrayList 却是动态数组。

49. HashMap 中的 key 我们可以使用任何类作为 key 吗?

在Java的 HashMap 中,key 可以是任何对象,但通常我们会使用那些实现了 equals() 和 hashCode() 方法的类作为 key。这是因为 HashMap 是基于这两个方法来存储和检索元素的。

equals() 方法:用于比较两个对象是否相等。对于键值对来说,如果两个键对象通过 equals() 方法比较结果为相等,那么它们被认为是同一个键。

hashCode() 方法:返回对象的哈希码值,通常用于快速查找对象在集合中的位置。如果两个对象通过 equals() 方法比较结果为相等,那么它们的哈希码值应该相等。

但是,需要注意的是,不是所有的类都适合作 HashMap 的 key。以下是一些不适合作为 key 的情况:

不可变对象(Immutable Objects):如果一个对象是不可变的,那么它的 hashCode() 方法返回的值就不会改变,这可能会导致 HashMap 的性能下降。

自定义类没有重写 equals() 和 hashCode() 方法:如果一个自定义类没有重写 equals() 和 hashCode() 方法,那么默认实现可能不是按照你期望的方式工作的。

对象大小超过一定限制:HashMap 的 key 大小是有限制的,如果 key 对象的大小超过了限制,可能会导致性能问题。

频繁变动的对象:如果一个对象在频繁地变动,那么它的哈希码值可能会频繁地改变,这可能会导致 HashMap 的性能下降。

因此,虽然理论上你可以使用任何类作为 HashMap 的 key,但在实际应用中,最好选择那些适合作为 key 的类。

50. HashMap 的长度为什么是 2 的 N 次方呢?// 第二天

为了提高查询性能。这个设计决策基于哈希表(Hash Table)的实现原理。

在哈希表中,元素通过哈希函数被分配到桶(Bucket)中。当哈希函数的输出落在某个桶的索引范围内时,元素就被放在这个桶里。为了提高查询效率,理想的哈希表大小应该是 2 的整数次幂,因为这样可以将哈希值直接转换为桶的索引。

如果哈希表的大小不是 2 的整数次幂,那么一些桶可能会被浪费,导致空间利用率降低。而选择 2 的整数次幂大小可以确保所有桶都被充分利用,从而提高空间利用率和查询性能。

此外,使用 2 的 N 次方大小还有一个好处,那就是在计算哈希值模除时可以使用位运算,这比直接使用除法更快。这是因为位运算的速度非常快,特别是在现代计算机架构中,位运算通常比除法更快。

综上所述,HashMap 的长度选择 2 的 N 次方是为了提高查询性能和空间利用率,以及利用位运算的优势。

51. HashMap 与 ConcurrentHashMap 的异同 ?

\1. 都是 key-value 形式的存储数据; 2. HashMap 是线程不安全的,ConcurrentHashMap 是 JUC 下的线程安全的; 3. HashMap 底层数据结构是数组 + 链表(JDK 1.8 之前)。JDK 1.8 之后是数组 + 链表 + 红黑树。当链表中元素个数达到 8 的时候,链表的查询速度不如红黑树快,链表会转为红黑树,红黑树查询速度快; 4. HashMap 初始数组大小为 16(默认),当出现扩容的时候,以 0.75 * 数组大小的方式进行扩容; 5. ConcurrentHashMap 在 JDK 1.8 之前是采用分段锁来现实的 Segment + HashEntry, Segment 数组大小默认是 16,2 的 n 次方;JDK 1.8 之后,采用 Node + CAS + Synchronized 来保证并发安全进行实现。

52. 说说堆和栈的区别?

堆和栈是两种不同的内存管理方式,它们在存储数据、分配内存和释放内存方面存在显著差异。

堆(Heap):

动态分配:堆上的内存是在运行时动态分配的,程序员可以在程序运行时动态地申请和释放内存。

大小无限制:堆的大小通常比栈大,因为其没有固定的大小限制,可以按需扩展。

申请与释放:在堆上申请内存需要使用 malloc、calloc 或 realloc 等函数,释放内存则使用 free 函数。

使用不便:由于需要手动管理,使用不当可能会导致内存泄漏或野指针问题。

碎片化:频繁的内存申请和释放会导致堆内存碎片化,影响性能。

栈(Stack):

静态分配:栈上的内存是在函数调用时静态分配的,其大小在编译时确定,并在线程创建时初始化。

有限制:栈的大小是有限的,通常远小于堆的大小。

自动管理:栈上的内存生命周期由编译器自动管理,当函数返回时,其局部变量会被自动释放。

安全:栈上的内存分配和释放是自动进行的,因此不易产生内存管理相关的问题。

快速:由于栈上的内存分配和释放速度非常快,所以适用于局部变量和临时数据的存储。

总的来说,堆和栈的主要区别在于它们的内存分配方式、大小、生命周期管理和使用场景。在实际编程中,需要根据具体需求选择使用堆还是栈。

53. 什么时候会触发FullGC?

Full GC 是 Java 的一种垃圾收集机制,用于回收堆内存中的所有对象。当堆内存中的空间不足以满足新的内存分配请求时,就会触发 Full GC。以下是几种常见的触发 Full GC 的情况:

堆内存不足:当应用程序需要分配的内存超出了当前堆内存可用空间时,就会触发 Full GC。这通常发生在大量对象被创建或数据结构不断增长时。

老年代空间不足:在 Java 堆内存中,新生代和老年代是两个不同的区域。当老年代空间不足以容纳新创建的对象时,也会触发 Full GC。

System.gc() 显式调用:虽然不推荐显式调用垃圾收集器,但在某些情况下,开发人员可能会显式调用 System.gc() 方法来触发 Full GC。然而,这种做法并不总是有效的,因为垃圾收集器的行为是不可预测的。

JVM 参数设置:通过设置 JVM 参数,如 -XX:+UseConcMarkSweepGC 和 -XX:+ExplicitGCInvokesConcurrent,可以配置垃圾收集器的行为,并在特定情况下触发 Full GC。

内存泄漏和异常对象增长:如果应用程序存在内存泄漏或异常的对象增长,可能会导致堆内存迅速耗尽,从而触发 Full GC。

需要注意的是,频繁的 Full GC 会对应用程序的性能产生负面影响,因为垃圾收集器需要停止应用程序的执行并释放堆内存中的对象。因此,优化应用程序以减少 Full GC 的发生和减少 Full GC 的持续时间是至关重要的。

54. 什么是Java虚拟机?为什么Java被称作是“平台无关的编程语 言”?

Java虚拟机(JVM)是一个软件实现的虚拟计算机,它能够执行Java字节码,这些字节码是在Java源代码编译之后生成的。JVM可以在任何支持它的硬件和操作系统上运行,这就是Java被称为“平台无关的编程语言”的原因。只要编写Java代码并将其编译成字节码,就可以在任何安装了Java运行时环境的机器上运行该程序,而不需要对每个平台都进行重新编译。这就是Java的跨平台能力。。

55. 说说对象分配规则**?**

对象在Java虚拟机(JVM)中的分配规则主要基于以下几个因素:

优先在Eden区分配:大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。

大对象直接进入老年代:大对象指的是需要大量连续内存空间的Java对象,比如长字符串或大数组。如果虚拟机提供了-XX:PretenureSizeThreshold参数,大于该值的对象将在老年代中分配,以避免在Eden区和两个Survivor区之间发生大量的内存复制。

长期存活的对象将进入老年代:虚拟机为每个对象定义了一个对象年龄计数器。如果对象在Eden区出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1。对象在Survivor区中每熬过一次Minor GC,年龄就增加1。当年龄达到一定程度(默认为15)时,就会被晋升到老年代。

相同年龄的对象大小总和大于Survivor的一半,直接进入老年代:无需等到设定的阈值年龄。

以上就是Java虚拟机对象内存分配的规则。需要注意的是,内存分配的规则可能会因不同的JVM实现和不同的版本而有所差异,因此在具体的实践中可能会有所不同。

56. 描述一下JVM加载class文件的原理机制?

JVM加载class文件的原理机制主要涉及以下几个步骤:

类的加载请求:当JVM启动时,它需要知道需要加载哪些类。这个信息通常是从命令行参数、系统属性、环境变量等地方获取的。

类的加载过程:类的加载过程包括加载、链接(验证、准备、解析)和初始化三个阶段。

加载:在类的加载阶段,JVM需要找到对应的class文件,并将其加载到内存中。这一步通常由类加载器完成,它会根据给定的类名去寻找对应的class文件。

链接:链接阶段包括三个部分:验证、准备和解析。验证是为了确保被加载的类文件的正确性和安全性;准备是为类的静态变量分配内存,并设置默认的初始值;解析是将符号引用转换为直接引用。

初始化:在初始化阶段,为类的静态变量赋予正确的初始值。这一步通常由JVM自动完成,也可以通过Java代码进行显式初始化。

类的卸载:当一个类不再被使用时,JVM会将其从内存中卸载,释放其所占用的空间。

需要注意的是,类的加载机制是Java虚拟机实现的一部分,不同的JVM实现可能会有不同的实现细节和行为。此外,类的加载机制也是Java安全模型和字节码验证的重要基础。

57. 说说Java对象创建过程 ?

Java对象创建过程涉及多个步骤,包括类加载、内存分配和初始化。以下是详细说明:

类加载:在Java中,类的加载指的是将类的字节码文件加载到内存中,并创建一个对应的Class对象。这个过程由类加载器完成。类加载器首先会检查该类是否已经被加载过,如果已经加载过,就直接复用已经加载的Class对象;否则,它会去加载这个类的字节码文件,并创建一个新的Class对象。

内存分配:对象在堆内存中分配空间。堆是所有线程共享的内存区域,用于动态分配内存。当我们在代码中创建一个对象时,JVM会在堆上为这个对象分配内存。对象的内存分配是在堆上进行的,堆是一个所有线程共享的区域,用于动态分配内存。堆的大小可以通过JVM参数来调整。

初始化:对象的初始化是执行构造函数和设置对象的初始状态。在Java中,构造函数是在创建对象时自动调用的方法,用于初始化对象的属性。构造函数的名称必须与类名相同,且没有返回类型。

以上就是Java对象创建的过程。这个过程涉及到多个步骤,需要理解每个步骤的作用和细节才能更好地理解Java的内存管理和垃圾回收机制。

58. 简述Java的对象结构**?**

Java的对象结构主要由三个部分组成:对象头、实例数据和对齐填充。

对象头:对象头由三部分组成,分别是Mark Word、指向类的指针和数组长度。Mark Word主要用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁标识状态、线程持有的锁和偏向线程ID等。指向类的指针记录了对象类在方法区或者元数据空间的位置。只有数组对象才有数组长度,用于记录数组的长度。

实例数据:实例数据部分是对象真正存储的有效信息,即在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都会记录在实例数据中。

对齐填充:对齐填充是为了满足JVM对对象大小的要求,即对象起始地址必须是8字节的整数倍(8字节对齐)。

这就是Java对象的基本结构,理解它有助于更好地理解Java内存管理和垃圾回收机制。

59. 如何判断对象可以被回收?

在Java中,对象的回收是由垃圾回收器(Garbage Collector,GC)自动完成的。垃圾回收器会定期检查对象,判断它们是否不再被引用,或者是否无法访问。如果一个对象满足以下条件,那么它就可以被垃圾回收:

无引用:如果一个对象没有任何引用指向它,那么这个对象就无法被外部访问,因此可以被回收。

弱引用:如果一个对象只被弱引用(WeakReference)所引用,那么这个对象也可以被回收。弱引用允许垃圾回收器回收其引用的对象,无论当前是否还有强引用存在。

可中断引用:如果一个对象只被可中断引用(SoftReference)所引用,那么在内存空间不足时,垃圾回收器会将其回收。只有在内存即将耗尽时,才会回收软引用关联的对象。

虚引用:如果一个对象只被虚引用(PhantomReference)所引用,那么这个对象可以被回收。

finalize方法:当垃圾回收器准备回收一个对象时,会首先调用该对象的finalize方法。如果该方法返回false,则表示该对象仍然需要被外部访问,因此不会被回收。如果finalize方法返回true,则表示该对象不再需要被外部访问,可以被回收。

需要注意的是,即使一个对象可以被垃圾回收,垃圾回收器的执行时间和频率都是不确定的,因此无法保证一个对象立即被回收。此外,垃圾回收器的具体实现和行为也可能因JVM的不同而有所差异。

60. JVM的永久代中会发生垃圾回收么?

在Oracle的JDK版本中,永久代(PermGen)确实会发生垃圾回收。不过在Java 8之后,永久代被元空间(Metaspace)取代了,所以从Java 8开始,不会在永久代中进行垃圾回收。在元空间中,垃圾回收也是会发生的。

61. 你知道哪些垃圾收集算法 ?

垃圾收集算法有很多种,其中一些常见的算法包括:

标记-清除(Mark-Sweep)算法:这是最基本的垃圾收集算法,分为标记和清除两个阶段。在标记阶段,垃圾收集器会标记出所有需要回收的对象;在清除阶段,垃圾收集器会清除所有被标记的对象所占用的空间。

复制(Copying)算法:为了解决内存碎片问题,复制算法将可用内存分为两块相同大小的区域,每次只使用其中一块。当一块内存用完时,将存活的对象复制到另一块内存上,然后清除已使用的内存。

标记-整理(Mark-Compact)算法:在标记-清除算法的基础上,标记-整理算法会找到所有存活的对象,并将它们移动到一端,然后清除边界以外的所有内存。这样可以解决内存碎片问题。

分代收集(Generational)算法:该算法基于这样一个观察:大多数对象很快就会变得无用,而存活的对象会存活很长时间。分代收集算法将内存分为新生代和老年代,并在新生代中频繁地进行Minor GC,而在老年代中进行Major GC。

分块收集(Block-Based)算法:这种算法将内存分为固定大小的块,每个块有自己的垃圾回收器。块的大小可以动态调整,以适应不同大小的对象。

分区收集(Region-Based)算法:该算法将内存划分为多个独立的区域,每个区域都有自己的垃圾回收器。这样可以减少垃圾回收时的停顿时间。

62. 调优命令有哪些?

Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo

jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。

jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟 机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

jmap,JVM Memory Map命令用于生成heap dump文件

jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内 置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看

jstack,用于生成java虚拟机当前时刻的线程快照。

jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。

63. 常见调优工具有哪些 ?

常见的JVM调优工具有很多,主要分为两类:

JDK自带监控工具:包括jconsole、jvisualvm等。这些工具用于对JVM中内存、线程和类等进行监控,可以实时查看JVM的运行状态,帮助开发者定位问题并进行调优。

第三方工具:包括MAT(Memory Analyzer Tool)、GChisto等。MAT是一个基于Eclipse的内存分析工具,可以快速、功能丰富地分析Java堆内存,帮助查找内存泄露和减少内存消耗。GChisto是一款专业分析GC日志的工具,能够帮助开发者更好地理解GC日志,并对JVM进行调优 。

64. Minor GC与Full GC分别在什么时候发生?

新生代内存不够用时候发生MGC也叫YGC,JVM内存不够的时候发生FGC

65. 你知道哪些JVM性能调优参数?(简单版回答)// 第三天

-Xmsm:设置JVM初始堆内存大小。

-Xmxm:设置JVM最大堆内存大小。

-Xssk:设置每个线程的堆栈大小。

-XX:NewRatio=1:设置新生代和老年代的比例。

-XX:SurvivorRatio=8:设置新生代中Eden区和Survivor区的比例。

-XX:MaxTenuringThreshold=15:设置对象在晋升老年代之前可以经历的GC次数。

-XX:ParallelGCThreads=4:设置并行垃圾回收器的线程数。

66. 对象一定分配在堆中吗?

不一定的,JVM通过「逃逸分析」,那些逃不出方法的对象会在栈上分配。

67. 什么是逃逸分析?

逃逸分析是一种编译器优化技术,用于判断一个对象指针是否会在方法之外被使用,从而进行相应的优化操作。通过逃逸分析,编译器可以对对象的动态作用域进行分析,以确定是否可以将对象分配在栈上,而不是在堆上。如果一个对象的指针没有发生逃逸,那么就可以将这个对象直接分配在栈上,从而减少堆内存的分配和回收压力,提高程序的执行效率和性能。

逃逸分析的目的是为了优化程序的内存分配和对象重用,提高程序的运行效率和性能。在编译器优化过程中,如果能够确定一个对象指针不会逃逸出方法的作用域,那么就可以将该对象分配在栈上,而不是在堆上。这样做的好处是减少了堆上对象的创建和回收的开销,同时也减少了对垃圾回收的压力。

逃逸分析的核心思想是通过方法的调用链分析,判断对象指针是否会被传递到方法之外。如果对象指针没有被传递到方法之外,那么就可以将对象分配在栈上。因为栈的分配和回收比堆快得多,所以能够有效地减少程序的开销。

逃逸分析可以应用于多种优化场景,例如当一个方法的返回值是一个对象指针时,编译器可以进行逃逸分析,判断该对象是否被调用者使用,如果没有使用,则可以进行逃逸分析优化,将对象分配在栈上。又如当一个对象被多个方法共同使用时,编译器可以进行逃逸分析,将对象实例放到堆上,以供多个方法共享使用,从而提高程序的效率。

总之,逃逸分析是一项重要的静态分析技术,通过对方法的调用链进行分析,判断对象是否会逃逸出方法的作用域,从而决定对象的分配方式。它能够显著提高程序的执行效率和性能,因此被广泛应用于现代编译器优化中。。

68. 什么是永久代?它和方法区有什么关系呢?

如果在HotSpot虚拟机上开发、部署,很多程序员都把方法区称作永久代。可以说方法区是规范,永久代是Hotspot针对该规范进行的实现。在Java7及以前的版本,方法区都是永久代实现 的。

69. 什么是元空间?它和方法区有什么关系呢?

对于Java8,HotSpots取消了永久代,取而代之的是元空间(Metaspace)。换句话说,就是方法区还是在的,只是实现变了,从永久代变为元空间了。

70. 为什么使用元空间替换了永久代?

元空间(Metaspace)替代了永久代(PermGen)主要是因为Java 8引入了模块化系统,这使得永久代不再满足需求。

首先,永久代主要用于存储类的元数据,但在Java 8中,类的元数据存储在独立的元空间中,这样可以使类的元数据和代码分离,更符合模块化原则。

其次,元空间使用本地内存来存储类的元数据,这样可以避免在JVM堆中分配空间,减少了内存碎片和垃圾回收的开销。

最后,元空间的大小可以通过参数进行配置,更加灵活,可以更好地满足不同的应用需求。

71. 什么是Stop The World ?

进行垃圾回收的过程中,会涉及对象的移动。为了保证对象引用更新的正确性,必须暂停所有的用户线程,像这样的停顿,虚拟机设计者形象描述为「Stop The World」。也简称为STW。

72. 什么是OopMap**?**

OopMap是用于记录对象指针的位置和类型的数据结构。在Java HotSpot虚拟机中,OopMap用于记录在栈上和寄存器中哪些位置是引用类型的数据。OopMap是一种数据结构,用于枚举垃圾回收时的GC Roots,即引用的对象。它记录了栈中每个位置的引用类型数据,帮助垃圾回收器快速准确地找到所有存活的对象。

每个线程都有一个OopMap,每个栈桢也都有一个OopMap,记录栈上该方法中引用的对象的位置。当线程执行到安全点(安全点或安全区域)时,垃圾回收器会使用OopMap来查找和回收存活的对象。

OopMap的作用是避免全栈扫描,加快枚举根节点的速度,并帮助HotSpot实现准确式GC。通过使用OopMap,垃圾回收器可以在不需要扫描整个栈的情况下找到所有引用的对象,从而提高回收效率。

73. 什么是安全点**?**

安全点是指在程序执行过程中特定位置,这些位置在程序执行到此位置时,可以安全地进行全局暂停,并进行垃圾回收,而不会引起程序混乱或死锁。安全点的选择对于垃圾回收的效率和成功至关重要,如果安全点设置得过于频繁,可能会导致垃圾回收效率低下,影响程序性能;如果安全点设置得太少,可能会导致垃圾回收器无法及时回收内存,造成内存泄漏。

在Java虚拟机中,安全点通常是在执行时间长、执行频率高的指令处,如方法调用、循环跳转和异常跳转等。在这些位置暂停程序执行,可以减少垃圾回收的停顿时间,提高程序的执行效率。在安全点上,垃圾回收器可以安全地检查并标记存活的对象,以便进行垃圾回收。

除了安全点外,还有一个概念叫做安全区域。安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始垃圾回收都是安全的。安全区域通常在循环和异常处理代码中定义,以确保在这些代码段中不会发生垃圾回收。

总的来说,安全点是程序执行过程中特定的位置,这些位置可以安全地进行全局暂停和垃圾回收。安全点的选择和设置对于垃圾回收的效率和成功至关重要,需要谨慎考虑。

74. 什么是指针碰撞**?**

一般情况下,JVM的对象都放在堆内存中(发生逃逸分析除外)。当类加载检查通过后,Java虚拟机开始为新生对象分配内存。如果Java堆中内存是绝对规整的,所有被使用过的的内存都被放到一边,空闲的内存放到另外一边,中间放着一个指针作为分界点的指示器,所分配内存仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的实例,这种分配方式就是指针碰撞。

75. 什么是空闲列表**?**

如果Java堆内存中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,不可以进行指针碰撞啦,虚拟机必须维护一个列表,记录哪些内存是可用的,在分配的时候从列表找到一块大的空间分配给对象实例,并更新列表上的记录,这种分配方式就是空闲列表。

76. 什么是TLAB**?**

TLAB是Thread Local Allocation Buffer的简称,即线程本地分配缓存。这是一种为每个线程分配专用的内存分配区域的技术,以避免多线程之间的竞争,从而提高内存分配的效率。

在启用TLAB的情况下,当线程被创建时,虚拟机会为每个线程分配一块TLAB空间,只供当前线程使用。如果需要分配内存,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提高分配效率。

需要注意的是,虽然每个线程在初始化时都会去堆内存中申请一块TLAB,但这并不意味着这个TLAB区域的内存其他线程就完全无法访问。其他线程的读取操作还是可以的,只不过无法在这个区域中分配内存而已。在使用上也没什么区别。

77. 如何选择垃圾收集器**?**

选择合适的垃圾收集器需要考虑多个因素,包括内存大小、系统吞吐量、延迟要求以及应用程序特性等。以下是一些选择垃圾收集器的建议:

如果应用程序的数据集较小(最大约100 MB),则可以使用串行收集器(Serial GC)。

如果应用程序将在单个处理器上运行,并且没有暂停时间要求,则可以让虚拟机选择收集器,或使用串行收集器(Serial GC)。

如果应用程序的峰值性能是首要考虑因素,并且没有暂停时间要求或可接受的暂停时间为1秒或更长时间,则可以使用并行收集器(Parallel GC)。

如果响应时间比整体吞吐量更重要,并且垃圾收集暂停时间必须保持小于1秒,则应该使用并发收集器(Concurrent GC),如CMS或G1垃圾收集器。

对于内存占用较大的应用程序,可以选择使用G1垃圾收集器。G1垃圾收集器将堆内存划分为多个独立的区域,每个区域都可以独立进行垃圾回收。这样可以更好地控制垃圾回收的停顿时间和吞吐量。

对于需要处理大量数据的应用程序,可以选择使用ZGC垃圾收集器。ZGC是一种基于读屏障的垃圾收集器,可以在不停止应用程序的情况下进行垃圾回收,并且具有较高的吞吐量和较低的停顿时间。

总之,选择合适的垃圾收集器需要考虑应用程序的需求和特性,以及系统的硬件和软件环境。在实际应用中,建议进行性能测试和调优,以找到最适合应用程序的垃圾收集器。

78. 什么是类加载器?

类加载器是一个用来加载类文件的类。Java 源代码通过 javac 编译器编译成类文件。然后 JVM 来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。

79. 说说Java中实现多线程有几种方法?

在Java中,实现多线程主要有四种方法:

实现 Runnable 接口:实现 Runnable 接口需要实现 run() 方法,该方法定义了线程需要执行的任务。然后通过 Thread 类或者 Executor 框架创建线程。

继承 Thread 类:继承 Thread 类需要重写 run() 方法,然后通过调用 start() 方法启动线程。

使用 Callable 和 Future:Callable 是另一个实现了 Runnable 接口的类,它返回一个结果,可以被多次调用。Future 表示异步计算的结果。可以使用 ExecutorService 的 submit() 方法提交 Callable 任务,返回一个 Future 对象。

使用 CompletableFuture:CompletableFuture 是 Java 8 中引入的一个类,它是对 Future 的增强,提供了更丰富的操作符,例如 thenApply、thenAccept、thenCompose 等,可以链式处理异步计算的结果。

除了以上四种方法外,还有一些其他方法可以在Java中实现多线程,但它们并不是主要的方法。这些方法包括:

使用线程池:通过创建 ExecutorService 对象来创建和管理线程池。线程池中的线程是共享的,可以复用,从而避免了频繁创建和销毁线程的开销。

综上所述,Java 中实现多线程主要有四种方法:实现 Runnable 接口、继承 Thread 类、使用 Callable 和 Future、使用 CompletableFuture。除此之外,还有使用线程池等。

80. 如何停止一个正在运行的线程?

1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。 3、使用interrupt方法中断线程。

81. notify()和notifyAll()有什么区别? // 2第一天

它们的主要区别在于影响的线程数量和唤醒线程的方式。

notify():

唤醒一个在此对象监视器上等待的线程(即调用过wait()的线程)。

只能唤醒一个线程。

被唤醒的线程将获得该对象的监视器,然后可以对对象的共享资源进行访问和修改。

notifyAll():

唤醒所有在此对象监视器上等待的线程。

可以唤醒多个线程。

所有被唤醒的线程都会获得该对象的监视器,然后可以对对象的共享资源进行访问和修改。

82. sleep()和wait() 有什么区别?

sleep()和wait()都是Java中线程控制的方法,但它们之间存在一些重要的区别:

用途:

sleep(): 让当前线程休眠一段时间,主要用于线程调度。

wait(): 使当前线程等待,直到其他线程调用同一对象的notify()或notifyAll()方法,主要用于协调多个线程对共享资源的访问。

是否占用CPU时间:

sleep(): 不占用CPU时间,线程进入休眠状态。

wait(): 占用CPU时间,线程进入等待状态,但可以被其他线程中断。

释放资源:

sleep(): 不释放任何锁资源。

wait(): 释放对象的锁,允许其他线程进入同步代码块或方法。

异常处理:

sleep(): 可以抛出InterruptedException。

wait(): 也可以抛出InterruptedException。

安全性:

sleep(): 是不安全的,因为可以通过反射机制强制唤醒一个正在休眠的线程。

wait(): 是相对安全的,因为它在对象的监视器上等待,需要获得该对象的监视器才能被唤醒。

唤醒机制:

sleep(): 只能被时间触发。

wait(): 可以被其他线程通过调用同一对象的notify()或notifyAll()方法触发,也可以被中断。

使用场景:

sleep(): 通常用于让当前线程暂停执行一段时间,或者用于测试和调试。

wait(): 通常用于多线程之间的协调,特别是在处理共享资源时。

灵活性:

sleep(): 比较简单和直接,只需要传入一个表示时间的参数。

wait(): 更灵活,可以与其他同步方法(如notify()和notifyAll())结合使用来控制线程的行为。

83. Thread 类中的start() 和 run() 方法有什么区别?

start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start() 方法才会启动新线程。

84. 为什么wait, notify 和 notifyAll这些方法不在thread类里 面?

wait(), notify(), 和 notifyAll() 方法并没有在 Thread 类中定义,而是在 Object 类中定义的。这是因为在 Java 中,所有类都继承自 Object 类,而 Thread 类也不例外。这样做有以下几个原因:

通用性:Object 类中的这些方法是为所有对象提供的,而不仅仅是线程。任何对象都可以调用这些方法,无论是线程还是其他类型的对象。这使得这些方法更具通用性,而不仅仅局限于线程。

多线程同步:wait(), notify(), 和 notifyAll() 方法是用于线程之间的协调和同步的。它们通常与对象的监视器(monitor)一起使用,以控制多个线程对共享资源的访问。由于所有对象都有监视器,因此这些方法放在 Object 类中,而不是 Thread 类中,使得任何对象都可以用于多线程同步。

避免过度耦合:将 wait(), notify(), 和 notifyAll() 方法放在 Object 类中,而不是 Thread 类中,有助于避免过度耦合。这意味着线程类和其他类可以独立地使用这些方法,而不需要紧密地绑定在一起。这增加了代码的灵活性和可维护性。

线程管理:线程管理是线程类特有的功能,如启动、停止、挂起等。这些方法更适合放在 Thread 类中,而不是放在 Object 类中。因此,wait(), notify(), 和 notifyAll() 方法被放在 Object 类中,以便更好地组织和管理线程相关的功能。

综上所述,将 wait(), notify(), 和 notifyAll() 方法放在 Object 类中而不是 Thread 类中是为了实现通用性、多线程同步、避免过度耦合和更好地组织线程相关功能。

85. 有三个线程T1,T2,T3,如何保证顺序执行?

可以使用Java中的同步机制,如同步代码块、Java并发工具类(如CountDownLatch、CyclicBarrier和Phaser)或信号量。

使用同步代码块:可以使用同步代码块来确保同一时间只有一个线程可以访问某个代码区域。通过将代码块标记为同步,可以在代码块前获取锁,并在代码块后释放锁,从而控制线程的执行顺序。

使用Java并发工具类:Java提供了许多并发工具类,如CountDownLatch、CyclicBarrier和Phaser等,这些工具类可以帮助实现线程间的同步和协调。例如,CountDownLatch可以用于等待一组线程完成某个任务,CyclicBarrier和Phaser则可以用于实现线程间的同步点。

使用线程池:通过使用线程池,可以更好地管理和控制线程的执行。线程池中的线程是复用的,可以避免频繁地创建和销毁线程对象。通过合理配置线程池的大小,可以控制同时执行的线程数量,从而实现线程的顺序执行。

使用信号量:信号量是一种计数器,可以用来控制对共享资源的访问。通过使用信号量,可以确保在任何时刻只有一个线程可以访问共享资源,从而实现线程的顺序执行。

86. SynchronizedMap和ConcurrentHashMap有什么区别**?**

SynchronizedMap和ConcurrentHashMap是Java中用于实现线程安全的Map容器的两种不同的实现方式。

SynchronizedMap是通过在每个方法上加锁来实现线程安全的。当一个线程访问SynchronizedMap的某个方法时,其他线程必须等待,直到该线程释放锁。这种方式虽然能够保证线程安全,是在高并发环境下性能较差,因为每次只能有一个线程访问该容器。

ConcurrentHashMap则采用了一种不同的实现方式,即锁分段技术。它将整个Map分成多个段(Segment),每个段都有自己的锁。不同的线程可以同时访问不同的段,从而提高了并发性能。这种方式在高并发环境下能够更好地支持并发访问。

另外,ConcurrentHashMap还提供了更多的功能和优化,例如支持高效的并发读取和写入操作,以及自动扩容等。

总结起来,SynchronizedMap通过加锁实现线程安全,但性能较差;而ConcurrentHashMap通过锁分段技术实现线程安全,并且在高并发环境下性能更好。

87. HashMap在java1.8前后数据结构和底层原理?

Java 1.8之前的HashMap:

数据结构:在Java 1.8之前,HashMap的底层数据结构是由数组和链表组成的。数组是HashMap的主体,用于存储键值对,而链表则用于解决哈希冲突。当发生哈希冲突时,会将冲突的元素插入到链表中。

底层原理:在Java 1.8之前,HashMap使用哈希表实现,通过哈希函数将键转换为数组的索引位置。当插入一个元素时,会先计算键的哈希值,然后根据哈希值确定元素在数组中的位置。如果发生哈希冲突,会将冲突的元素插入到链表中。在查找元素时,会根据键的哈希值在数组中查找元素。

Java 1.8及之后的HashMap:

数据结构:在Java 1.8及之后,HashMap进行了较大的改进,引入了红黑树。当链表长度超过一定阈值(默认为8)并且数组长度大于64时,链表会转换为红黑树。红黑树是一种自平衡的二叉查找树,可以保证查找效率。因此,Java 1.8及之后的HashMap的底层数据结构是由数组、链表和红黑树组成的。

底层原理:在Java 1.8及之后,HashMap的底层实现进行了优化。首先,当创建HashMap时,默认容量为0,当调用put方法时,容量会进行首次扩容,长度为16。如果元素的数量超过了负载因子(默认为0.75)所决定的容量,则会进行扩容。每次扩容时,数组长度会翻倍。在添加元素时,首先计算键的哈希值,然后通过取模运算计算出元素在数组中的位置。如果当前位置已经存在元素,则将该元素插入到链表中。当链表长度超过一定阈值时,链表会转换为红黑树。在查找元素时,首先计算键的哈希值,然后通过取模运算在数组中查找元素。如果当前位置没有元素,则继续在链表或红黑树中查找。

总结起来,Java 1.8之前的HashMap使用哈希表实现,数据结构为数组+链表;而Java 1.8及之后的HashMap则引入了红黑树进行优化,数据结构为数组+链表+红黑树。这种改进可以减少哈希冲突,提高查找效率。

88. Thread类中的yield方法有什么作用**?**

Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法 而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可 能在进入到暂停状态后马上又被执行。

89. Java线程池中submit() 和 execute()方法有什么区别?

两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了 Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些 方法。

90. 说一说自己对于 synchronized 关键字的了解 ?

synchronized 关键字是Java中用于实现线程同步的关键字之一。它可以应用于方法或代码块,以确保在多线程环境下只有一个线程可以访问被 synchronized 修饰的部分。

当一个线程进入到 synchronized 修饰的方法或代码块时,它会锁定该对象,其他线程将无法访问该对象的其他 synchronized 方法或代码块,直到当前线程释放锁。这样可以避免多个线程同时修改共享资源而导致的数据不一致问题。

使用 synchronized 关键字可以有效地保护共享资源的完整性和一致性,但也可能降低程序的性能。因为每次只有一个线程可以执行 synchronized 代码块,其他线程必须等待,这可能会引起线程竞争和性能瓶颈。

除了应用于方法和代码块,synchronized 还可以应用于静态方法和类,实现对静态资源的同步访问。此外,Java 5及以上版本还引入了更灵活的锁机制,如 ReentrantLock 类,它提供了更多的功能和扩展性,但使用时需要手动管理锁的获取和释放。

总结来说,synchronized 是Java中用于实现线程同步的关键字,它可以确保在多线程环境下只有一个线程可以访问被 synchronized 修饰的部分。它是最基本和常用的线程同步机制,但在性能方面可能有一些局限性。

91. 常用的线程池有哪些?

FixedThreadPool:固定大小线程池,创建时指定线程数量,一旦线程达到最大值后,新的任务将等待。适用于控制并发线程数的场景。

CachedThreadPool:缓存线程池,适用于执行大量短期任务的场景。线程池中的线程数量会根据需求自动调整,如果有空闲线程可用,则会复用空闲线程,否则创建新的线程。

SingleThreadExecutor:单线程线程池,只有一个工作线程的线程池,适用于需要保证任务按顺序依次执行的场景。

ScheduledThreadPool:定时任务线程池,可以按照给定的时间间隔或者延迟执行任务。适用于需要定期执行任务的场景。

WorkStealingPool:工作窃取线程池,在 Java 8 中引入。每个线程维护自己的任务队列,当一个线程完成了自己的任务后,可以从其他线程的任务队列中窃取任务来执行。

92. 简述一下你对线程池的理解 (如果问到了这样的问题,可以展开的说一下线程池如何用、线程池的好处、线程池的启动策略)

线程池是一种用于管理和重用线程资源的机制,它可以有效地控制并发线程数量,提高系统的性能和稳定性。

使用线程池:

通过使用线程池,我们可以将需要执行的任务提交给线程池,而不是手动创建和管理线程。线程池会自动创建、管理和调度线程来执行这些任务。通过线程池,我们可以更好地控制系统中的并发线程数量,并且可以复用线程,避免了频繁地创建和销毁线程的开销。

线程池的好处:

\1. 资源管理:线程池可以限制并发线程的数量,避免过多的线程占用系统资源,提高系统的资源利用率。

\2. 提高响应速度:通过重用已经创建的线程,线程池可以减少线程创建和销毁的开销,从而提高任务的处理速度和响应时间。

\3. 避免线程过载:线程池可以根据系统的负载情况来调整线程数量,避免因大量线程导致系统崩溃或性能下降。

\4. 提供线程管理和监控:线程池提供了统一的接口来管理和监控线程的状态,包括线程的启动、停止、暂停等操作,方便线程的管理和调试。

线程池的启动策略:

线程池的启动策略指定了创建线程池时线程的初始数量、最大数量以及线程空闲时的存活时间等参数。合理的启动策略可以根据任务负载的变化来动态地调整线程池的大小,以满足不同场景下的需求。

常见的线程池启动策略包括:

\1. FixedThreadPool:固定大小的线程池,初始化时创建指定数量的线程,并且线程数量不会改变。

\2. CachedThreadPool:缓存线程池,根据任务负载动态调整线程数量,如果有空闲线程可用,则会复用空闲线程,否则创建新的线程。

\3. ScheduledThreadPool:定时任务线程池,按照给定的时间间隔或者延迟执行任务,线程数量一般固定。

选择适当的线程池启动策略需要根据业务场景和系统要求进行评估,考虑到任务的性质、并发量、响应时间和系统资源等因素。

93. 线程池的常见参数?

corePoolSize:核心线程数。这些线程会一直存活,即使没有任务需要执行。当线程数小于corePoolSize时,即使有线程空闲,线程池也会优先创建新线程处理任务。

maximumPoolSize:最大线程数。当线程数达到这个值时,新任务会放在队列中排队等待执行。

keepAliveTime:这是多余的空闲线程的存活时间。当线程数量超过corePoolSize时,一个处于空闲状态的线程,在指定的时间后会被销毁。

unit:这是keepAliveTime的单位。

workQueue:这是任务队列,被提交但尚未被执行的任务会被放入此队列中。

threadFactory:这是表示生成线程池中工作线程的线程工厂,用于创建线程。一般用默认的即可。

handler:这是拒绝策略,表示当队列满了并且工作线程大于等于线程池的数量最大值时如何来拒绝请求执行的runnable的策略。

这些参数共同决定了线程池的行为和性能,需要根据实际的应用场景和需求进行合理的配置。

94. 线程池的工作原理?

当有任务提交给线程池时,线程池会根据内部线程的数量和任务队列的容量来决定如何处理这个任务。如果线程池中的线程都在运行,新任务就会被放在任务队列中等待。如果任务队列也满了,线程池就会根据其配置的策略来创建新的线程或拒绝任务。

线程池的参数配置包括核心线程数、最大线程数、任务队列容量等。这些参数决定了线程池的行为和性能,需要根据实际的应用场景和需求进行合理的配置。

线程池的启动策略取决于其配置。在创建线程池时,可以根据需要设置核心线程数、最大线程数、线程存活时间等参数。当有任务提交给线程池时,线程池会根据当前线程数和任务队列的情况来决定是否需要创建新的线程。如果需要创建新线程,可以根据配置决定创建多少个新线程。如果不需要创建新线程,任务就会被放在任务队列中等待。如果任务队列也满了,可以配置线程池根据需要创建新的线程或拒绝任务。

线程池的优点包括资源复用、控制并发度、任务调度和系统稳定性等。通过重复利用已存在的线程,避免了线程的创建和销毁开销,提高了系统的性能和效率。同时,通过合理的管理和调度机制,可以提高系统的稳定性和可靠性。

总的来说,线程池的工作原理是基于对线程的复用和管理,通过预先创建一定数量的线程并保存在内存中,避免了频繁地创建和销毁线程,从而提高了性能和效率。合理配置和管理线程池可以有效地解决并发编程中遇到的问题,提高系统的性能和稳定性。

95. 线程池核心线程数怎么设置呢?

分为CPU密集型和IO密集型,CPU 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出 来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦 任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空 闲时间。 IO密集型 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占 用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们 可以多配置一些线程,具体的计算方法是 : 核心线程数=CPU核心数量*2。

96. 线程池的拒绝策略有哪些?

主要有4种拒绝策略:

AbortPolicy:这是默认的拒绝策略,当任务添加到线程池中被拒绝时,它会直接抛出异常。

CallerRunsPolicy:当任务加到线程池中被拒绝时,主线程会自己去执行该任务。该策略既不会抛弃任务,也不会抛出异常,而是将某些任务退回,从而降低新任务的流量。

DiscardOldestPolicy:当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。

DiscardPolicy:当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务。。

97. 锁的优化机制了解吗?

锁的优化机制主要包括偏向锁、轻量级锁和自旋锁。

\1. 偏向锁:偏向锁是一种针对无竞争情况下的优化机制。当一个线程获取到锁后,会在对象头中的Mark Word中记录下该线程的ID,表示该对象偏向于该线程。当其他线程尝试获取该锁时,只需要检查Mark Word中的线程ID是否与当前线程ID相同,如果相同,则无需竞争,直接获取锁。这样可以避免加锁解锁的开销,提高性能。

\2. 轻量级锁:轻量级锁是一种针对短时间内有竞争的情况下的优化机制。当一个线程尝试获取锁时,如果发现该锁没有被其他线程持有,就会将锁的状态设置为轻量级锁,并将线程ID记录在锁的对象头中。如果其他线程也尝试获取该锁,会进入自旋状态,不断尝试获取锁,而不是阻塞等待。这样可以避免线程的上下文切换,提高性能。

\3. 自旋锁:自旋锁是一种针对竞争激烈的情况下的优化机制。当一个线程尝试获取锁时,如果发现该锁已经被其他线程持有,就会进入自旋状态,不断尝试获取锁,而不是阻塞等待。自旋锁适用于锁的持有时间非常短暂的情况,可以避免线程的上下文切换,提高性能。

98. 产生死锁的四个必要条件?

\1. 互斥条件:一个资源每次只能被一个线程使用 2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放 3. 不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺 4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系

99. 如何避免死锁?

死锁是多个进程或线程因为相互等待持有的资源而无法继续执行的一种情况。为了避免死锁的发生,可以采取以下几种方法:

\1. 避免循环等待(Circular Wait):确保每个进程按照相同的顺序请求资源,避免形成资源请求的环路。

\2. 按序获取资源(Ordered Resource Allocation):引入资源的全局顺序,并要求每个进程按照顺序获取资源,释放资源时也按照相同的顺序释放。

\3. 资源预留(Resource Reservation):进程在执行前先申请所有需要的资源,如果不能满足,则不申请任何资源,避免占用部分资源导致其他进程无法执行。

\4. 使用超时机制(Timeouts):给资源请求设置超时时间,在一定时间内未能获取到所需资源,则放弃当前请求并释放已获得的资源。

\5. 资源剥夺(Resource Preemption):当一个进程请求新的资源时,如果该资源被其他进程持有且无法满足当前进程的需求,可以抢占其他进程当前持有的资源。

\6. 使用死锁检测和恢复机制:定期检查系统中是否存在死锁,如果检测到死锁,通过抢占资源或终止某些进程来解除死锁状态。

\7. 合理设置锁的粒度:尽可能缩小锁的范围,避免使用过大的锁粒度,以减少并发冲突和死锁的可能性。

以上方法可以在设计系统时考虑并实施,以降低死锁发生的概率。同时,对于已经发生的死锁,需要通过分析日志和监控信息,找出造成死锁的原因,并进行相应的调整和处理。。

100. 线程安全需要保证几个基本特征? // 2第二天

原子性:原子性是指一个操作或者多个操作要么全部执行,要么都不执行。这通常是通过使用锁或者其他同步机制来保证的。

可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。这通常是通过内存屏障或者刷新指令来实现的。

有序性:有序性是指一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序。这可以通过使用锁或者其他同步机制来保证指令的有序执行。

隔离性:隔离性是指线程之间互不干扰,一个线程不能看到其他线程的私有变量,这就保证了每个线程读取的变量都是一致的。

避免死锁:死锁是指两个或多个线程无限期地等待对方释放资源的情况。为了避免死锁,需要设计程序以一种可预测的方式获取和释放资源。

101. 说一下线程之间是如何通信的?

线程之间可以通过多种方式进行通信,包括共享内存和消息传递。

共享内存:线程可以通过访问共享的内存区域来进行通信。这意味着一个线程可以将数据写入共享内存,而其他线程可以读取该数据。然而,由于多个线程同时访问共享内存可能导致竞态条件(race condition),需要使用同步机制(如互斥锁、信号量)来确保线程安全。

消息传递:线程可以通过发送和接收消息来进行通信。这种通信方式被称为消息传递或者消息队列。线程可以将消息发送到一个队列中,其他线程可以从队列中接收并处理这些消息。消息传递可以是同步的(发送者等待接收者处理完消息)或异步的(发送者不等待接收者处理完消息即可继续执行)。

在实际应用中,通常会根据具体的需求选择合适的通信方式。共享内存适用于需要高效数据共享和交互的场景,但需要注意保证线程安全;而消息传递适用于松散耦合的线程之间进行通信,通过解耦各个线程可以提高系统的可扩展性和模块化。

102. 说一下java中线程之间是如何通信的?

在Java中,线程之间可以使用以下方式进行通信:

\1. 共享变量:线程可以通过访问共享的变量或对象来进行通信。多个线程可以读取和写入相同的变量或对象,从而实现数据共享。为了保证线程安全,需要采用同步机制,如使用synchronized关键字或ReentrantLock类来确保共享变量的一致性。

\2. 等待/通知机制(wait/notify):线程可以使用wait()方法来等待某个条件的满足,同时释放锁。当其他线程满足了条件后,可以使用notify()notifyAll()方法通知正在等待的线程恢复执行。这种机制可以实现线程之间的协作和同步,常见的应用场景是生产者-消费者模型。

\3. 管道(Pipes):Java提供了管道流(PipedInputStream和PipedOutputStream)用于在线程之间传递数据。一个线程将数据写入管道的输出流,另一个线程可以从管道的输入流中读取数据。管道可以在单向或双向上进行通信。

\4. 同步队列(SynchronousQueue):它是一种特殊类型的阻塞队列,用于在线程之间传递元素。当一个线程尝试向队列中插入元素时,它会被阻塞,直到另一个线程尝试从队列中提取元素。这种机制可以实现线程之间的一对一通信。

\5. CountDownLatch和CyclicBarrier:这些是Java并发包中提供的同步工具类,用于线程之间的等待和通知。CountDownLatch允许一个或多个线程等待其他线程执行完特定操作后再继续执行。CyclicBarrier则允许一组线程相互等待,直到所有线程都满足条件后才能继续执行。

通过以上方式,Java中的线程可以在不同的场景下进行有效的通信和协作,实现并发编程的需求。

103. 引用类型有哪些?有什么区别?

在Java中,引用类型主要分为强引用、软引用、弱引用和虚引用。这四种引用类型的主要区别在于对垃圾回收机制的影响和内存中的生存时间。

强引用(Strong Reference):这是最常见的引用类型。当一个对象具有强引用时,垃圾回收器不会回收这个对象。即使JVM已经发出了OutOfMemoryError,并停止运行,只要强引用还存在,垃圾回收器就不会清理掉被强引用的对象。

软引用(Soft Reference):软引用关联的对象只有在内存不足时才会被垃圾回收器回收。也就是说,如果一个对象还有软引用,那么这个对象不会被垃圾回收,直到内存不足。软引用通常用于实现内存敏感的缓存。

弱引用(Weak Reference):弱引用也是用来描述一些还有用但并非必需的对象。对于弱引用的对象,当垃圾回收机制运行时,无论当前内存空间足够与否,都会回收被引用的对象。

虚引用(Phantom Reference):虚引用是所有引用类型中最弱的一种。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

以上四种引用类型的强度由强到弱依次为:强引用 > 软引用 > 弱引用 > 虚引用。它们各自的应用场景和目的是为了解决不同的问题,例如内存管理、缓存策略等。在实际编程中,需要根据具体需求选择合适的引用类型。

104. 说说ThreadLocal原理?

ThreadLocal是Java中的一个类,它提供了线程局部变量(thread-local variables)的实现。线程局部变量允许每个线程拥有独立的变量副本,这些变量与其它线程的变量值互不干扰。ThreadLocal的原理主要基于Java的内部类和每个线程的本地存储(ThreadLocalMap)机制。

ThreadLocal类内部维护了一个ThreadLocalMap对象,用于存储每个线程的独立变量副本。当线程首次访问线程局部变量时,它会使用一个内部的弱引用(WeakReference)将ThreadLocal对象引用与线程关联起来。这个弱引用允许垃圾回收器在必要时回收ThreadLocal对象。

当线程需要访问线程局部变量时,它首先从自己的本地存储(ThreadLocalMap)中查找该变量。如果变量不存在,该线程就会从主存储中获取变量的初始值,并将其存储到自己的本地存储中。这样,每个线程都拥有独立的变量副本,并且对变量的修改不会影响其他线程的变量副本。

ThreadLocal的主要用途是解决多线程中的数据共享问题,避免数据不一致性和线程安全问题。通过使用ThreadLocal,可以将数据绑定到执行任务的线程上,从而避免多个线程之间的数据共享和竞争条件。

105. 多线程有什么用?

多线程在计算机科学中是一个重要的概念,主要应用于并发程序设计。多线程的作用主要体现在以下几个方面:

提高程序运行效率:通过同时处理多个任务,多线程可以显著提高程序的运行效率。在多核CPU的系统中,多线程能充分利用CPU资源,减少程序等待时间。

提高用户体验:在许多应用程序中,如网页浏览器或图形渲染工具,用户界面需要在程序其它部分处理任务的同时保持响应。通过在用户界面线程中运行事件分发器,可以确保用户界面始终保持响应,从而提高用户体验。

并发执行任务:多线程可以让程序并发执行多个任务。例如,一个程序可以同时进行文件下载、图片处理和音频播放等多个任务,而不需要等待一个任务完成后再开始下一个任务。

充分利用硬件资源:在多核CPU或多线程硬件的系统中,多线程能充分利用这些资源,避免某些资源空闲而其他资源还在忙碌的情况,从而提高硬件利用率。

简化复杂任务的处理:对于一些复杂的任务,如大数据处理、网络爬虫等,可以将这些任务分解为多个子任务,每个子任务由一个线程处理。这样可以将复杂的任务简化,提高代码的可读性和可维护性。

需要注意的是,多线程也可能会带来一些问题,如线程间的同步、数据竞争、死锁等问题。因此,在使用多线程时需要充分考虑这些问题,并采取相应的措施来避免或解决这些问题。

106. 什么是阻塞队列?阻塞队列的实现原理是什么?

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空;当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。

阻塞队列的实现原理是基于线程之间的通信。当队列满时,生产者线程会被阻塞,直到队列不满;当队列为空时,消费者线程会被阻塞,直到队列非空。这种阻塞机制通过条件变量或者wait/notify机制实现,保证了生产者和消费者线程之间的同步。

107. 如何使用阻塞队列来实现生产者-消费者模型?

使用阻塞队列来实现生产者-消费者模型的基本步骤如下:

创建阻塞队列:选择一个阻塞队列实现,例如ArrayBlockingQueue、LinkedBlockingQueue等。这些类是阻塞队列的常用实现,提供了线程安全的数据结构。

创建生产者和消费者线程:创建生产者线程和消费者线程。这些线程通常继承自Thread类或者实现Runnable接口。

实现生产者和消费者逻辑:生产者:生成一定量的数据并存放到阻塞队列中。当队列满时,生产者线程会被阻塞,直到队列不满为止。消费者:从阻塞队列中取出数据并处理。当队列为空时,消费者线程会被阻塞,直到队列非空为止。

保证同步和互斥:在生产者和消费者线程之间需要保证同步和互斥,避免出现数据竞争和死锁等问题。可以使用synchronized关键字或者Lock对象来实现互斥访问。

控制生产者和消费者的速度:可以使用一些方法来控制生产者和消费者的速度,避免生产者生成数据的速度超过消费者处理的速度,从而导致队列溢出。例如,可以使用节流阀(Throttle)机制来控制生产者的速度。

处理阻塞和异常情况:在生产者和消费者线程中,需要处理阻塞和异常情况。例如,当队列满时,生产者线程会被阻塞,此时需要处理异常情况;当队列为空时,消费者线程会被阻塞,此时也需要处理异常情况。

108. 什么是多线程中的上下文切换?

在多线程编程中,上下文切换(Context Switching)是指当一个线程(例如,一个执行线程)被暂停,以便让另一个线程获得执行机会时,系统保存和恢复这个线程的执行环境的过程。这种保存和恢复通常发生在操作系统级别,用于协调多个线程在多核或多处理器系统中的执行。

在上下文切换过程中,操作系统会保存当前线程的CPU寄存器中的值,并加载下一个线程的这些值,以便该线程可以在其上次离开的地方继续执行。这个过程涉及到很多细节,包括线程的状态管理、内存管理以及可能的资源同步问题。

上下文切换并不是多线程编程中的唯一挑战。还有其他一些关键问题,例如线程同步(例如,避免竞态条件和死锁)、资源共享和数据一致性等。

值得注意的是,上下文切换并不是多线程编程的必然部分,而是由操作系统管理的。不同的操作系统和硬件平台可能会有不同的上下文切换机制和性能影响。在某些情况下,过度频繁的上下文切换可能会导致系统性能下降,因为这会增加额外的CPU时间来保存和恢复线程的执行环境。

109. 乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

乐观锁和悲观锁是两种常见的并发控制方法,用于处理多用户同时访问数据库时可能出现的数据冲突问题。

悲观锁(Pessimistic Locking)

悲观锁在数据被修改时锁定数据,以防止其他用户进行修改。这种策略假定冲突很可能发生,因此采取了保守的锁定策略。

实现方式:

行级锁:当对数据库表中的某一行进行操作时,直接锁定这一行,直到操作完成。这避免了死锁,但可能会降低并发性能。

表级锁:当对整个表进行操作时,锁定整个表,阻止其他用户对表进行操作。这种方式开销小,但并发性能较差。

排他锁:这是行级锁和表级锁的一种变种,只允许被锁定的数据被读取或修改。

乐观锁(Optimistic Locking)

乐观锁则相反,假设冲突不太可能发生,因此不会在数据被修改时立即锁定。而是在提交更新时检查是否有冲突发生。

实现方式:

版本号控制:为每个数据行增加一个版本号字段。每次更新时,检查版本号是否发生变化。如果发生变化,说明有其他用户修改了该数据,因此拒绝本次更新。这种方式不需要数据库表级别的锁,可以提高并发性能。

时间戳控制:使用时间戳字段来代替版本号字段。同样,在更新数据时,检查时间戳是否发生变化。如果发生变化,说明有其他用户修改了该数据,因此拒绝本次更新。

CAS(Compare and Swap)算法:在更新数据前,先获取当前数据的值,然后与期望的值进行比较。如果当前值与期望值相同,则更新数据;否则拒绝更新。这种方式可以避免死锁,但需要额外的原子操作来保证数据的一致性。

总的来说,悲观锁和乐观锁各有优缺点,需要根据具体的应用场景和需求来选择使用哪种策略。

悲观锁和乐观锁是两种常见的数据库并发控制策略,它们在处理数据库并发访问时采用了不同的思路。

悲观锁假定数据在被并发访问时很可能会产生冲突或被破坏,因此在数据被访问或修改时会对数据加锁,以防止其他事务同时进行修改。在悲观锁的策略下,一旦数据被锁定,其他事务就不能进行修改,直到锁被释放。这种策略可以确保数据的完整性和一致性,但也可能导致事务的等待和降低数据库的并发性能。

相比之下,乐观锁则采用更加乐观的态度,认为数据冲突发生的概率很小,因此在数据提交更新时才会进行检查是否有冲突发生。通常,乐观锁通过版本控制或时间戳来实现。在乐观锁的策略下,当一个事务试图更新数据时,会检查数据是否被其他事务修改过。如果数据没有被修改过,事务就可以正常更新数据;如果数据已经被其他事务修改过,则事务会失败并返回给用户错误信息,让用户决定如何处理冲突。

​ 总的来说,悲观锁和乐观锁都是为了解决数据库并发访问问题而采取的不同策略。悲观锁更注重数据的完整性,通过加锁来防止冲突;而乐观锁则更加注重性能和并发性,通过检查冲突来避免不必要的等待和失败。在实际应用中,可以根据具体的需求和场景选择合适的锁策略。

110. 讲一下什么是Spring?

Spring是一个开源的Java框架,用于构建企业级应用程序。它提供了一种轻量级的、非侵入式的开发方式,通过依赖注入和面向切面编程等特性,简化了Java应用程序的开发过程。

Spring框架的核心特性包括:

\1. 控制反转(IoC):通过IoC容器管理对象的创建和依赖关系,将对象的创建和依赖解耦,提高了代码的灵活性和可维护性。

\2. 依赖注入(DI):通过依赖注入,将对象之间的依赖关系交给容器来管理,减少了代码之间的耦合度,提高了代码的可测试性。

\3. 面向切面编程(AOP):通过AOP可以将与业务逻辑无关的横切关注点(如日志、事务管理等)从业务逻辑中分离出来,提高了代码的模块化和可维护性。

\4. 数据访问支持:Spring提供了对各种数据访问技术的支持,包括JDBC、ORM框架(如Hibernate、MyBatis)、NoSQL数据库等。

\5. Web开发支持:Spring提供了对Web开发的支持,包括MVC框架、RESTful服务等。

\6. 安全性支持:Spring提供了对安全性的支持,包括认证、授权等。

\7. 容器管理:Spring提供了一个容器,用于管理对象的生命周期和依赖关系。

总的来说,Spring框架通过提供一系列的模块化特性和功能,简化了Java应用程序的开发过程,提高了代码的可维护性和可测试性。

111. 你们项目中为什么使用Spring框架?

轻量:Spring 是轻量的,基本的版本大约2MB。控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。 容器:Spring 包含并管理应用中对象的生命周期和配置。 MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务 (JTA)。 异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛 出的)转化为一致的unchecked 异常。

112. Autowired和Resource关键字的区别?

支持的参数数量:@Autowired只支持一个参数,而@Resource可以设置最多7个参数。

依赖注入的用法:@Autowired既支持通过构造器注入,也支持通过setter方法注入和属性注入。而@Resource只支持通过setter方法注入和属性注入。

IDE提示:当使用@Autowired注解进行Mapper对象注入时,IDE可能会给出错误提示,而使用@Resource注解则不会。

默认的装配策略:@Autowired是按照类型(byType)进行装配的,如果找不到匹配的bean或者存在多个匹配的bean,会抛出异常。如果需要按照名称进行装配,可以结合@Qualifier注解一起使用。而@Resource默认是按照名称(byName)进行装配的,如果找不到匹配的bean或者存在多个匹配的bean,也会抛出异常。

性能:由于@Resource默认是按照名称进行装配的,因此在默认情况下它的性能可能会稍微优于@Autowired,因为它是通过指定名称来定位和注入依赖的。

总的来说,这两个注解都可以用于实现依赖注入,但它们在参数数量、依赖注入的用法、IDE提示、默认装配策略和性能方面存在一些差异。具体使用哪一个注解,应该根据项目的具体需求和场景来决定。

113. SpringMVC常用的注解有哪些?

@Controller:这个注解用于标记一个类为 Spring MVC 的控制器。使用这个注解的类是一个 Spring Bean,并且可以被 Spring 容器自动扫描和实例化。

@RequestMapping:这个注解用于映射 Web 请求到特定的处理方法。它可以用于类或方法上,用于指定处理请求的 URL 路径和 HTTP 方法(如 GET、POST 等)。

@GetMapping、@PostMapping、@PutMapping、@DeleteMapping:这些注解是 @RequestMapping 的快捷方式,用于指定特定的 HTTP 方法。

@PathVariable:这个注解用于获取 URL 路径中的参数值。它可以将 URL 中的某个部分提取出来,并将其作为方法的参数。

@RequestParam:这个注解用于获取请求参数的值。它可以用于获取表单数据、查询参数和请求体中的数据。

@ResponseBody:这个注解用于将方法的返回值直接写入 HTTP 响应体中,而不是将其解析为视图。它通常与返回 JSON 或其他数据类型的场景一起使用。

@RequestBody:这个注解用于将请求体中的数据绑定到方法的参数上。它通常与 POST 和 PUT 请求一起使用,用于接收 JSON 或其他数据格式的请求体。

@Autowired:这个注解用于自动装配 Spring 容器中的 Bean。它可以用于字段、构造函数或方法上,以便自动注入依赖项。

@Component、@Service、@Repository:这些注解用于标记类为特定类型的 Bean,例如组件(基本的 Bean)、服务(业务逻辑)或存储库(数据访问)。这样可以使配置更加清晰,并且可以使用 Spring 的组件扫描功能自动发现和注册 Bean。

这些注解提供了强大的功能,使得开发人员可以更加高效地构建 Web 应用程序。通过使用这些注解,可以减少大量的配置工作,并且使代码更加简洁和易于维护。

114. Spring AOP和AspectJ AOP有什么区别?

Spring AOP和AspectJ AOP是两种不同的面向切面编程(AOP)技术,它们之间存在一些关键的区别。

面向的对象不同:Spring AOP是针对Spring框架的AOP实现,它依赖于Spring框架进行实施和管理。而AspectJ是一个独立的AOP框架,不依赖于任何框架或容器,可以应用于Java应用程序、Java EE应用程序、OSGi环境、Android环境等。

织入方式不同:Spring AOP默认使用运行时织入(runtime weaving),在运行时动态地在目标对象上创建切面。而AspectJ提供了三种类型的织入方式,包括编译期织入(Compile-time weaving)、编译后织入(Post-compile weaving)和加载时织入(Load-time weaving)。其中,编译期织入是AspectJ的主要织入方式,可以在编译期间将切面代码与目标代码编译到一起,因此不需要代理,并且可以提供比Spring AOP更快的性能。

性能不同:由于Spring AOP使用动态代理来实现,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,因此在性能上不如AspectJ。而AspectJ由于在编译期间将切面代码与目标代码编译到一起,不需要额外的代理,因此可以提供更好的性能。

实现方式不同:Spring AOP是基于动态代理的AOP实现,通过在运行时动态地创建代理对象来实现切面。而AspectJ是基于静态代理的AOP实现,通过在编译期间将切面代码与目标代码编译到一起来实现切面。

适用范围不同:Spring AOP主要用于简化代码,通过抽象出一些公共逻辑,并将其封装在切面中,以减少代码的重复性。而AspectJ不仅提供了基本的AOP功能,还支持一些额外的特性,如织入(weaving)前后连接点、类型织入等。

总结来说,Spring AOP和AspectJ AOP各有优缺点,选择哪种技术取决于具体的应用场景和需求。如果需要与Spring框架集成的AOP解决方案,或者需要简化代码、减少重复性,并且对性能要求不是非常高,可以选择使用Spring AOP。如果需要一个独立于任何框架或容器的AOP解决方案,或者需要更好的性能、更丰富的功能,可以选择使用AspectJ AOP。

115. 在Spring框架中什么是通知呢?有哪些类型呢?

在Spring框架中,通知(Notification)是指在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP(面向切面编程)框架触发的代码段。通知有五种类型:

前置通知(Before Advice):在方法执行前被调用。

后置通知(After Advice):在方法执行之后被调用,无论方法是否成功执行。

返回后通知(After Returning Advice):仅当方法成功完成后执行的通知。

抛出异常后通知(After Throwing Advice):在方法抛出异常退出时执行的通知。

环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型,可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

在Spring AOP中,这些通知可以应用于切面,以便在程序的特定点(例如方法调用之前或之后)自动执行额外的逻辑。这种技术可以使代码更干净、更简洁,并且可以用来处理许多常见任务,例如日志记录、事务管理、安全检查等。。

116. 说说你对Spring的IOC是怎么理解的?

Spring的IoC容器是一个用于管理Bean的对象,它负责创建、配置、组装和生命周期管理这些Bean。通过使用IoC容器,开发人员可以更方便地组织和管理应用程序中的对象及其相互关系,而不需要依赖硬编码的代码或显式的对象创建。

IoC容器的核心思想是将对象的创建和配置的主动权从程序代码中移交给容器,由容器来负责对象的生命周期和依赖关系的管理。通过配置文件或注解的方式,开发人员可以指定对象之间的依赖关系,以及对象的创建和销毁时机。IoC容器会自动解析这些配置,并按照预定的规则创建和管理对象。

IoC容器的出现解决了许多传统编程模式中存在的问题,如硬编码的代码和紧密耦合的对象关系。它使得应用程序中的对象更加灵活、可复用,并且可以方便地进行组装和扩展。此外,IoC容器还提供了许多高级功能,如依赖注入、事务管理、安全性检查等,使得开发人员可以更加专注于业务逻辑的实现,而不需要花费过多的精力在对象的管理和配置上。

117. Spring框架中都用到了哪些设计模式?

工厂模式(Factory Pattern):Spring使用工厂模式来创建和管理Bean对象。例如,BeanFactory和ApplicationContext都是工厂模式的实现,用于创建、配置和管理Bean。

单例模式(Singleton Pattern):在Spring中,通过IoC容器来管理Bean,每个Bean默认都是单例的。这意味着在整个应用程序中,每个Bean都只有一个实例。

代理模式(Proxy Pattern):Spring AOP使用代理模式来实现面向切面编程。通过代理模式,可以拦截方法的调用,实现跨切面的编程,如日志记录、事务管理等。

模板方法模式(Template Method Pattern):用于解决代码重复的问题。例如,Spring中的JdbcTemplate、HibernateTemplate等都是模板方法模式的实现,它们封装了数据库操作的公共代码,使得开发人员可以更方便地编写重复性较少的代码。

观察者模式(Observer Pattern):Spring的事件驱动模型就是观察者模式的典型应用。通过观察者模式,一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并被自动更新。

适配器模式(Adapter Pattern):在Spring MVC中,控制器(Controller)可以使用适配器模式来适配不同的请求和响应格式。

装饰器模式(Decorator Pattern):通过装饰器模式,可以在运行时动态地给对象添加额外的职责。Spring中的AOP就是通过装饰器模式来实现的。

以上是Spring框架中常用的一些设计模式,它们的使用使得Spring成为一个功能强大、灵活且易于扩展的框架。

118. Spring 框架中的单例 Bean 是线程安全的么?

是的,Spring 框架中的单例 Bean 是线程安全的。Spring 框架通过单例模式来管理 Bean,这意味着在整个应用程序中,每个 Bean 只有一个实例。当多个线程同时访问同一个 Bean 时,Spring 框架会确保每个线程都获得该 Bean 的一个独立副本,从而避免了线程安全问题。

在 Spring 框架中,每个 Bean 都有一个唯一的标识符,当 Bean 被注入到其他 Bean 中时,会根据 Bean 的标识符来确定注入的是哪个 Bean 的实例。由于每个 Bean 实例都有唯一的标识符,因此每个线程都可以访问到自己的 Bean 副本,避免了线程安全问题。

当然,如果你的 Bean 中有共享变量或状态,你需要确保正确地处理这些共享变量的线程安全性。但是,Spring 框架本身已经确保了单例 Bean 的线程安全性。。

119. 说说事务的传播级别?

事务的传播级别是指当一个事务在运行过程中遇到不同的数据库事务时,根据事务的隔离级别和已经发生的情况,将如何传播和影响其他事务。

在Spring框架中,事务的传播级别有以下几种:

PROPAGATION_REQUIRED:这是默认的传播级别。如果当前上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。这个级别通常能满足处理大多数的业务场景。

PROPAGATION_SUPPORTS:如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。这个级别通常用于处理那些并非原子性的非核心业务逻辑操作。

PROPAGATION_MANDATORY:这个级别的事务要求上下文中必须要存在事务,否则就会抛出异常。

此外,还有PROPAGATION_NEVER、PROPAGATION_NESTED等传播级别。

不同的事务传播级别会导致不同的传播行为,选择适合的事务传播级别是保证数据一致性和并发性能的关键。

120. Spring 事务实现方式?

\1. 编程式事务管理:在代码中调用事务管理相关的方法,如commit()、rollback()等。

\2. 基于TransactionProxyFactoryBean的声明式事务管理:通过配置TransactionProxyFactoryBean来实现声明式事务管理。

\3. 基于@Transactional的声明式事务管理:通过在方法或类上添加@Transactional注解来实现声明式事务管理。

\4. 基于Aspectj AOP配置事务:使用Aspectj AOP配置事务,通过在切面中定义事务的行为来实现事务管理。

\5. 编程式事务管理中的transactionTemplate:使用Spring提供的TransactionTemplate来进行编程式事务管理。

121. Spring框架的事务管理有哪些优点?

简化开发:Spring框架提供了声明式事务管理,这意味着开发者只需要通过简单的配置就可以定义事务的行为,而无需手动编写事务管理的代码。这大大简化了开发者的开发工作,并减少了代码的复杂度。

保证数据一致性:通过事务管理,可以确保在事务完成前提交的所有更改都能正确地保存到数据库中。如果事务中的多个更改失败,事务管理器将回滚事务,从而确保数据的一致性。

支持多种持久层技术:Spring的事务管理可以与多种持久层技术(如JDBC、Hibernate、JPA等)一起使用,这使得应用程序的代码与底层技术解耦,提高了代码的可维护性和可扩展性。

支持声明式事务处理:通过声明式事务管理,开发者只需要通过配置就可以完成对事务的管理,而无需手动编程。这大大简化了事务管理的复杂性,并提高了代码的可读性。

方便集成各种优秀框架:Spring框架不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持。

支持简单、可测试和松耦合等特点:Spring框架不仅适用于服务器端开发,也可以应用于任何Java应用的开发中。它具有简单、可测试和松耦合等特点,这使得Spring不仅可以用于开发复杂的业务逻辑,也可以用于构建可维护、可测试的代码。

支持分布式事务:Spring框架通过JTA(Java Transaction API)支持分布式事务,可以在多个数据库或资源之间进行事务的控制和协调。

性能优化:Spring框架使用缓存和批处理等技术来提高数据库操作的性能,减少了数据库的访问次数。

异常处理:Spring框架提供了统一的异常处理机制,可以在事务发生异常时进行回滚操作,避免了数据的损坏。

综上所述,Spring框架的事务管理具有简化开发、保证数据一致性、支持多种持久层技术、支持声明式事务处理、方便集成各种优秀框架、支持简单、可测试和松耦合等特点、支持分布式事务、性能优化、异常处理等优点。

122. 事务三要素是什么?

事务的三要素是原子性、一致性和隔离性。

原子性:事务作为一个整体执行,要么全部完成,要么全部回滚,不会出现中间状态。

一致性:事务必须使数据库从一个一致性状态变换到另一个一致性状态。

隔离性:在并发控制过程中,一个事务的执行不能被其他事务所干扰,每个事务必须与其他事务隔离。

这些要素可以确保交易的完整性和可靠性,避免数据的不一致和冲突。

123. 事务注解的本质是什么?

事务注解的本质是元数据,它在运行时被事务基础设施读取并消费,用于配置bean的事务行为。具体来说,它主要有以下两方面功能:

表明该方法要参与事务。

配置相关属性来定制事务的参与方式和运行行为。

简单来说,事务注解就是一种标记,它在运行时被事务基础设施解析并使用,用于指导事务如何执行。

124. 什么是MyBatis?

MyBatis是一个Java的持久层框架,它通过配置文件和注解方式将接口与SQL语句绑定,使得开发人员能够更加方便地访问数据库。

MyBatis的核心思想是将SQL语句与Java对象进行映射,通过映射配置文件或注解方式将SQL语句与Java对象关联起来,从而实现数据持久化操作。

与传统的JDBC相比,MyBatis简化了JDBC的代码,使得代码更加简洁、可读性更好、易于维护。同时,MyBatis提供了丰富的映射类型和动态SQL功能,支持自定义SQL、存储过程以及高级映射等,使得开发人员能够更加灵活地操作数据库。

总的来说,MyBatis是一个优秀的持久层框架,它使得开发人员能够更加高效地访问数据库,并且提供了丰富的功能和灵活的配置选项。

125. 说说MyBatis的优点和缺点?

MyBatis的优点包括:

简单易学:MyBatis本身很小且简单,易于学习和使用。

解除SQL与程序代码的耦合:通过提供DAL层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。

提供映射标签,支持对象与数据库的ORM字段关系映射:MyBatis提供了映射标签,支持对象与数据库的ORM字段关系映射,提高了可维护性。

提供XML标签,支持编写动态SQL:MyBatis提供了XML标签,支持编写动态SQL语句(XML中使用if、else)。

与各种数据库兼容:因为MyBatis是通过JDBC来连接数据库的,只要JDBC支持的数据库MyBatis都支持。

与Spring框架的集成:MyBatis能与Spring框架很好地集成。

缺点:

SQL语句的编写工作量较大:尤其是字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。

SQL语句依赖于数据库:导致数据库移植性差,不能随意更换数据库。

框架比较简陋:功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据库修改。

综上所述,MyBatis的优点主要集中在简单易学、解除SQL与程序代码的耦合等方面,而缺点则主要表现在SQL语句的编写工作量大、数据库移植性差等方面。在实际使用中,需要根据具体需求和场景进行选择和权衡。。

126. Mybatis是如何进行分页的?分页插件的原理是什么?

MyBatis可以通过分页插件来进行分页处理,常见的分页插件有PageHelper和Mybatis-Plus等。这些插件通过拦截MyBatis的SQL语句,在SQL语句中添加分页条件来实现分页功能。

以PageHelper为例,其分页插件的原理如下:

拦截器(Interceptor):PageHelper使用MyBatis的拦截器机制,对SQL语句进行拦截。通过拦截器,可以获取到原始的SQL语句。

自定义分页参数:在分页插件中,通常需要提供一个自定义的分页参数类,用于存储分页相关的信息,如当前页码、每页显示数量等。

修改SQL语句:在拦截到SQL语句后,根据自定义的分页参数,对SQL语句进行修改。常见的修改方式是在SQL语句中添加LIMIT或TOP等关键字,以实现分页功能。

返回结果集:在执行修改后的SQL语句后,将结果集返回给调用者。

总的来说,MyBatis分页插件的原理是通过拦截SQL语句,根据分页参数对SQL语句进行修改,然后返回结果集。这种方式可以方便地实现分页功能,并且不需要手动编写复杂的分页逻辑。

127. Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

MyBatis 是一个优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。在 MyBatis 中,你可以使用映射配置文件或注解来定义 SQL 语句和 Java 对象之间的映射关系。

MyBatis 将 SQL 执行结果封装为目标对象并返回的过程主要涉及以下步骤:

结果映射定义:在 MyBatis 的映射文件中,你可以通过 元素来定义 SQL 查询结果与 Java 对象之间的映射关系。这个映射关系可以是基于列名和属性名的直接映射,也可以是复杂的嵌套映射。

执行 SQL 查询:当 MyBatis 执行 SQL 查询时,它会将查询结果集返回给 MyBatis 的结果集处理器。

结果集处理:MyBatis 的结果集处理器会根据映射定义,将结果集中的数据逐行提取并转换成 Java 对象。

返回结果:最后,MyBatis 将转换后的 Java 对象集合返回给调用者。

MyBatis 支持多种映射形式,以下是一些常见的映射形式:

一对一映射:一个 Java 对象对应数据库中的一条记录。这种映射通常用于将单条记录从数据库中检索出来。

一对多映射:一个 Java 对象对应数据库中的多条记录。这种映射通常用于将多条记录从数据库中检索出来,并将它们存储在一个 Java 对象中。

多对一映射:多个 Java 对象对应数据库中的一条记录。这种映射通常用于将一个 Java 对象列表作为参数传递给数据库更新或插入操作。

多对多映射:多个 Java 对象对应数据库中的多条记录。这种映射通常用于建立复杂的关联关系,例如在一个订单表和订单明细表之间建立关联关系。

通过这些映射形式,MyBatis 可以灵活地处理各种复杂的数据库操作和数据转换需求。

128. MyBatis实现一对一有几种方式?具体怎么操作的?

MyBatis实现一对一关系主要有两种方式:

a) 使用单表操作:

这种方式将一对一的关系视为一个整体,将两个对象的信息存储在同一张表中。通常使用两个字段来唯一标识这个整体,例如用户表中的user_id和address_id。

在MyBatis的映射文件中,可以使用元素来定义这种映射关系。例如,在一个用户地址的查询中,可以定义一个resultMap,将user_id和address_id作为外键关联,将查询结果映射到一个UserAddress对象中。

b) 使用两张表操作:

这种方式将一对一的关系拆分成两个独立的关系,每个对象都有自己的表。例如,用户表和地址表可以通过user_id字段进行关联。

在MyBatis的映射文件中,可以使用元素来定义这种映射关系。例如,在一个用户查询中,可以定义一个resultMap,使用元素将查询结果映射到一个User对象中,同时通过外键关联地址信息。

具体操作方法如下:

编写SQL语句:根据业务需求编写相应的SQL语句,用于查询或更新数据。

配置映射文件:在MyBatis的映射文件中,使用元素定义SQL查询结果与Java对象之间的映射关系。对于一对一的关系,可以使用元素指定主键列和Java对象的属性名,使用元素指定其他列和Java对象的属性名。

编写接口和映射器文件:根据业务需求编写相应的接口和映射器文件,用于执行SQL语句并将结果返回给调用者。在映射器文件中,使用元素指定要执行的SQL语句和输入参数类型,使用元素指定查询结果的映射关系。

调用接口方法:在应用程序中调用相应的接口方法,传递参数并获取查询结果。MyBatis会自动将查询结果封装为目标对象并返回给调用者。

需要注意的是,在实际应用中,需要根据具体的业务需求和数据库设计来选择适合的一对一实现方式。同时,还需要考虑性能和数据一致性的问题,确保数据库操作的高效性和正确性。

129. Mybatis是否支持延迟加载?如果支持,它的实现原理是什 么?

是的,MyBatis 支持延迟加载。延迟加载是一种优化技术,用于在真正需要数据时才进行加载,而不是在查询时就加载所有数据。这可以提高查询性能,特别是对于大量数据的场景。

MyBatis 的延迟加载实现原理如下:

懒加载:MyBatis 通过在映射文件中使用 元素的 lazyLoading 属性来启用延迟加载。当设置为 lazyLoading=“true” 时,MyBatis 会将数据加载延迟到真正需要时才进行。

使用代理模式:为了实现延迟加载,MyBatis 使用代理模式。它会在运行时动态创建一个代理对象,该对象实现了接口并重写了方法。在方法被调用时,代理对象会检查是否已经加载了数据,如果没有,则会执行 SQL 查询并将数据加载到内存中。

使用 Hibernate 的特性和类库:MyBatis 的延迟加载功能依赖于 Hibernate 的特性和类库。Hibernate 提供了一些工具和类,如 Hibernate.initialize() 方法,用于在运行时动态加载数据。MyBatis 利用这些工具和类来实现延迟加载。

需要注意的是,虽然延迟加载可以提高性能,但它也有一些限制和注意事项。例如,如果关联的实体没有被访问或引用,则可能会发生 LazyInitializationException 异常。因此,在使用延迟加载时,需要谨慎处理关联关系和数据加载时机。

130. 为什么要用SpringBoot?

简化开发:Spring Boot通过自动配置和约定大于配置的方式,简化了Spring应用的搭建和开发过程。它使得编码简单,配置简单,部署简单,监控简单,让开发人员有更多关注业务的时间,提高开发效率。

快速构建独立运行的应用:Spring Boot是一个独立的运行环境,内嵌了各种Servlet容器,如Tomcat、Jetty等,不再需要打成war包部署到容器中,只需打成一个可执行的jar包就能独立运行。所有的依赖包都在一个jar包内,简化了应用的部署和运维。

简化配置:Spring Boot使用习惯优于配置的理念,让项目快速运行起来。它提供了spring-boot-starter-web等启动器,这些启动器会自动依赖其他组件,简少了maven的配置。同时,Spring Boot能根据当前类路径下的类、jar包来自动配置bean,如添加一个spring-boot-starter-web启动器就能拥有web的功能,无需其他配置。

丰富的应用监控:Spring Boot提供一系列端点可以监控服务及应用,做健康检测。这些功能可以帮助开发人员更好地了解应用的运行状况,以便及时发现和解决问题。

与Spring Cloud集成:Spring Cloud是一系列框架的有序集合,它利用Spring Boot的便利性巧妙地简化了分布式系统基础设施的开发。Spring Boot与Spring Cloud的集成使得微服务的开发和治理变得更加容易。

动态语言和流行技术的支持:随着Ruby、Node.js等动态语言的流行,Java的开发变得笨重。而Spring Boot的出现使得Java开发更加轻便,同时它对第三方技术集成的难度也大大降低。

综上所述,Spring Boot的优势主要表现在简化开发、快速构建独立运行的应用、简化配置、丰富的应用监控、与Spring Cloud集成以及对动态语言和流行技术的支持等方面。这些优点使得Spring Boot成为Java开发者在构建企业级应用时的首选。

131. Spring Boot 的核心注解是哪个?它主要由哪几个注解组成 的?

Spring Boot的核心注解是@SpringBootApplication,它也是启动类使用的注解,主要包含了3个注解:

@SpringBootConfiguration:它组合了@Configuration注解,实现配置文件的功能。

@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项。

@ComponentScan:Spring组件扫描,可自动发现和装配一些Bean。

总的来说,Spring Boot的核心注解主要由以上3个注解组成,它们共同实现了Spring Boot的自动配置和简化开发等功能。

132. 运行Spring Boot有哪几种方式?

\1. 使用IDE运行:可以使用集成开发环境(IDE)如IntelliJ IDEA、Eclipse等直接运行Spring Boot应用。在IDE中配置好项目依赖和启动配置后,可以通过点击运行按钮或执行相应的快捷键来启动应用。

\2. 使用Maven命令运行:如果项目使用了Maven作为构建工具,可以使用Maven命令来运行Spring Boot应用。在项目根目录下执行mvn spring-boot:run命令即可启动应用。Maven会自动检测并下载所需的依赖,并启动应用程序。

\3. 使用Java命令运行:将Spring Boot应用打包成可执行的JAR文件后,可以使用Java命令来运行应用。命令格式为java -jar your-application.jar,其中your-application.jar是可执行的JAR文件名。

\4. 使用Spring Boot Maven插件运行:可以在项目的pom.xml文件中添加Spring Boot Maven插件,然后使用命令mvn spring-boot:run来运行Spring Boot应用。这样可以方便地进行热部署和调试。

\5. 使用Docker容器运行:如果使用Docker来管理应用程序的部署,可以将Spring Boot应用打包成Docker镜像,并在Docker容器中运行。可以使用Docker命令或Docker Compose来管理和运行容器。

这些是常见的运行Spring Boot应用的方式,每种方式都有其特点和适用场景。根据项目需求和开发环境选择合适的方式来运行Spring Boot应用。。

133. 如何在Spring Boot启动的时候运行一些特定的代码?

实现Spring的ApplicationRunner或者CommandLineRunner接口,并重写其中的run方法 。

134. Spring Boot 需要独立的容器运行吗? // 复第一天

Spring Boot 可以不需要独立的容器运行。Spring Boot 本身并不需要独立的容器来运行,而是使用了内置的容器,如 Tomcat、Jetty 或 Undertow。这意味着 Spring Boot 应用程序可以直接在标准的 Java 虚拟机(JVM)上运行,而不需要额外的容器。

135. Spring Boot中的监视器是什么?

Spring Boot中的监视器是一组用于监视应用程序性能和运行状况的工具和指标。Spring Boot提供了许多内置的监视器,可以帮助开发者实时了解应用程序的各种指标。监视器可以监控应用程序中的各种参数,例如内存使用情况、线程数、请求处理时间等,并将这些信息提供给开发者,以便他们可以对应用程序进行优化和调整。

监视器可以通过各种方式提供信息,例如通过Web界面、控制台输出、日志文件等。Spring Boot提供了Actuator模块,这是一个专门用于监视和管理的模块。通过Actuator,开发者可以轻松地访问应用程序的各种信息,例如健康状况、审计信息、配置信息等。

监视器在Spring Boot中具有重要的作用,可以帮助开发者及时发现和解决应用程序中的问题,提高应用程序的性能和稳定性。同时,监视器也可以帮助开发者更好地了解应用程序的运行状况,从而进行适当的调整和优化。

136. 如何使用Spring Boot实现异常处理?

使用@ControllerAdvice注解:可以创建一个带有@ControllerAdvice注解的类,用于全局处理应用程序中的异常。在这个类中,可以定义各种异常处理方法,并使用@ExceptionHandler注解来指定要处理的异常类型。

使用@RestControllerAdvice注解:如果需要将异常处理结果直接返回给客户端,可以使用@RestControllerAdvice注解代替@ControllerAdvice注解。这样处理方法的返回值将自动转换为JSON格式。

使用@ExceptionHandler注解:可以在Controller类中的具体方法上使用@ExceptionHandler注解来处理特定的异常。这样可以针对某个具体的请求或操作进行异常处理。

自定义异常类和异常处理器:可以创建自定义的异常类,继承自Exception类,并创建相应的异常处理器。在异常处理器中,可以定义异常处理逻辑并返回合适的响应信息。

137. SpringBoot 实现热部署有哪几种方式?

模板热部署:在Spring Boot中,模板引擎的页面默认是开启缓存的,如果修改了页面的内容,则刷新页面是得不到修改后的页面的。因此可以在application.properties中关闭模版引擎的缓存。

使用调试模式Debug实现热部署:这是最简单最快速的一种热部署方式,运行系统时使用Debug模式,无需装任何插件即可。但此方式无发对配置文件,方法名称改变,增加类及方法进行热部署,使用范围有限。

Spring Boot DevTools:在Spring Boot项目中添加spring-boot-devtools依赖即可实现页面和代码的热部署。spring-boot-devtools默认关闭了模版缓存,如果使用这种方式不用单独配置关闭模版缓存。

Spring Loaded:此方式与Debug模式类似,适用范围有限,但是不依赖于Debug模式启动,通过Spring Loaded库文件启动,即可在正常模式下进行实时热部署。

JRebel:JRebel是Java开发最好的热部署工具,对Spring Boot提供了极佳的支持,JRebel为收费软件,试用期14天。

138. 如何理解 Spring Boot 配置加载顺序?

Spring Boot 的配置加载顺序遵循一定的规则,这些规则决定了不同配置文件和配置属性的优先级。以下是 Spring Boot 配置加载顺序的主要规则:

先加载 JAR 包外的配置文件,再加载 JAR 包内的配置文件:这意味着在 Spring Boot 项目打包之前,可以在项目根目录下放置一些全局的配置文件,例如 application.properties 或 application.yml,这些文件将被优先加载。而打包后的 JAR 包内的配置文件将覆盖这些全局配置。

先加载 config 目录内的配置文件,再加载 config 目录外的配置文件:这意味着在 Spring Boot 项目打包后,可以在 JAR 包内的 config 目录下放置一些特定的配置文件,这些文件将覆盖其他位置的同名配置。

先加载 config 子目录下的配置文件,再加载 config 目录下的配置文件:在 config 目录下,可以创建子目录来组织相关的配置文件,子目录下的配置文件将优先于父目录下的同名配置被加载。

先加载 appliction-{profile}.properties/yml,再加载application.properties/yml:通过使用不同的 profile,可以在同一份主配置文件 application.properties 或 application.yml 中包含多个环境的配置。具有特定 profile 名称的配置将优先于主配置被加载。

先加载 .properties 文件,再加载 .yml 文件:Spring Boot 支持 .properties 和 .yml 格式的配置文件。如果同一属性的配置在两个文件中都存在,那么 .properties 格式的文件中的配置将被优先加载。

同一个配置属性,在多个配置文件中都存在时,默认使用第一个读取到的,后面读取的不覆盖前面读取到的:这是 Spring Boot 配置加载的一个通用规则,无论是在不同位置还是不同格式的配置文件中。

理解这些规则对于正确地组织和管理 Spring Boot 的配置文件非常重要,因为它们决定了在应用程序运行时如何覆盖和合并不同的配置属性。正确的配置加载顺序可以确保应用程序按照预期的方式运行,同时避免了因不恰当的覆盖导致的意外行为。

139. Spring Boot 的核心配置文件有哪几个?它们的区别是什 么?

Spring Boot 的核心配置文件主要有两个:bootstrap.properties 或 bootstrap.yml 和 application.properties 或 application.yml。

bootstrap.properties 或 bootstrap.yml:

这是 Spring Boot 的引导配置文件,用于加载应用程序的外部配置。

它在应用程序的启动过程中被加载,优先于 application 配置文件。

它主要用于从额外的资源加载配置信息,例如连接外部服务(如数据库、消息队列等)的配置。

它还用于在本地外部配置文件中解密属性。

当使用 Spring Cloud Config 注册中心时,需要在 bootstrap 配置文件中添加链接到配置中心的配置属性,以加载外部配置中心的配置信息。

application.properties 或 application.yml:

这是 Spring Boot 的主要配置文件,通常位于项目的 classpath:config 目录下。

Spring Boot 会根据该文件中设置的属性值进行相应的配置,例如数据库连接信息、数据库的类型、端口号等。

它主要用于设置应用程序的通用配置参数,如数据库连接信息、端口号等。

它还可以用于配置更复杂的参数,如环境变量、线程数等,通常用于大型应用程序或需要更精细控制的应用程序。

总的来说,bootstrap 和 application 这两个配置文件共同作用,使得 Spring Boot 框架更加易于使用和扩展。它们的区别在于加载顺序和用途,其中 bootstrap 配置文件主要用于加载外部资源和解密属性,而 application 配置文件主要用于设置应用程序的通用配置参数。。

140. 数据库的三范式是什么?

数据库的三范式是关系型数据库设计的基础理论,用于确保数据的完整性和减少数据冗余。以下是三范式的定义:

第一范式 (1NF):确保每列保持原子性,即列不能可分。这要求表中的每个字段都是不可分割的最小单元,没有重复的数据和没有多余的数据。

第二范式 (2NF):在第一范式的基础上,非主键列必须完全依赖于主键,不能只依赖于主键的一部分。这要求数据库表中的每一个非键字段都完全依赖于整个主键,而不是只依赖于主键的一部分。

第三范式 (3NF):在第二范式的基础上,任何非主键列不能依赖于其他非主键列。这要求非主键列之间不存在传递依赖,即一个非主键字段不能依赖于另一个非主键字段。

遵循三范式的设计原则有助于提高数据库的性能、减少数据冗余并简化数据操作。然而,有时候为了提高查询性能或满足特定的业务需求,会故意违反三范式。这通常涉及到数据库的规范化与反规范化设计之间的权衡。

141. MySQL数据库引擎有哪些?

如何查看mysql提供的所有存储引擎 mysql> show engines;

InnoDB:这是MySQL的默认存储引擎。它支持事务处理,行级锁定,外键约束,并发性能高,且提供了崩溃恢复功能。InnoDB适用于大多数数据库应用,尤其是需要高并发读写和大量数据的事务处理系统。

MyISAM:MyISAM是MySQL最早的默认存储引擎。它不支持事务处理,但具有较高的读性能。MyISAM适用于读操作密集的应用,但在有大量写操作的场景下可能不是最佳选择,因为它不支持行级锁定。

Memory/HEAP:这个存储引擎将所有数据存储在RAM中,所以读写速度非常快。然而,当数据库服务器重启时,所有数据都会丢失。Memory引擎适用于临时表和缓存数据。

CSV:CSV引擎将数据存储为CSV格式,每行是一条记录,每个字段由逗号分隔。CSV引擎不支持索引,因此查询性能可能不如其他引擎。

ARCHIVE:这个存储引擎用于存储归档数据,主要用于备份和恢复操作。ARCHIVE引擎不支持事务处理、行级锁定和索引,但它具有低写入的性能优势。

BLACKHOLE:BLACKHOLE引擎主要用于数据复制,它会接收数据但不存储任何数据,只将其转发到另一个数据库服务器。

Federated:Federated引擎允许用户通过统一的数据库管理系统查询位于另一个MySQL服务器上的表。

以上是MySQL支持的主要数据库引擎。根据不同的应用需求和场景,可以选择最适合的引擎来提高性能和满足业务需求。

142. 说说InnoDB与MyISAM的区别?

InnoDB和MyISAM是MySQL中最常用的两种存储引擎,它们之间存在以下主要区别:

事务支持:InnoDB支持事务处理,提供了ACID兼容的存储,而MyISAM不支持事务处理。这意味着InnoDB可以提供更高的数据完整性和并发性能。

锁定机制:InnoDB支持行级锁定,而MyISAM只支持表级锁定。行级锁定允许更高的并发访问,减少了锁竞争,提高了并发性能。

外键约束:InnoDB支持外键约束,这有助于维护数据完整性。而MyISAM不支持外键约束,需要依赖应用程序来维护数据完整性。

崩溃恢复:InnoDB使用事务日志来确保数据的持久性和崩溃恢复能力。而MyISAM没有事务日志,因此恢复能力较弱。

数据存储结构:InnoDB采用聚集索引来存储数据,数据和索引都存储在B+树中。而MyISAM采用非聚集索引,数据和索引分别存储在不同的文件中。

全文索引:InnoDB不支持全文索引,而MyISAM支持全文索引。这对于需要全文搜索的应用程序来说是重要的考虑因素。

性能优化:在某些情况下,MyISAM可能具有更好的性能,尤其是在只读操作较多的情况下。但是,随着事务处理和并发写入的需求增加,InnoDB通常表现出更好的性能。

总结来说,InnoDB和MyISAM之间存在多个重要差异。选择哪种存储引擎取决于应用程序的具体需求,包括对事务处理、并发性能、数据完整性和崩溃恢复能力的要求。在需要高并发写入、事务处理和行级锁定的场景中,通常推荐使用InnoDB引擎。而在需要高性能的只读操作或全文搜索的场景中,MyISAM可能是更好的选择。

143. 数据库的事务,什么是事务?

数据库事务(Transaction)是数据库管理系统执行过程中的一个逻辑单位,它由一个有限的数据库操作序列构成。这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务(Transaction)是并发控制的基本单位。

事务通常包含了一个序列的对数据库的读、写、操作,其存在包含以下两个目的:为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。。

144. 索引是什么?

索引是数据库中用于快速检索数据的数据结构,它能够加快对数据的访问速度,提高数据库查询效率。索引类似于一本书的目录,可以快速定位到所需的数据,而不需要逐行扫描整个数据表。

索引的实现方式有多种,常见的有B树、哈希、位图等。在关系型数据库中,索引可以创建在单个列上,也可以创建在多个列上。创建索引时,数据库系统会自动维护索引数据结构,以支持高效的查询操作。

虽然索引可以提高查询速度,但也会占用额外的存储空间,并可能增加插入、更新和删除操作的时间开销。因此,在数据库设计和优化时,需要根据实际应用的需求和数据的特点来合理选择和使用索引。。

145. SQL优化手段有哪些?

使用索引:为经常查询的列和JOIN操作中的列建立索引,可以显著提高查询速度。同时,要注意避免过度索引,因为这会增加写操作的开销。

优化查询语句:使用EXPLAIN命令分析查询的执行计划,找出性能瓶颈。避免在查询中使用SELECT *,而是指定需要的列名。避免在WHERE子句中使用函数或计算。

使用合适的数据类型:为列选择合适的数据类型可以减少存储需求并提高查询性能。

使用连接(JOIN)代替子查询:在适当的情况下,使用连接代替子查询可以提高性能。

避免使用LIKE操作符进行通配符搜索:特别是在通配符位于搜索字符串的开头时,这会导致全表扫描。

优化存储引擎:根据需要选择合适的存储引擎,如InnoDB或MyISAM。

分区表:对于非常大的表,可以考虑使用分区表来提高查询性能。

使用缓存:如MySQL的查询缓存或应用层的缓存机制,可以减少对数据库的访问。

数据库优化工具:使用数据库提供的优化工具,如MySQL的OPTIMIZE TABLE命令。

定期进行数据库维护:如更新统计信息、重建索引等,保持数据库的健康状态。

读写分离:通过主从复制将读操作和写操作分离到不同的服务器上,提高性能。

垂直分表和水平分表:根据业务特点将一个大表分成多个小表,提高查询性能。

使用数据库代理:如ProxySQL,可以帮助负载均衡和查询缓存。

避免使用非标准的SQL功能:尽量使用标准SQL,这样在更换数据库系统时可以保证最大的兼容性。

以上是一些常见的SQL优化手段,但具体的优化策略需要根据实际的数据库结构、数据量和查询负载来制定。。

146. EXPLAIN命令分析查询的执行计划?

\1. 使用EXPLAIN命令:在SQL查询前加上EXPLAIN关键字。

这将返回查询的执行计划。

\2. 分析执行计划:查看EXPLAIN的输出结果,并关注以下几个关键点:

类型:查看查询的类型,如"index"、“range”、“ref"等。如果类型是"ALL”,则表示进行了全表扫描,这通常是性能瓶颈。

key:查看使用的索引。如果没有使用索引或使用不当,可能会导致性能问题。

rows:查看数据库估计需要扫描的行数。如果这个数字很大,可能表明查询需要优化。

Extra:查看额外的信息,如"Using filesort"、“Using temporary"等。如果看到"Using filesort”,表示需要对结果进行外部排序,这可能是一个性能瓶颈。

\3. 优化查询:基于执行计划的分析结果,可以采取以下优化措施:

* 添加或调整索引,以提高查询速度。

* 重新组织查询语句,避免全表扫描和不必要的JOIN操作。

* 优化数据库配置,如增加内存、调整缓存设置等。

\4. 持续监控和调整:性能优化是一个持续的过程。定期使用EXPLAIN分析查询执行计划,并监控数据库的性能指标,以发现潜在的性能瓶颈并进行调整。

通过以上步骤,你可以使用EXPLAIN命令分析查询的执行计划,并找出性能瓶颈。然后根据分析结果进行相应的优化措施,提高数据库的查询性能。

147. 简单说一说drop、delete与truncate的区别?

\1. DROP:DROP语句用于删除数据库中的表、视图、索引等对象。使用DROP语句会完全删除目标对象,包括其结构和数据。被删除的对象将无法恢复,需要谨慎使用。

\2. DELETE:DELETE语句用于从表中删除特定的行。使用DELETE语句,可以根据指定的条件删除表中的数据。DELETE语句只删除行,而不删除表本身或其结构。如果需要保留表的结构,但删除部分或全部数据,可以使用DELETE语句。

\3. TRUNCATE:TRUNCATE语句用于快速删除表中的所有数据,它与DELETE语句有所不同。TRUNCATE语句会删除表中的所有行,但保留表的结构,包括列、索引、约束等。TRUNCATE语句比DELETE语句更快,因为它是直接删除整个表的数据,而不是逐行删除。

主要区别如下:

- 执行速度:TRUNCATE语句通常比DELETE语句快,因为它不需要逐行删除数据,而是直接删除整个表的数据。

- 回滚操作:DELETE语句可以通过事务进行回滚,可以撤销删除操作,而TRUNCATE不支持回滚。一旦使用TRUNCATE删除数据,就无法撤销操作。

- 日志记录:DELETE语句在执行时会生成日志记录,可以通过回滚操作来还原数据。而TRUNCATE语句不会产生日志记录,无法回滚。

- 自增长列:使用DELETE语句删除表中的数据后,自增长列的值不会重置。而TRUNCATE语句删除表中的数据后,自增长列的值会被重置为初始值。

综上所述,DROP用于删除整个表或其他数据库对象,DELETE用于逐行删除表中的数据,TRUNCATE用于快速删除表中的所有数据并重置自增长列。选择使用哪种语句需要根据具体需求和场景来决定。。

148. 什么是视图?

视图(View)是数据库中的一个虚拟表,它是通过查询语句从一个或多个基本表(或其他视图)中导出的结果集。视图并不实际存储数据,而是根据查询定义的规则动态生成的。

​ 视图可以被看作是一个虚拟表,它具有列和行,但其数据实际上来自于基本表。通过使用视图,可以将复杂的查询逻辑封装起来,提供简化的数据访问接口,隐藏底层表的细节。视图还可以用于限制对数据的访问权限,只显示用户需要的数据,保护敏感信息。

使用视图的好处包括:

\1. 简化复杂查询:通过创建视图,可以将复杂的查询操作转换为简单的查询语句,提高查询的可读性和可维护性。

\2. 数据安全性:通过视图可以控制对数据的访问权限,只向用户暴露必要的信息,保护敏感数据的安全性。

\3. 数据独立性:当基本表结构发生变化时,视图可以保持不变,使应用程序与底层数据解耦,提供更好的数据独立性和灵活性。

\4. 简化数据访问:通过视图,可以隐藏底层表的细节,提供简化的数据访问接口,简化应用程序的开发和维护工作。

创建视图时,需要定义视图的查询语句以及列名。一旦视图被创建,可以像操作表一样使用它进行查询、插入、更新或删除操作。视图提供了一种方便而灵活的方式来处理复杂的数据操作和数据访问需求。

149. 什么是内联接、左外联接、右外联接?

内联接(Inner Join)是一种结合两个表中匹配行的连接操作。它基于两个表中的共有列,在这些列上对两个表进行比较,并返回符合条件的行。

左外联接(Left Outer Join)是一种连接操作,它返回左表中所有的行以及与右表中匹配的行。如果右表中没有匹配的行,那么结果集中相应的列将包含 NULL 值。

右外联接(Right Outer Join)与左外联接类似,不同之处在于它返回右表中所有的行以及与左表中匹配的行。如果左表中没有匹配的行,那么结果集中相应的列将包含 NULL 值。

这三种联接操作可以用来将多个表中的数据关联起来,根据指定的列值进行匹配。通过联接操作,可以获取到满足特定关联条件的数据行,可以从多个表中检索相关信息,使得查询更加灵活和强大。。

150. 并发事务带来哪些问题?

并发事务指的是在数据库系统中同时执行多个事务的情况。虽然并发事务可以提高数据库系统的吞吐量和性能,但也会导致一些问题和挑战。以下是几个常见的并发事务带来的问题:

\1. 脏读(Dirty Read):一个事务读取了另一个未提交事务的数据,在后续操作中可能会导致数据的不一致性。

\2. 不可重复读(Non-repeatable Read):一个事务在同一个查询中多次读取同一行数据,但由于其他并发事务的修改,每次读取的结果不一样。

\3. 幻读(Phantom Read):一个事务在同一个查询中多次读取符合条件的记录数,但由于其他并发事务的插入或删除操作,每次读取的记录数不一致。

\4. 丢失更新(Lost Update):两个或多个事务同时修改同一行数据,其中一个事务的修改结果被另一个事务覆盖,导致部分更新丢失。

\5. 死锁(Deadlock):两个或多个事务相互等待对方所持有的资源,导致系统无法继续执行。

\6. 性能下降(Performance Degradation):并发事务可能引起资源竞争,包括锁、内存和CPU等,从而导致系统性能下降。

​ 为了解决这些问题,数据库系统提供了事务隔离级别(如读未提交、读已提交、可重复读和串行化)以及并发控制机制(如锁机制和多版本并发控制)。通过正确选择合适的隔离级别和并发控制策略,可以保证数据的一致性、完整性和可靠性,同时提高数据库系统的并发性能。。

151. 事务隔离级别有哪些?MySQL的默认隔离级别是?

事务隔离级别是指在并发事务执行过程中,一个事务对其他事务的可见性和影响的限制程度。标准的SQL定义了四个事务隔离级别,分别是:

\1. 读未提交(Read Uncommitted):最低的隔离级别,允许一个事务读取另一个事务尚未提交的数据。这种隔离级别可能导致脏读、不可重复读和幻读的问题。

\2. 读已提交(Read Committed):允许一个事务只能读取其他事务已经提交的数据。这种隔离级别可以避免脏读问题,但仍可能出现不可重复读和幻读的情况。

\3. 可重复读(Repeatable Read):保证同一个事务多次读取同一行记录的结果都是一致的。在这个隔离级别下,会使用锁来确保其他事务无法修改被读取的数据,从而避免了脏读和不可重复读的问题。但幻读问题仍然可能存在。

\4. 串行化(Serializable):最高的隔离级别,要求事务串行执行,完全隔离了并发操作。通过强制事务的顺序执行,可以避免脏读、不可重复读和幻读等问题。但由于串行化的性能开销较大,一般很少使用。

MySQL的默认隔离级别是可重复读(Repeatable Read)。这意味着在一个事务中,多次读取同一行数据时,会得到一致的结果。其他并发事务无法修改被读取的数据,但新插入的数据可能会导致幻读问题。MySQL还提供了设置不同隔离级别的选项,可以通过设置SET TRANSACTION ISOLATION LEVEL来改变隔离级别。。

152. 大表如何优化?

\1. 合适的索引设计:通过创建合适的索引,可以加快查询操作的速度。对于大表来说,选择适当的索引列和索引类型非常重要。

\2. 分区:将大表按照某个特定的规则进行分区,可以将数据分散存储在多个物理文件上,从而提高查询效率。分区可以基于范围、列表或哈希等方式进行。

\3. 垂直拆分和水平拆分:如果大表中包含了不同的实体或者属性,可以考虑将其拆分成多个较小的表,使得每个表的数据量更小。另外,根据访问模式,也可以将表中的数据水平拆分到多个服务器上。

\4. 定期维护和优化:对于大表来说,定期进行表的优化操作是必要的,如重新组织表、优化查询语句、删除冗余数据等。这可以提高表的性能,并减少存储空间的占用。

\5. 批量操作和并行处理:针对大表的更新操作,尽可能使用批量操作,减少事务的次数,降低锁竞争的风险。同时,可以考虑使用并行处理技术来加快大表的处理速度。

\6. 数据分析和优化:通过对大表的数据进行分析,了解其访问模式、热点数据等特性,可以有针对性地进行优化。例如,将经常访问的数据缓存在内存中,减少磁盘IO等操作。

\7. 硬件升级:如果以上方法无法满足需求,可以考虑升级硬件设备,如增加内存、更换存储设备等,以提高整体性能。

详细内容可以参考: MySQL大表优化方案:

https://segmentfault.com/a/1190000006158186

153. 分库分表之后,id 主键如何处理?

全局唯一 ID(Global Unique ID, GUID):使用全局唯一ID作为主键,可以确保在整个分库分表架构中的唯一性。GUID通常是一个128位的数字,可以通过算法生成,例如UUID。

雪花算法(Snowflake):雪花算法是一种用于生成有序且唯一的ID的算法,适用于分布式系统。它由时间戳、机器ID和自增序列组成,可以保证在不同的数据库中生成不重复的主键。

分段自增 ID:在每个分库分表中维护一个独立的自增序列,每个序列分配一个固定范围的ID号段,然后在该范围内递增生成主键。这样可以避免全局锁或序列生成器的性能瓶颈。

哈希函数转换:将原始的主键值通过哈希函数进行转换,使其均匀地分散到不同的分库或分表中。这样可以保证主键在整个分库分表架构中的分布较为平衡。。

Twitter的snowflake算法 :Github 地址:https://github.com/twitter-archive/snowflake。

154. 说说在 MySQL 中一条查询 SQL 是如何执行的?

在MySQL中,一条查询SQL的执行过程可以简单概括为以下几个步骤:

\1. 解析器(Parser):首先,MySQL服务器会对查询SQL进行解析,将其分解为语法树。解析器会检查SQL的语法和语义是否正确,并将其转化为内部数据结构表示。

\2. 优化器(Optimizer):接下来,MySQL的优化器会对查询进行优化,选择最佳的查询执行计划。优化器会考虑索引、表连接顺序、子查询优化等因素,以尽量提高查询性能。

\3. 执行计划生成(Execution Plan Generation):优化器选择了最佳的查询执行计划后,会生成一个执行计划,该计划指定了如何获取和处理数据。执行计划包括了表的访问顺序、使用的索引、连接方式等信息。

\4. 执行引擎(Execution Engine):根据生成的执行计划,MySQL的执行引擎开始执行查询操作。执行引擎会负责打开表、读取数据、筛选和排序结果等操作。它会根据查询计划使用适当的算法和技术来完成查询任务。

\5. 返回结果(Result):最后,MySQL的执行引擎将查询结果返回给客户端。如果查询需要返回大量数据,则可能会使用临时文件或者排序缓冲区进行中间结果的处理。

​ 需要注意的是,MySQL还会根据数据库的统计信息和缓存情况等因素做出决策,以进一步优化查询执行过程。此外,MySQL还支持并发执行多个查询,通过线程池管理同时进行的查询操作。整个查询过程是一个复杂的协作过程,涉及到多个组件的配合工作来完成。。

155. MySQL 中 varchar 与 char 的区别?varchar(30) 中的 30 代表的涵义?

在MySQL中,varcharchar是用于存储字符串类型数据的两种列类型。

区别:

\1. 存储方式: char类型是固定长度的,它会在存储时自动填充空格使其达到指定长度;而varchar类型是可变长度的,它只会占用实际存储的字符长度加上一些额外的字节用于记录长度信息。

\2. 空间占用: 由于char类型是固定长度的,所以占用的存储空间始终等于定义的长度。而varchar类型的存储空间则根据实际存储的字符串长度而变化,不会浪费额外的空间。

\3. 查询效率: 对于char类型的列,由于固定长度,查询时不需要额外计算字符串长度,因此在某些情况下可能会比varchar类型更快。但对于较长的字符串,由于varchar类型减少了存储空间的浪费,可能占优势。

varchar(30) 中的 30 代表的涵义:

在MySQL中,varchar(30)中的30表示该字段最大可存储的字符数为30。这意味着可以存储长度为0到30的字符串。当存储的字符串超过30个字符时,MySQL会截断超出部分。需要注意的是,存储的字符数不包括用于记录长度信息的额外字节。

使用合适的字段长度对于数据库设计非常重要,既要满足数据的存储需求,又要避免空间的浪费。因此,在选择varcharchar类型时,应根据实际情况和业务需求来确定字段的最大长度。。

156. int(11) 中的 11 代表什么涵义?

在MySQL中,int(11)中的11代表的是显示宽度(display width),它指定了用于显示该整数类型的值时所需的最小位数。

​ 显示宽度并不会限制存储的实际范围或精度,而是起到一种视觉上的展示效果。对于int类型来说,其存储范围是固定的,并不受显示宽度的影响。

当使用int(11)定义一个整数列时,这个数字只是用于指示客户端工具在显示结果集时应该保留的最小位数。例如,如果某个整数的值为10,那么在显示时可能会以"10"的形式呈现,而不是"0000000010"。它主要用于美化和对齐输出结果,而不会改变实际存储或计算的方式。

需要注意的是,对于int类型,显示宽度仅适用于零填充(zero-padding)的展示,而不适用于负数或其他特殊情况。此外,显示宽度的取值范围为1到255。大多数情况下,没有必要指定显示宽度,可以将其省略,默认为10。

157. 为什么 SELECT COUNT(*) FROM table 在 InnoDB 比 MyISAM 慢?

在InnoDB存储引擎中,执行SELECT COUNT(*) FROM table比在MyISAM存储引擎中慢的原因主要有两个方面:

\1. 存储方式: InnoDB和MyISAM在数据存储方式上有所不同。InnoDB使用了聚集索引(clustered index),即数据按照主键的顺序物理上存放在磁盘上。而MyISAM使用堆表(heap table),数据是按照插入顺序存放的。

\2. 计算方式: 当执行SELECT COUNT(*) FROM table时,InnoDB和MyISAM的计算方式也不同。InnoDB需要遍历整个表来计算行数,即使只需要返回行数而无需具体的行数据。而MyISAM存储引擎中,行数是存储在表头的一个计数器中的,它能直接返回准确的行数,无需遍历整个表。

由于以上两个原因,当执行SELECT COUNT(*) FROM table时,InnoDB需要遍历整个表来计算行数,而MyISAM可以直接返回存储在表头的行数计数器的值,因此在大型表中,特别是当表中包含大量行时,InnoDB的计算速度可能会比MyISAM慢。

然而,需要注意的是,在实际开发中,我们更关注的是数据库的整体性能,而不仅仅是某个特定查询的速度。InnoDB存储引擎在其他方面(如事务支持、并发性等)提供了更强大和可靠的功能,因此在大多数情况下,选择使用InnoDB作为存储引擎可能更加合适。如果需要频繁执行类似的计数查询,可以考虑使用其他策略来优化查询性能,例如使用缓存或者维护额外的统计表来存储行数信息。

158. MySQL 索引类型有哪些?

MySQL支持多种类型的索引,包括:

\1. B-Tree索引(默认): B-Tree(平衡树)索引是MySQL最常用的索引类型。它适用于全文索引、哈希索引以及范围查询等各种场景。

\2. 哈希索引: 哈希索引使用哈希算法将索引列的值映射到一个哈希表中的地址,因此查询速度非常快。但是,哈希索引只支持精确匹配查找,不适用于范围查询或排序操作。

\3. 全文索引: 全文索引主要用于对文本内容进行搜索。它可以在文本字段中进行关键词搜索,并返回与关键词相关的行。

\4. 空间索引: 空间索引是为了支持地理空间数据类型而设计的索引。它们使得可以有效地执行基于位置的查询,例如查找附近的店铺或者在某个区域内查找数据。

此外,在MySQL 8.0版本及以上,还引入了一些新的索引类型:

\5. R-Tree索引: R-Tree是一种用于空间数据的索引结构,它支持高效的范围查询和空间关系操作。

\6. BTREE/RTREE混合索引(即GEOGRAPHY索引): 它是一种将B-Tree和R-Tree索引结合起来的混合索引,适用于空间数据类型和非空间数据类型的混合查询。

每种索引类型都有其特定的使用场景和优缺点。在设计数据库时,需要根据具体的需求和数据特性选择最合适的索引类型,以提高查询性能和数据访问效率。。

159. 什么时候不要使用索引?

虽然索引可以提高查询性能,但并不是在所有情况下都适合使用索引。以下是一些情况下不建议使用索引的场景:

\1. 小型表: 当表的数据量较小(例如只有几十行或几百行)时,使用索引可能会增加额外的开销而带来很少的性能提升。因为索引需要占用存储空间,并且在插入、更新和删除操作时需要更新索引结构。

\2. 频繁进行大批量的数据插入、更新和删除: 当需要频繁执行大批量的数据插入、更新和删除操作时,索引会对性能产生负面影响。每次进行这些操作时,都需要更新相关的索引,这可能会导致额外的写入开销和索引维护成本。

\3. 高度动态的表: 如果表中的数据经常发生变动,特别是频繁进行更新和删除操作时,索引会导致额外的维护成本。因为每次更新和删除操作都需要更新索引结构,这可能会影响整体性能。

\4. 大部分查询返回结果集过大: 如果查询的结果集很大,返回大部分或全部的表数据,使用索引可能会导致性能下降。因为对于大的结果集,使用索引可能会比全表扫描更慢,因为需要额外的I/O操作来获取索引数据。

​ \5. 查询中有复杂的函数或表达式: 如果查询中使用了复杂的函数或表达式,例如使用了LIKECONCAT或自定义的函数等,索引对于这些查询可能无法有效利用,导致性能下降。

\6. 频繁进行随机查询: 如果查询模式是随机访问,即没有明确的谓词或过滤条件,那么索引可能无法提供明显的性能优势。在这种情况下,全表扫描可能比使用索引更高效。

总之,不应盲目地将索引应用于所有表和所有查询。在确定是否使用索引时,需要仔细分析具体的业务需求、查询模式以及数据变动性,权衡索引带来的性能提升和开销。。

160. 说说什么是 MVCC?

是一种并发控制的方法。MVCC通过维护一个数据的多个版本,减少读写操作的冲突,提高数据库的并发性能。在MVCC中,每个事务在开始时都会获取一个唯一的事务ID,称为版本号。当数据被修改时,不是直接修改原数据,而是生成一个新的数据版本。读取操作总是读取最新版本的数据,而写操作则生成一个新的数据版本。通过这种方式,多个事务可以同时访问同一份数据,而不会互相干扰。

161. MVCC 可以为数据库解决什么问题?

在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。

解决脏读、幻读、不可重复读等事务隔离问题,但无法解决更新丢失问题。

162. 说说 MVCC 的实现原理?

MVCC 的实现原理主要包括以下几个方面:

版本号管理:在 MVCC 中,每个数据项都有一个唯一的版本号,每个事务也有一个唯一的版本号。通过比较事务的版本号和数据项的版本号,可以判断事务是否可以看到这个数据项。

事务开始时获取读版本:在事务开始时,系统会为其分配一个唯一的版本号,这个版本号将用于该事务所读取的所有数据项。这样,当其他事务修改数据时,当前事务仍然可以看到它开始时读取的数据版本,直到其提交或回滚。

写操作生成新版本:当数据被修改时,MVCC 不会直接覆盖旧数据,而是生成一个新的数据版本。这样,其他事务仍然可以看到旧的数据版本,直到其结束。

读操作读取最新版本:MVCC 的读操作总是读取最新版本的数据。如果某个数据项有多个版本,系统会根据一定的算法(如乐观锁)选择最新的版本返回给读操作。

过期数据清理:随着时间的推移,数据项的旧版本会越来越多,占用大量的存储空间。MVCC 实现通常会定时清理过期和不再被使用的数据版本,释放存储空间。

总的来说,MVCC 的实现原理是通过维护数据的多个版本和记录每个事务的可见性范围,来减少读写操作的冲突和提高并发性能。这种机制可以有效地处理多个事务同时对同一数据进行读写的情况,而不需要加锁等待,从而提高数据库的并发性能和响应速度。

163. 请说说 MySQL 数据库的锁?

MySQL数据库使用锁来实现并发控制和事务隔离。下面介绍几种常见的MySQL数据库锁类型:

\1. 表级锁(Table-level Locks): 表级锁是最粗粒度的锁,它会锁定整个数据表。MySQL中的表级锁包括两种类型:共享锁(Shared Lock)和排他锁(Exclusive Lock)。共享锁允许多个事务同时读取数据,但不允许其他事务对该表进行写操作。排他锁则只允许一个事务对表进行写操作,其他事务无法读取或写入该表。

\2. 行级锁(Row-level Locks): 行级锁是最细粒度的锁,在MySQL InnoDB存储引擎中支持。行级锁允许在同一表中的不同行上同时进行读和写操作。当一个事务获取了某一行的排他锁后,其他事务无法读取或写入该行,从而确保数据的完整性。

\3. 页级锁(Page-level Locks): 页级锁介于表级锁和行级锁之间,它将数据按页划分,每个页面包含多行数据。MySQL的MyISAM存储引擎使用页级锁。当一个事务获取了某一页的锁时,其他事务无法读取或写入该页中的任何行。

\4. 表锁(Table Locks): 表锁是MySQL中最基本和最低级别的锁,它会锁定整个数据表。当一个事务获取了某个表的锁时,其他事务无法对该表进行读取或写入操作。

​ 行级锁提供了更好的并发性能和更细粒度的锁定,因此在多数情况下推荐使用。然而,行级锁也带来了额外的开销和复杂性。对于特定的场景和需求,可以根据具体情况选择适当的锁类型来平衡并发性能和资源消耗。 MySQL数据库还提供了其他类型的锁,如意向锁(Intention Lock)、记录锁(Record Lock)等,用于优化并发控制和提高性能。。

164. 说说什么是锁升级?

锁升级是指在数据库中将低级别的锁转换为更高级别的锁。在多数数据库管理系统中,锁升级是一种优化策略,旨在减少锁冲突和提高并发性能。

常见的锁升级方式如下:

​ \1. 行级锁升级为表级锁: 当一个事务需要在某个表的多行上进行写操作时,数据库可能会将原本的行级锁升级为表级锁,以避免频繁的行级锁竞争和切换带来的开销。这样做可以减少锁冲突,但也会牺牲并发性能。

\2. 共享锁升级为排他锁: 当一个事务在某个数据对象(如表、页或行)上已经持有共享锁,但随后需要对该对象进行写操作时,数据库可能会将共享锁升级为排他锁。这样做的目的是为了确保对数据对象的写操作不会与其他事务的读操作产生冲突。

锁升级是数据库管理系统根据具体情况动态进行的,旨在平衡并发性能和资源消耗。它可以减少锁粒度的切换和冲突,从而提高系统的吞吐量和响应速度。然而,锁升级也可能增加了某些事务等待锁的时间,因此在设计和优化数据库应用程序时,需要考虑到合适的锁级别和锁升级策略。。

165. 主键和候选键有什么区别?

主键是用于唯一标识数据库表中每一行的数据列或列的组合。一个表只能有一个主键,主键的值在表中必须是唯一的,并且不能为空。主键的主要作用是确保数据的唯一性和完整性,它能够快速地定位到特定的行,并确保数据的准确性和可靠性。

候选键是能够唯一标识数据库表中每一行的数据列或列的组合,但是它并不一定是主键。候选键满足唯一性和完整性,但在没有设置为主键之前,它并不是唯一的标识符。换句话说,候选键是一种候选的主键,但在它被选作主键之前,它仍然是一个普通的列。

总结来说,主键和候选键的主要区别在于:主键是唯一的标识符,用于唯一地标识表中的每一行;而候选键是可以被用作标识符的列或列的组合,但并不是唯一的标识符,除非它被设置为主键。。

166. 主键与索引有什么区别?

主键是用于唯一标识数据库表中每一行的数据列或列的组合。主键的主要作用是确保数据的唯一性和完整性,它能够快速地定位到特定的行,并确保数据的准确性和可靠性。此外,主键还可以作为表与表之间关系的外键,建立表与表之间的联系。

而索引是一种数据库对象,用于提高数据检索的速度。索引可以应用于单个列或多个列,并且可以包含其他数据库对象,如列、视图、存储过程等。通过索引,数据库系统可以快速地定位到表中的数据行,从而加速查询操作的速度。

​ 总结来说,主键和索引的主要区别在于:主键主要用于唯一标识表中的每一行,而索引主要用于提高数据检索的速度。虽然主键和索引都可以提高查询速度,但主键还具有保证数据完整性的作用,而索引只是用于加速查询的辅助工具。

167. MySQL 如何做到高可用方案?

\1. 主从复制(Master-Slave Replication):在主数据库上进行写操作,然后将数据复制到一个或多个从数据库中。当主数据库发生故障时,可以将其中一个从数据库提升为新的主数据库,以保证服务的持续可用性。

\2. MySQL集群(MySQL Cluster):MySQL集群是一种基于共享存储和同步复制的高可用解决方案。它可以将数据库分布在多个节点上,并自动处理节点之间的数据同步和容错。如果集群中的某个节点发生故障,其他节点可以接管请求并继续提供服务。

\3. 数据库镜像(Database Mirroring):使用数据库镜像技术可以将主数据库的数据实时地复制到备用数据库中。当主数据库发生故障时,备用数据库可以立即接管服务,并确保业务的连续运行。

\4. MySQL复制链(Replication Chain):复制链是一种多级复制的架构,其中每个节点既是上一级节点的从节点,又是下一级节点的主节点。这种设计可以实现更高级别的冗余和可用性。

\5. 心跳监测与故障切换:通过使用心跳监测机制,可以定期检测MySQL服务器的状态。当监测到服务器不可用时,可以自动将请求切换到备用服务器上,以确保服务的连续性。

\6. 负载均衡(Load Balancing):使用负载均衡器将请求分发到多个MySQL实例,以平衡数据库的负载。当一个实例发生故障时,负载均衡器可以自动将请求重定向到其他可用实例上。

以上是一些常见的MySQL高可用方案,具体选择哪种方案取决于应用程序的需求、可用资源和数据安全性要求。

168. 什么是SpringCloud?

Spring Cloud是基于Spring Boot的一整套实现微服务架构的解决方案。它提供了一系列的组件和服务治理工具,使得开发者可以快速构建分布式系统。

Spring Cloud的核心组件包括Spring Cloud Gateway、Spring Cloud Eureka、Spring Cloud Ribbon、Spring Cloud OpenFeign(默认将Ribbon作为负载均衡器,直接内置了 Ribbon)等,这些组件分别提供了微服务网关、服务注册与发现、负载均衡、声明式REST服务等功能。

同时,Spring Cloud基于Netflix的多个开源组件进行了高度抽象和封装,利用Spring Boot的开发便利性,简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、熔断器等。

总之,Spring Cloud为微服务架构开发提供了一种简单有效的解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶。

169. 什么是微服务?

将一个单一的应用拆分为多个小型的服务,每个服务运行在自己的进程中,服务间采用轻量级的通信机制(HTTP/webservice等)。这些服务围绕业务能力构建,并且可以全自动独立部署。服务可以采用不同的语言和存储技术开发。

微服务的优点在于:

代码更容易更新:可以直接添加新特性或功能,而不必更新整个应用,团队可以对不同的组件使用不同的技术栈和不同的编程语言。

组件可以相互独立地扩展:从而减少与必须扩展整个应用相关的浪费和成本(因为单个功能可能面临过多的负载)。

然而,微服务也带来了一些挑战,例如运维要求较高和分布式系统带来的复杂性等。

170. SpringCloud有什么优势?

易于开发和维护:基于Spring Boot,简化了配置,提供了丰富的组件和工具,使得开发人员能够更加灵活地选择所需的功能组件。

高可用性:通过负载均衡和服务注册中心等功能,实现对微服务的自动化管理和扩展,提高了微服务的可用性和可扩展性。

安全性:提供了多种安全机制和技术,可以确保微服务之间的通信安全性和数据保护,保障应用系统的安全性。

透明性:各个部件都具有高度的可扩展性和可配置性,帮助开发人员更好地实现应用系统的透明性和可维护性。

强大的社区支持:活跃的社区提供了丰富的教程和解决方案,遇到问题容易找到解决方案。

​ 适合中小企业:对于中小企业来说,使用门槛较低,可以快速构建微服务架构。

持续更新和完善:Spring Cloud在不断发展中,不断推出新功能和修复问题,使得它能够始终保持最新状态。

综上所述,Spring Cloud的优势使其成为构建微服务架构的理想选择之一。

171. 什么是服务熔断?什么是服务降级? // 复第二天

服务熔断是一种用于处理分布式系统中服务间调用故障的机制。当一个微服务发生故障或超时时,服务熔断可以避免连锁故障,提高整个系统的稳定性。通过在服务调用的过程中设置一个熔断器,并监控服务的调用情况,当服务的错误率或失败次数超过设定的阈值时,熔断器会打开,将后续的请求快速失败,而不是继续调用具有高延迟或已经失效的服务。

服务降级则是在面对系统负载过高、资源不足或外部依赖故障等异常情况下,通过临时屏蔽某些功能或改变服务行为,以保证核心功能的可用性和性能稳定性的一种策略。服务降级的目的是在极端或异常情况下提供有限但可靠的服务,而不是完全失败或导致系统崩溃。

服务熔断和服务降级的主要区别在于触发原因和服务处理方式。服务熔断主要是由于某个服务的故障或延迟导致,通过快速失败和降级处理来提高系统的可用性和稳定性。而服务降级则是在极端或异常情况下触发,通过临时屏蔽某些功能或改变服务行为来保证核心功能的可用性和性能稳定性。

在实际应用中,需要根据具体情况选择适合的策略来处理分布式系统中的故障和异常情况,以提高系统的可用性和稳定性。。

172. Eureka和zookeeper都可以提供服务注册与发现的功能,请 说说两个的区别?

Eureka和Zookeeper都是常用的服务注册与发现工具,但在某些方面有一些区别。

\1. 架构模型:

- Eureka:Eureka采用了客户端-服务器的架构模型。在Eureka中,存在一个Eureka Server作为服务注册与发现的中心节点,而各个服务实例作为客户端向Eureka Server注册并获取其他服务的信息。

- Zookeeper:Zookeeper采用集群模式,其中包含多个节点的集合。每个节点都存储了服务实例的信息,并协同工作以提供服务注册与发现功能。

\2. CAP原则:

- Eureka:Eureka遵循AP(可用性和分区容忍性)原则。在网络分区故障时,Eureka会优先保证可用性,可能导致服务注册与发现的一致性稍有延迟。

- Zookeeper:Zookeeper遵循CP(一致性和分区容忍性)原则。它将一致性放在首位,即在网络分区故障时,可能会牺牲一部分可用性来确保一致性。

\3. 功能特点:

- Eureka:Eureka提供了自我保护机制,当一段时间内没有收到服务实例的心跳时,Eureka仍然会保留该实例的信息,避免因网络波动而导致误删。Eureka还提供了基于RESTful的API,便于集成和使用。

- Zookeeper:Zookeeper具有较强的一致性和可靠性,在分布式环境下表现出色。它可以作为分布式系统的协调者,并提供了许多附加功能,如分布式锁、队列等。

​ 总体而言,Eureka更适合于构建云原生微服务架构,具有简单的部署和易用性,而Zookeeper则适用于更复杂的分布式环境,具有强一致性和可靠性。选择使用哪个取决于具体的应用需求和技术栈。

173. Eureka和nacos?

Eureka和Nacos都是服务注册与发现的组件,用于微服务架构中。它们有一些共同点,如都支持服务注册和服务拉取,都支持服务提供者心跳的方式做健康检测。然而,它们之间也存在一些显著的区别。

Eureka是Spring Cloud微服务框架默认的也是推荐的服务注册中心,由Netflix公司开发并开源。它基于REST服务开发,主要用于实现AWS云中的服务定位,以实现中间层服务器的负载均衡和故障转移。Eureka分为Eureka Server(Eureka服务)和Eureka Client(Eureka客户端),所有Eureka Server通过Replicate进行数据同步。Eureka遵循着CAP理论中的A(可用性)和P(分区容错性)。然而,需要注意的是,Eureka 2.0之后已经停止开源。

Nacos是阿里巴巴最新开源的项目,致力于帮助用户发现、配置和管理微服务。Nacos提供了一组简单易用的特性集,帮助用户快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos既可以作为服务注册中心,也可以作为配置中心。它支持多种数据存储后端,包括MySQL、MariaDB和Apache SkyWalking,并提供了自动故障转移、高可用性等特性。此外,Nacos还支持服务端主动检测提供者状态,以及服务列表变更的消息推送模式,使服务列表更新更加及时。在集群模式下,Nacos默认采用AP方式,当集群中存在非临时实例时,则采用CP模式。

总结来说,Eureka和Nacos都是用于服务注册与发现的组件,但Nacos提供了更丰富的功能,包括作为配置中心使用,以及支持多种数据存储后端和自动故障转移等特性。因此,在选择服务注册与发现的组件时,需要根据具体的需求和场景来决定使用Eureka还是Nacos。

174. SpringBoot和SpringCloud的区别?

SpringBoot和SpringCloud是两个密切相关的框架,但它们在功能和用途上存在一些重要的区别。

核心功能:SpringBoot专注于快速开发单个独立的Spring应用,通过简化配置过程,提高了开发效率。而SpringCloud则是一系列框架的有序集合,主要目的是为微服务提供综合管理框架,从而简化了微服务的开发和部署。

使用方式:SpringBoot可以独立使用,而SpringCloud必须在SpringBoot使用的前提下才能使用。

目的:SpringBoot的设计目的是为了在微服务开发过程中简化配置文件,提高工作效率。而SpringCloud的设计目的是为了管理同一项目中的各项微服务。

集成性:SpringCloud与SpringBoot是相互依赖的关系,但二者的创作初衷是完全不同的。SpringCloud的组成部分包括服务发现、负载均衡、熔断器等。

总结来说,SpringBoot和SpringCloud的主要区别在于它们的核心功能和使用场景。SpringBoot主要关注单个应用的快速开发,而SpringCloud则旨在提供一个综合的框架来管理微服务。

175. 负载平衡的意义什么?

\1. 提高系统性能和可伸缩性:负载平衡将请求分发到多个服务器上,使得每个服务器都可以处理一部分负载。这样可以减轻单个服务器的压力,提高整体系统的吞吐量和响应速度。通过增加服务器数量,还可以更好地应对流量峰值,实现系统的可伸缩性。

\2. 提高系统的可用性和可靠性:通过将负载分散到多个服务器上,即使其中某一台服务器出现故障或不可用,其他服务器仍然可以继续处理请求,从而避免了单点故障。负载平衡可以提供高可用性,确保服务的连续性和可靠性。

\3. 优化资源利用和成本效益:负载平衡可以帮助合理利用服务器资源,使得每台服务器的负荷均衡,避免某些服务器过载而浪费资源,同时也减少了服务器的闲置时间。通过有效地利用服务器资源,可以降低硬件投资和运维成本。

\4. 支持横向扩展和灵活部署:负载平衡器作为中间层,可以透明地将请求转发到后端的多台服务器上。这使得系统可以方便地进行横向扩展,添加新的服务器节点,实现更高的性能和容量。同时,负载平衡器还可以灵活地调整请求转发策略,以满足特定的需求和场景。

​ 总之,负载平衡在分布式系统中具有重要的意义,它通过合理分配和分发负载,提高系统性能、可用性和可靠性,优化资源利用和成本效益,支持系统的扩展和部署。。

176. 说说 RPC 的实现原理?

RPC(Remote Procedure Call)是一种远程过程调用协议,可以让一台计算机的程序调用另一台计算机的子程序,而不需要了解底层网络通信协议。RPC 的实现原理可以分为以下几个步骤:

定义接口:首先需要定义一套接口,接口中包含了需要远程调用的方法。这些方法可以是任何可执行的操作,例如计算、查询、更新等。

序列化/反序列化:在 RPC 中,数据需要在不同的计算机之间传输。为了能够传输这些数据,需要将这些数据转换成一种通用的格式,这种过程称为序列化。当数据到达目标计算机后,需要将这些数据转换回原来的格式,这个过程称为反序列化。

通信:在 RPC 中,客户端和服务器之间的通信是通过网络完成的。客户端和服务器之间需要建立一条通信通道,以便客户端可以向服务器发送请求,服务器可以向客户端返回结果。

动态代理:在 RPC 中,客户端实际上是通过调用本地代理来调用远程方法的。代理会负责将请求发送给服务器,并将服务器的结果返回给客户端。这样,客户端就可以像调用本地方法一样调用远程方法。

执行:当客户端向服务器发送请求后,服务器会执行相应的操作,并将结果返回给客户端。如果客户端需要再次调用同一个远程方法,它只需要再次发送请求即可。

​ 以上是 RPC 的实现原理,具体实现方式可能会因不同的 RPC 框架而有所不同。但总的来说,RPC 的目标是让客户端能够像调用本地方法一样调用远程方法,从而简化分布式系统的开发。。

177. eureka自我保护机制是什么?

Eureka的自我保护机制是为了防止误杀服务。当Eureka集群中的某些实例出现故障时,Eureka不会将其删除,从而对整个服务造成影响。

Eureka的自我保护机制的工作原理是:当Eureka检测到某个服务实例的心跳消失时,它不会立即将该实例从注册表中删除,而是会在一段时间内将其移动到一个保护段。在这段时间内,Eureka仍然会接收该实例的心跳。如果该实例恢复了,它将从保护段恢复到注册表中。如果该实例在保护期间没有恢复,Eureka将其从注册表中删除。

Eureka的自我保护机制能够有效地避免因暂时的故障导致服务不可用,提高了服务的稳定性和可用性。。

178. 什么是Ribbon?

提供了多种负载均衡算法和故障转移机制,可以帮助开发人员在微服务架构中有效地管理服务间的通信。Ribbon可以与多种基于HTTP的客户端库(如RestTemplate)结合使用,并允许开发人员灵活地配置和定制负载均衡策略。

使用Ribbon,开发人员可以将单个服务的请求分发到不同的实例上,以提高系统的可用性和性能。Ribbon通过周期性地从服务注册中心获取服务实例的列表,并根据一定的算法选择要发送请求的目标实例。当某个实例出现故障时,Ribbon会自动切换到其他健康的实例,从而实现故障转移。

总之,Ribbon是一个功能强大的负载均衡客户端库,适用于构建弹性和高可用性的分布式系统。。

179. 什么是 feigin ?它的优点是什么?

Feign是一个声明式的、基于注解的HTTP客户端工具,它简化了使用HTTP进行服务间通信的过程。Feign是Netflix开源的一部分,旨在简化微服务架构中的远程服务调用。

Feign的优点包括:

\1. 声明式的API:使用Feign,我们只需要定义接口并添加相关注解即可,而无需手动编写HTTP请求的代码。这样可以大大减少重复的模板代码,并提高开发效率。

\2. 集成负载均衡和服务发现:Feign与Ribbon和Eureka等服务治理组件集成紧密,可以自动实现负载均衡和服务发现功能。通过注解配置,Feign可以自动选择目标服务的实例,并将请求转发到相应的实例上。

\3. 内置支持多种编解码器:Feign内置了对多种数据传输格式的支持,包括JSON、XML等。开发人员可以根据需求选择合适的编解码器,使得数据的序列化和反序列化变得简单而灵活。

​ \4. 容易集成和扩展:Feign与Spring Cloud框架无缝集成,在配置上非常方便。同时,Feign还允许开发人员通过编写自定义的拦截器来扩展其功能,以满足特定的需求。

综上所述,Feign是一个简化微服务架构中远程服务调用的强大工具,通过其声明式的API和集成的负载均衡和服务发现功能,可以显著提升开发效率和系统的可维护性。

180. Ribbon和Feign的区别?

Ribbon和Feign都是Spring Cloud中提供的组件,用于实现微服务间的调用和负载均衡。它们有一些不同之处,主要表现在以下几个方面:

启动类使用的注解:在Ribbon中,使用@RibbonClient注解,而在Feign中则使用@EnableFeignClients注解。

服务的指定位置:在Ribbon中,服务的声明是在@RibbonClient注解上,而在Feign中,服务的声明是在定义抽象方法的接口中使用@FeignClient注解。

调用方式:Ribbon需要自己构建HTTP请求,模拟HTTP请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。而Feign则是在Ribbon的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建HTTP请求。

综上所述,Ribbon和Feign的区别主要表现在启动类使用的注解、服务的指定位置以及调用方式上。

181. 说说 Dubbo 工作原理?

Dubbo是一种高性能、轻量级的开源RPC(远程过程调用)框架,用于构建分布式服务架构。以下是Dubbo的工作原理:

\1. 服务注册与发现:提供者在启动时向注册中心注册自己所提供的服务,并将服务的元数据信息注册到注册中心上。消费者在启动时从注册中心获取可用的服务列表。这个注册中心充当了服务提供者和服务消费者之间的中介。

\2. 服务治理:当服务提供者接收到请求时,Dubbo通过负载均衡算法选择一个可用的提供者实例来处理请求。同时,Dubbo还提供了集群容错机制,可以在提供者实例失败时进行故障转移和自动切换。

\3. 通信协议:Dubbo支持多种通信协议,包括基于TCP的Dubbo协议和HTTP协议。Dubbo协议使用了自定义的二进制序列化方式,可以提供更高的性能和吞吐量。

\4. 数据传输:Dubbo采用了NIO(Non-blocking I/O)异步通信模型,在服务提供者和消费者之间建立长连接,以减少网络开销和连接建立的时间。

​ \5. 负载均衡:Dubbo提供了多种负载均衡策略,如随机、轮询、一致性哈希等。根据负载均衡策略,Dubbo将请求分发到不同的服务提供者实例上,以实现负载均衡和故障转移。

总之,Dubbo通过注册中心、负载均衡、服务治理和高效的通信协议等机制,实现了可靠的远程服务调用。它具有高性能、灵活的配置和丰富的功能,被广泛应用于构建大规模分布式系统。。

182. Dubbo 负载均衡策略?

Dubbo 提供了多种负载均衡策略,包括:

随机(Random):按照权重的设置随机概率进行负载均衡。

轮询(Round Robin):按照权重设置轮询比率进行负载均衡。

最少活跃调用数(Least Active):响应快的提供者接受越多请求,响应慢的接受越少请求。

​ 一致性哈希(Consistent Hash):相同参数的请求总是发到同一个服务提供者。如果服务挂了,则会基于虚拟节点平摊到其他提供者上。

此外,Dubbo 还支持自定义负载均衡策略,以满足特定的业务需求。用户可以根据实际场景选择合适的负载均衡策略,以提高系统的性能和可靠性。。

183. Dubbo 动态代理策略有哪些?

Dubbo 支持多种动态代理策略,以满足不同的业务需求。以下是 Dubbo 常见的动态代理策略:

JDK 动态代理:JDK 动态代理是基于 Java 的反射机制实现的,主要针对接口进行代理。当服务提供者需要暴露多个接口时,可以使用 JDK 动态代理来生成代理对象。

CGLIB 动态代理:CGLIB 是一个强大的高性能的代码生成库,可以扩展 JAVA 类与实现 JAVA 接口。当服务提供者没有实现接口或者需要基于类进行代理时,可以使用 CGLIB 动态代理。

Javassist 动态代理:Javassist 是字节码操控框架,用于在运行时动态生成和修改 Java 类。它可以用于实现 AOP(面向切面编程)编程。当需要基于方法进行切面编程时,可以使用 Javassist 动态代理。

​ 总之,Dubbo 支持多种动态代理策略,用户可以根据实际需求选择合适的策略,以满足不同的业务需求。。

184. 说说 Dubbo 与 Spring Cloud 的区别?

Dubbo和Spring Cloud是两个流行的微服务框架,它们具有以下几点区别:

\1. 架构和设计思想:Dubbo是一个RPC(远程过程调用)框架,主要关注于服务间的通信和调用,提供了服务注册发现、负载均衡、容错等功能。而Spring Cloud是一个完整的微服务解决方案,基于Spring生态系统,提供了更多关于微服务的支持,包括配置管理、服务网关、断路器、分布式追踪等。

\2. 生态和社区支持:Spring Cloud使用Spring作为基础框架,借助于Spring Boot进行快速开发和部署,具有庞大的社区和丰富的生态系统。Dubbo则是由阿里巴巴开发并维护的,在国内拥有广泛的用户和社区基础,但相较于Spring Cloud的全球化影响力稍逊一些。

\3. 服务注册和发现:Dubbo使用自己的注册中心来实现服务的注册和发现,例如Zookeeper或Nacos;而Spring Cloud则可以集成多种注册中心,如Eureka、Consul、Zookeeper等,也支持使用外部的服务注册和发现。

\4. 协议支持:Dubbo支持多种协议,包括Dubbo协议、HTTP协议和RMI协议等;而Spring Cloud主要使用HTTP协议进行服务间的通信,可以与其他RESTful服务相互操作。

\5. 适用场景:Dubbo更适用于大规模分布式系统中的服务调用,特别是在面对复杂的微服务架构时更具优势。Spring Cloud则更适用于基于Spring的现有应用程序和团队,能够更好地实现快速开发、部署和管理微服务。

总体而言,Dubbo注重解决服务之间的高性能通信和调用问题,而Spring Cloud提供了更全面的微服务解决方案,包括服务注册发现、配置管理、服务网关等。选择哪个框架取决于具体的需求和项目规模。。

185. Zookeeper 和 Dubbo 的关系?

Zookeeper和Dubbo是两个不同的框架,但它们在微服务架构中存在一定的关系和依赖。

Zookeeper是一个分布式协调服务,主要用于构建分布式系统的基础设施,提供了分布式锁、分布式队列等机制。它可以用于服务的注册与发现、配置中心等场景。Dubbo是一个高性能的Java RPC框架,用于提供服务的管理和调用机制。Dubbo的核心功能包括服务治理、负载均衡、容错机制等。

在微服务架构中,Dubbo可以借助Zookeeper实现服务的注册、发现、负载均衡和容错等功能。具体而言,Dubbo将自身作为一个服务,通过Zookeeper将自身注册到Zookeeper集群中,并在消费方从Zookeeper中发现并订阅服务。Zookeeper作为一个分布式协调工具,可以用于服务的注册与发现、配置中心等场景。

Dubbo和Zookeeper之间的关系是相互依存的:Dubbo依赖Zookeeper实现服务注册与发现,而Zookeeper也可以通过Dubbo来实现服务治理的能力。通过这种方式,Dubbo实现了服务治理,同时Zookeeper提供了一个强大的平台来支持Dubbo的服务治理能力。

综上所述,Zookeeper和Dubbo在微服务架构中各自发挥着重要的作用。Zookeeper提供了分布式协调和服务治理的基础设施,而Dubbo则是一个高性能的RPC框架,用于提供服务的调用和管理机制。它们可以一起使用,以构建高效、可靠的微服务架构。。

186. 简述一下什么是Nginx,它有什么优势和功能?

Nginx是一款高性能的HTTP和反向代理服务器,同时也提供了IMAP/POP3/SMTP服务。它是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点开发的,以轻量级、高效率、稳定性和扩展性强而闻名。

Nginx的优势包括:

高并发能力:Nginx采用异步事件驱动架构,能够处理大量的并发连接,具有很高的性能和吞吐量。

低内存消耗:Nginx本身占用较少的内存,并且可以通过内存池等技术进一步降低内存消耗。

高可靠性:Nginx具有高可靠性,因为它采用了非阻塞I/O模型和事件驱动架构,可以在不降低性能的情况下处理大量请求。

可扩展性:Nginx具有良好的模块化设计和可扩展性,可以通过添加第三方模块来扩展其功能。

易于配置和管理:Nginx的配置文件简洁明了,易于理解和配置。同时,它还提供了强大的监控和日志管理功能。

Nginx的功能包括:

HTTP服务器:Nginx可以作为HTTP服务器,提供静态文件服务和动态内容服务。

反向代理服务器:Nginx可以作为反向代理服务器,将客户端请求转发到后端服务器,并缓存响应结果以提高性能和可靠性。

负载均衡:Nginx可以作为负载均衡器,将请求分发到多个后端服务器,实现横向扩展和高可用性。

SSL加密通信:Nginx可以提供SSL加密通信功能,支持HTTPS协议,保护数据传输的安全性。

身份验证和授权:Nginx可以集成各种身份验证和授权机制,对请求进行身份验证和授权控制。。

187. Nginx是如何处理一个HTTP请求的呢?

Nginx处理一个HTTP请求主要分为以下几个阶段:

接收到HTTP请求:Nginx首先会接收到客户端的HTTP请求。

读取请求头部:Nginx读取HTTP请求的头部信息,包括请求方法、请求的URL和协议版本等。

路由和分发请求:根据请求的URL和配置规则,Nginx会选择将请求路由到相应的后端服务器或处理程序。

处理请求:对于静态资源的请求,Nginx可以直接从本地文件系统提供服务。对于动态内容的请求,Nginx可以调用相应的后端处理程序来生成响应内容。

合并响应头和内容:Nginx将响应头部信息和响应内容合并在一起,形成一个完整的HTTP响应。

发送响应给客户端:最后,Nginx将HTTP响应发送回客户端。

在整个处理过程中,Nginx还提供了各种功能,如负载均衡、缓存控制、SSL加密通信等,以增强其性能和可靠性。同时,Nginx还具有强大的配置和管理功能,可以根据实际需求进行灵活的配置和管理。。

188. 列举一些Nginx的特性?

Nginx是一款高性能、高可靠性的Web和反向代理服务器,具有丰富的特性和功能。以下是一些Nginx的特性:

高性能和扩展性:Nginx以其高效的处理能力而闻名,可以处理大量的并发请求,并具有优化的内存管理和缓存机制。同时,Nginx的设计使其易于扩展和适应各种规模和需求的网络环境。

模块化和可定制性:Nginx支持模块化架构,这意味着用户可以根据需要安装和管理自定义模块,以实现特定的功能和性能优化。

​ 负载均衡:Nginx可以作为负载均衡器使用,将请求分发到多个后端服务器上,从而实现高可用性和可扩展性。Nginx提供了多种负载均衡算法,如轮询、IP哈希等。

​ 反向代理:Nginx可以作为反向代理服务器,将客户端请求转发到后端服务器,并缓存响应结果以提高性能和可靠性。

SSL加密通信:Nginx可以提供SSL加密通信功能,支持HTTPS协议,保护数据传输的安全性。

身份验证和授权:Nginx可以集成各种身份验证和授权机制,对请求进行身份验证和授权控制。

​ 静态资源服务:Nginx可以作为静态资源服务器,提供对静态文件的直接服务,如HTML、图片等。

配置简单:Nginx的配置文件简洁明了,易于理解和配置。同时,Nginx还提供了丰富的配置选项和指令,以满足各种需求。

高可用性和稳定性:Nginx采取了分阶段资源分配技术,使得CPU与内存的占用率非常低。Nginx支持热部署,可以在不间断服务的情况下进行软件版本或者配置的升级。

低内存消耗:Nginx本身占用较少的内存,并且可以通过内存池等技术进一步降低内存消耗。

总之,Nginx是一款高性能、高可靠性的Web和反向代理服务器,具有丰富的特性和功能。它可以处理大量的并发请求,提供高效的静态和动态内容服务,并具有强大的负载均衡、缓存控制、SSL加密通信等能力。同时,Nginx还具有易于配置和管理、高可用性和稳定性等特点,是互联网架构中不可或缺的一部分。。

189. 请解释Nginx服务器上的Master和Worker进程分别是什么?

Nginx服务器上的Master进程和Worker进程是两个主要的进程类型,它们协同工作以实现Nginx服务器的功能。

Master进程:Master进程是Nginx服务器的主进程,有时也被称为Nginx的主进程。它主要负责启动和监控其他工作进程(即Worker进程),同时负责接收和分发请求。Master进程负责管理这些Worker进程的生命周期,包括它们的启动、停止、重启和升级等操作。此外,Master进程还负责与系统调度器交互,分配CPU资源给Worker进程,确保它们能够高效地处理请求。

Worker进程:Worker进程是Nginx服务器的工作进程,也被称为子进程。它们是实际处理HTTP请求的进程。每个Worker进程都有自己的工作线程,它们协同工作,对收到的请求进行处理。Worker进程负责接收和处理HTTP请求,执行相应的操作,如访问数据库、读取静态资源等。当一个Worker进程完成任务后,Nginx会创建一个新的Worker进程来替代它,以保持高可用性和高效率。

总的来说,Master进程和Worker进程是Nginx服务器中的核心组件,它们共同实现了Nginx的负载均衡、请求分发、高性能处理等功能。Master进程负责管理和管理Worker进程,而Worker进程则负责实际处理请求。这种设计使得Nginx能够高效地处理大量的并发请求,并保持高可用性和稳定性。

190. Redis 的数据类型以及底层数据结构?

Redis支持多种数据类型,每种类型都有不同的特点和用途。以下是Redis的数据类型及其底层数据结构:

\1. 字符串(String):

- 字符串类型是最基本的数据类型,可以存储任何类型的数据,如文本、整数或二进制数据。

- 底层数据结构是简单动态字符串(SDS),它是一个可变长度的字节数组。

\2. 列表(List):

- 列表类型是一个按插入顺序排序的字符串元素集合。

- 底层数据结构使用双向链表实现,使得在两端进行快速插入和删除操作。

\3. 集合(Set):

- 集合类型是一个无序且唯一的字符串元素集合。

- 底层数据结构使用哈希表实现,保证高效的元素查找和去重。

\4. 有序集合(Sorted Set):

- 有序集合类型是一个有序的字符串元素集合,每个元素关联了一个分数,用于排序。

- 底层数据结构采用跳跃表和哈希表的组合实现,使得元素的添加、删除和范围查询等操作都具有较高的性能。

\5. 哈希表(Hash):

- 哈希表类型是一个键值对的无序散列集合。

- 底层数据结构使用哈希表实现,可以快速地通过键进行查找、插入和删除操作。

\6. Bitmap:

- Bitmap类型是位数组,用于对大量位的设置、清除或查询操作。

- 底层数据结构使用字节数组实现,每个比特位代表一个状态。

\7. HyperLogLog:

- HyperLogLog类型用于进行基数估计,即统计集合中不重复元素的个数。

- 底层数据结构使用原位指令实现,占用固定的空间,但可能会有一定的误差。

191. 使用 Redis 有哪些好处?

\1. 高性能:Redis是基于内存的键值存储数据库,正因如此,它具有非常快速的读写操作。由于数据存储在内存中,而不是磁盘上,所以可以达到毫秒级的读写延迟,适用于对性能要求较高的应用。

\2. 丰富的数据结构:Redis提供多种丰富的数据结构(字符串、列表、集合、有序集合等),使得开发人员能够更灵活地处理不同类型的数据,并且能够根据具体的场景选择最合适的数据结构。

\3. 内置缓存功能:作为一个高效的缓存解决方案,Redis可以将常用的数据存储在内存中,从而加快数据访问速度,减轻后端数据库的负载。它还提供了过期时间设置、LRU算法等功能,可以有效管理缓存数据。

\4. 发布/订阅功能:Redis支持发布/订阅模式,允许不同的客户端之间进行实时消息传递。这在构建实时聊天应用、实时数据更新等场景下非常有用。

\5. 支持事务和原子操作:Redis支持事务,可以将多个命令打包成一个原子操作,保证操作的一致性。通过使用事务和原子操作,可以实现复杂的数据操作,同时保持数据的完整性。

\6. 数据持久化:Redis提供了两种持久化方式,即RDB快照和AOF日志。RDB快照可以定期将内存中的数据保存到磁盘上,而AOF日志则记录了对数据库的写操作,以便在重启时恢复数据。

\7. 可扩展性:Redis支持主从复制和集群模式,可以方便地进行水平扩展和高可用部署,提供更好的性能和容灾能力。

总之,Redis具有高性能、丰富的数据结构、缓存功能、发布/订阅、事务支持、数据持久化和可扩展性等优势,使其成为一种强大的缓存和数据存储解决方案,并被广泛应用于各种Web应用和分布式系统中。

192. Redis 的持久化机制是什么?各自的优缺点?

Redis提供了两种持久化机制,分别是RDB快照和AOF日志。

RDB快照:

原理:RDB快照通过将数据库在某个时间点的状态保存到磁盘上的二进制文件中来实现持久化。它会周期性地执行全量备份,或者根据配置选项触发自动备份。

优点:

快速恢复:RDB快照是将整个数据集写入磁盘,因此在恢复数据时非常快速。

文件紧凑:RDB文件采用二进制格式,相对较小,可以节省磁盘空间。

适用于大规模数据集:对于较大的数据集,RDB快照比AOF日志更高效。

缺点:

数据丢失:由于RDB快照是定期生成的,并且是全量备份,如果Redis在最近一次快照之后崩溃,那么最后一次快照与崩溃之间的修改数据将会丢失。

高延迟:生成RDB快照需要将整个数据集写入磁盘,可能会造成短暂的停顿,导致较高的延迟。

AOF日志(Append-Only File):

原理:AOF日志以追加的方式记录每个写操作,将所有对数据库的修改操作追加到一个只进行写入的文件中。在Redis重启时,可以通过重新执行AOF日志中的命令来还原数据。

优点:

数据安全性:AOF日志提供了更高的数据安全性,因为它记录了每个写操作,可以最大限度地减少数据丢失风险。

可读性:AOF日志是以文本格式保存的,易于理解和分析。

极低的数据丢失:由于AOF日志记录了每个写操作,即使Redis异常终止,也可以使用较小的AOF日志文件进行恢复,并尽量减少数据丢失。

缺点:

文件较大:相比RDB快照,AOF日志文件通常会更大,可能占用更多的磁盘空间。

恢复速度较慢:由于需要重新执行AOF日志中的所有写操作,AOF日志的恢复速度可能会比RDB快照慢一些。

193. redis 过期键的删除策略?Redis 的回收策略(淘汰策略)?

Redis中有两种过期键的删除策略:惰性删除和定期删除。

\1. 惰性删除:当客户端尝试访问一个已经过期的键时,Redis会立即将其删除。这种策略确保了过期键不会被访问到,但也带来了一些性能开销,因为每次访问都需要检查键是否过期。

\2. 定期删除:Redis会每隔一段时间(默认是每秒钟)随机检查一些过期键,并将其删除。这种策略通过将删除操作分摊到多个时间段,减少了每次访问的性能开销。但是,如果某个时间段内有大量的过期键需要删除,可能会导致Redis在这段时间内的性能下降。

对于回收策略(淘汰策略),Redis提供了以下几种选项:

\1. noeviction(默认):当内存不足以容纳新写入操作时,新写入操作会报错。这种策略保证了数据的完整性,但可能导致写入操作失败。

\2. allkeys-lru:当内存不足以容纳新写入操作时,在所有键中,优先移除最近最少使用的键。这种策略保留了最常用的数据,但可能导致一些冷数据被删除。

\3. allkeys-random:当内存不足以容纳新写入操作时,在所有键中,随机移除一些键。这种策略简单且随机,但可能导致一些重要的数据被删除。

\4. volatile-lru:当内存不足以容纳新写入操作时,在设置了过期时间的键中,优先移除最近最少使用的键。这种策略保留了最常用的数据,并且只会删除过期键。

\5. volatile-random:当内存不足以容纳新写入操作时,在设置了过期时间的键中,随机移除一些键。这种策略简单且随机,并且只会删除过期键。

\6. volatile-ttl:当内存不足以容纳新写入操作时,在设置了过期时间的键中,优先移除剩余时间最短的键。这种策略保留了剩余时间较长的数据,并且只会删除过期键。

194. Redis 的同步机制了解么?

Redis的同步机制主要有两种:主从复制和哨兵模式。

\1. 主从复制:主从复制是Redis最常用的同步机制。在主从复制中,一个Redis服务器作为主节点,而其他Redis服务器则作为从节点。主节点负责处理写操作,并将写操作的数据同步到从节点上。从节点只能进行读操作,并通过与主节点的通信来获取最新的数据。主从复制可以实现数据的备份和读写分离,提高系统的可用性和性能。

\2. 哨兵模式:哨兵模式是一种用于自动故障转移和高可用性的解决方案。在哨兵模式中,有一个或多个哨兵进程监控Redis服务器的状态。当主节点出现故障时,哨兵会自动将一个从节点升级为新的主节点,并将其他从节点重新配置为新的从节点。这样可以实现主节点的自动切换,保证系统的高可用性。

195. 是否使用过 Redis 集群,集群的原理是什么?

Redis集群是一种分布式存储方案,旨在通过多台Redis节点共同处理数据和请求,以提高系统的可用性和性能。

Redis集群的原理主要基于数据分片(Sharding)和节点间的通信与协作。在集群模式下,Redis会将数据划分为多个分片,每个分片存储在不同的节点上。客户端在执行写操作时,会根据一定的算法(如哈希槽)将数据路由到相应的节点上进行存储。同样,在执行读操作时,客户端也会根据相同的算法找到存储数据的节点,从而获取数据。

为了实现节点间的通信与协作,Redis集群使用了一种称为Gossip协议的通信机制。每个节点会定期向其他节点发送消息,以交换彼此的状态信息和数据分片情况。通过这种方式,每个节点都能了解整个集群的状态,从而可以正确地处理数据和请求。

此外,Redis集群还提供了数据冗余和故障恢复机制。每个数据分片都会有一个或多个副本节点,以确保数据的安全性。当主节点出现故障时,副本节点可以接管其数据和服务,从而保证集群的高可用性。

总之,Redis集群通过数据分片、节点间通信与协作以及数据冗余和故障恢复机制,实现了分布式存储和负载均衡,提高了系统的可用性和性能。

196. MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据?

以下是一些建议来实现这一目标:

数据预热:在应用程序启动或低峰时段,预先将预计会被频繁访问的数据加载到Redis中。这可以通过分析历史访问日志或预测模型来实现。

缓存失效策略:为Redis中的数据设置合适的失效时间。如果某个数据的访问频率突然下降,它会在一段时间后自动从Redis中移除,从而留出空间给其他热点数据。

LRU(Least Recently Used)策略:使用Redis的LRU策略来自动移除最近最少使用的数据。这可以确保Redis中始终存储的是最近被频繁访问的数据。

动态更新缓存:当某个数据在MySQL中的访问频率超过某个阈值时,可以将其动态地添加到Redis中。这需要对MySQL和Redis之间的数据进行监控和分析。

缓存降级策略:如果Redis中的数据量接近饱和,而又有新的热点数据需要加入,可以考虑采用降级策略。例如,可以移除一些较少访问的数据,或者根据数据的访问频率和重要性来动态调整其在Redis中的存储。

使用数据分析工具:利用数据分析工具对MySQL中的数据进行分析,找出访问频率最高的数据,并优先将这些数据缓存到Redis中。

监控和调优:持续监控Redis和MySQL的性能和数据访问情况,根据实际需求调整缓存策略和数据预热策略。

综上所述,确保Redis中的数据都是热点数据需要综合考虑多种因素,包括数据预热、缓存失效策略、LRU策略、动态更新缓存、缓存降级策略以及持续监控和调优。这些策略可以根据具体的应用场景和需求进行调整和优化。

197. Redis 最适合的场景?

Redis 最适合的使用场景包括但不限于以下几点:

数据缓存:Redis 是一个快速且可靠的数据结构存储系统,适合用作应用程序的缓存层。通过缓存经常访问的数据,可以减少对数据库的访问,提高应用程序的性能。

排行榜和消息队列:Redis 提供了一些内置的数据结构,如有序集合(sorted sets)和列表(lists),适用于实现排行榜和消息队列。这些数据结构提供了高效的插入、删除和查找操作,使得 Redis 在这些场景中表现优异。

分布式锁和计数器:在分布式系统中,Redis 可以作为分布式锁和计数器使用。通过使用 Redis 的原子操作,可以确保多个节点之间的数据一致性,实现高并发的分布式系统。

发布/订阅模式:Redis 支持发布/订阅模式,允许开发者实现实时消息传递和流处理。通过使用 Redis 的发布/订阅功能,可以实现实时应用程序,如聊天室、游戏等。

实时数据分析:Redis 提供了一些高级的数据结构,如哈希表和有序集合,适用于实时数据分析。通过将数据存储在 Redis 中,可以快速执行复杂的分析操作。

此外,Redis 还适用于许多其他场景,包括全文搜索、事务处理、数据持久化、数据共享和同步等。通过合理地使用 Redis,可以提高应用程序的性能、可靠性和可扩展性。

198. Kafka 与传统 MQ 消息系统之间有三个关键区别?

Kafka与传统MQ消息系统之间有三个关键区别:

分布式体系结构:Kafka被设计为一个分布式系统的消息传递平台,其分布式特性内置在核心中,允许用户轻松地将多个Kafka服务器构建成一个集群,以处理高吞吐量和容错性。而传统的消息系统虽然也可以部署在多台服务器上,但通常需要额外的配置和管理。

持久性和数据保留:Kafka采用了一种持久性日志(commit log)的存储方式,会将消息持久化到磁盘,并允许消息在一段时间内保留,以供后续消费或检索。这与传统消息系统的持久性不同,传统系统通常只能在消息被消费之前保留它们,一旦消息被消费,就无法再次获取。

高吞吐量和实时处理:Kafka支持高吞吐率,即使在非常廉价的商用机器上也能做到单机支持每秒10w条以上消息的传输,这在传统MQ消息系统中可能较难实现。此外,Kafka还支持实时的流式处理。

总结来说,Kafka的分布式体系结构、持久性和数据保留以及高吞吐量和实时处理使其与传统MQ消息系统相比具有显著的优势。然而,每种技术都有其适用的场景和限制,因此在选择消息系统时,需要根据具体的需求和场景来决定使用哪种技术。

199. 讲一讲 kafka 的 ack 的三种机制?

Kafka 的 ACK(Acknowledgment)机制是用于确认消息是否成功发送到 Kafka 服务器的机制。Kafka 支持三种 ACK 级别,分别是:

acks=0:生产者在发送消息后不会等待来自服务器的确认。这意味着消息可能会在发送之后丢失,生产者将无法知道它是否成功到达服务器。这种模式是最不可靠的,因为它没有利用任何来自服务器的确认。

acks=1:这是默认模式,也是一种折衷方式。在这种模式下,生产者会在消息发送后等待来自分区领导者(leader)的确认,但不会等待所有副本(replicas)的确认。这意味着只要消息被写入分区领导者,生产者就会收到确认。如果分区领导者成功写入消息,但在同步到所有副本之前宕机,消息可能会丢失。这种模式在可靠性方面有所提高,但仍然存在消息丢失的风险。

acks=all:这是最可靠的模式。在这种模式下,生产者会在消息发送后等待所有副本的确认。只有在所有副本都成功写入消息后,生产者才会收到确认。这确保了消息的可靠性,但会导致更长的延迟。因为需要等待所有副本的确认,所以这种模式可能会对性能产生一定的影响。

在选择合适的 ACK 级别时,需要根据对消息可靠性和性能的要求进行权衡。如果对消息的可靠性要求较高,可以选择 acks=all 级别,但需要考虑性能损耗。如果对消息的可靠性要求不高,可以选择 acks=1 或 acks=0 级别,以提高性能。

200. Kafka 如何保证消息不丢失?

​ Kafka通过多个机制来确保消息不丢失:

生产者确认机制:生产者发送消息时,可以选择同步(sync)或异步(async)发送。同步发送会等待服务器的确认,以确保消息被成功写入。此外,生产者还可以配置重试次数(retries),当发送失败时,会自动重新发送消息。

消息持久化:Kafka将消息持久化到磁盘,即使服务器宕机,重启后也能恢复数据。同时,Kafka提供了消息副本机制,每个分区都有多个副本,确保数据的可靠性。

副本同步:Kafka通过ISR(In-Sync Replicas)机制确保副本之间的数据同步。只有当ISR列表中的副本都同步了消息,消息才被认为是“已提交”的。此外,Kafka还提供了acks参数,允许生产者指定需要多少个副本确认消息写入才算成功。

消费者确认机制:消费者在处理消息时,可以选择自动提交偏移量(offset)或手动提交。自动提交可能导致消息丢失,因为它可能在消费者处理完消息之前就提交了偏移量。手动提交则允许消费者在消息处理成功后再提交偏移量,确保消息被正确处理。

日志压缩:Kafka支持日志压缩,可以删除旧的、不再需要的消息,以节省存储空间。这有助于确保Kafka集群的稳定运行,避免因存储空间不足而导致消息丢失。

综上所述,Kafka通过生产者确认机制、消息持久化、副本同步、消费者确认机制和日志压缩等多个机制来确保消息不丢失。这些机制共同提高了Kafka的可靠性和稳定性。

201. kafka 如何不消费重复数据?

使用消息偏移量(Offset)管理:Kafka 使用消息偏移量来唯一标识每条消息。消费者在消费消息时,可以保存已经消费过的消息偏移量,然后在消费新消息时,从上一次消费的偏移量开始,避免重复消费。消费者可以使用 Kafka 提供的 API 来提交消费的偏移量,从而实现精确的消费控制。

使用消费者组(Consumer Group)管理:Kafka 允许多个消费者以消费者组的形式同时消费同一个主题的消息。这样,不同的消费者组可以独立消费消息,互不干扰,避免了重复消费。

使用幂等性处理:应用程序的消费逻辑可以设计为幂等的,即使消息被重复消费,也不会导致副作用。通过在应用程序逻辑中实现幂等性,即使消息重复消费,也不会产生错误结果。

使用消息去重技术:可以通过在应用程序中维护一个已处理消息的记录或使用外部存储(如数据库)来实现消息的去重。在消费消息前,先检查该消息是否已经被处理过,如果已经处理过,则跳过该消息。

使用消息的唯一标识符:在每条消息中添加一个唯一标识符,并在应用程序中记录已经处理的标识符。通过检查消息的唯一标识符,可以确定是否已经处理过该消息,从而避免重复消费。

综上所述,避免 Kafka 中消费重复数据的方法有多种,可以根据具体的应用场景和需求选择合适的方法。同时,需要注意在使用 Kafka 时要保证消费者的可靠性和稳定性,以减少因消费者问题导致的重复消费。

202. K8s、Docker容器?

K8s(Kubernetes)和Docker容器是两种在现代化软件开发和运维中非常重要的技术。它们各自有独特的特性和用途,但经常一起使用来构建、部署和管理应用程序。

Docker容器:

Docker是一种开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间没有任何接口(类似iPhone的app)。几乎没有性能开销,可以很容易地在机器和虚拟机之间迁移。

Docker使用Linux内核的一些特性如cgroups(控制组)和namespaces(命名空间)来实现其容器化的功能。控制组用于隔离进程所能使用的资源(如CPU、内存、磁盘I/O等),而命名空间则用于将系统的一部分视为一个独立的空间,使得一个容器内的进程看不到其他容器的进程,也看不到主机系统的进程,从而实现了隔离。

K8s(Kubernetes):

Kubernetes是一个开源的容器编排系统,用于自动化部署、扩展和管理容器化应用程序。它提供了一组API,允许用户定义、部署和运行复杂的应用程序。Kubernetes可以管理大量的容器,使得它们像一个单一的、大型的、可扩展的计算资源一样工作。

Kubernetes提供了一系列的功能,包括服务发现、负载均衡、故障恢复、自动扩展等。它还提供了强大的抽象层,使得开发者可以忽略底层的基础设施细节,专注于他们的应用程序。

K8s与Docker容器的关系:

Docker容器是Kubernetes的基本构建块。Kubernetes使用Docker(或其他兼容的容器运行时,如containerd或CRI-O)来运行和管理容器。在Kubernetes中,用户首先会创建一个或多个Pod(Pod是Kubernetes的最小部署单元,每个Pod包含一个或多个容器)。然后,Kubernetes会根据用户的需求,自动调度这些Pod到合适的节点上运行,并监控它们的状态,确保它们始终处于预期的运行状态。

总的来说,Docker提供了容器化的技术,而Kubernetes则提供了管理和编排这些容器的平台。它们一起使用,可以极大地简化现代化应用程序的开发、部署和运维工作。

203. RabbitMQ 和 Kafka?

RabbitMQ和Kafka都是流行的消息队列系统,但它们在设计、使用场景和功能上存在一些明显的区别。

语言和设计哲学:RabbitMQ是使用Erlang语言开发的,强调可靠性、稳定性和易用性。它遵循AMQP(高级消息队列协议)标准,这使得它能够在不同的平台和语言之间实现互操作性。Kafka则是使用Scala和Java开发的,它更注重大数据处理和高吞吐量,设计目标是在分布式环境中提供实时的流式数据处理。

架构模型:RabbitMQ是一个以broker为中心的模型,它支持消息的确认机制,确保消息被成功消费。Kafka则是一个以consumer为中心的模型,它注重数据的持久性和容错性,没有消息确认机制。

吞吐量:RabbitMQ支持消息的可靠传递,但吞吐量相对较小,每秒可处理几万条消息。Kafka则以其高吞吐量而著称,每秒可处理十几万甚至更多的消息,非常适合处理大数据量。

容错和可用性:RabbitMQ支持消息的持久化存储,提供了镜像队列和消息存活时间(TTL)等功能,以提高系统的容错性和可用性。Kafka也支持消息的持久化存储,但它更注重数据的顺序性和容错处理,如通过复制和分区来实现容错。

路由和过滤:RabbitMQ提供了灵活的路由和过滤机制,可以根据消息的属性和内容来决定消息的路由和消费者。Kafka则更注重数据的流式处理,它的路由和过滤机制相对简单。

综上所述,RabbitMQ更适合用于需要高可靠性、稳定性和互操作性的场景,如金融、电商等;而Kafka则更适合用于需要处理大量数据、注重实时性和流式处理的场景,如日志收集、实时监控等。在选择消息队列系统时,需要根据具体的需求和场景来决定使用哪种技术。

204. 什么是死信队列?如何导致的?

DLX,全称为 Dead-Letter-Exchange,死信交换器,死信邮箱。当消息在一个队列中变成死信 (dead message) 之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为死信队列。

导致的死信的几种原因:

消息被拒(Basic.Reject /Basic.Nack) 且 requeue = false。

消息 TTL 过期。

队列满了,无法再添加。

205. 什么是延迟队列?RabbitMQ 怎么实现延迟队列?

延迟队列指的是存储对应的延迟消息,消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。

RabbitMQ本身是没有延迟队列的,要实现延迟消息,一般有两种方式:

通过RabbitMQ本身队列的特性来实现,需要使用RabbitMQ的死信交换机(Exchange)和消息的存活时间TTL(Time To Live)。

在RabbitMQ 3.5.7及以上的版本提供了一个插件(rabbitmq-delayed-message-exchange)来实现延迟队列功能。同时,插件依赖Erlang/OPT 18.0及以上。

也就是说,AMQP 协议以及RabbitMQ本身没有直接支持延迟队列的功能,但是可以通过TTL和DLX模拟出延迟队列的功能。

206. RabbitMQ如何保证消息的可靠性?

RabbitMQ通过以下几种机制来保证消息的可靠性:

消息持久化:RabbitMQ可以将消息持久化到磁盘,以确保即使在系统崩溃或重启的情况下,消息也不会丢失。要实现这一点,需要在发送消息时将消息的delivery_mode属性设置为2,以及在创建队列时将durable参数设置为true。

消息确认机制:RabbitMQ支持消息确认机制,即消费者在处理完消息后需要显式地发送一个确认信号给RabbitMQ,告诉它消息已经被成功处理。如果消费者在处理消息时崩溃或无法发送确认信号,RabbitMQ会将该消息重新放入队列中,以便其他消费者可以处理它。这种机制可以确保消息不会被丢失或重复处理。

生产者确认模式:RabbitMQ提供了两种生产者确认模式:confirm模式和return模式。在confirm模式下,当消息被成功路由到一个或多个队列时,RabbitMQ会发送一个确认给生产者。在return模式下,如果消息无法被路由到任何队列,RabbitMQ会返回一个错误给生产者。这两种模式都可以帮助生产者确保消息被正确地发送到RabbitMQ中。

死信队列:当消息无法被消费者正常消费时,RabbitMQ可以将这些消息发送到一个特殊的队列(称为死信队列)。这样,即使消费者出现问题,也不会导致消息丢失。开发者可以监听这个队列,并对这些无法消费的消息进行处理。

镜像队列:在分布式环境中,RabbitMQ支持镜像队列,即队列可以在多个节点上进行复制。这样,即使某个节点出现故障,其他节点上的队列副本仍然可以提供服务,确保消息的可靠性。

综上所述,RabbitMQ通过消息持久化、消息确认机制、生产者确认模式、死信队列和镜像队列等多种机制来确保消息的可靠性。这些机制共同提高了RabbitMQ在处理消息时的稳定性和可靠性。

;