Java常见面试题

目录

1、mysql并发事务会带来哪些问题,如何解决?

MySQL的并发事务可能会带来以下几个主要问题:

脏读(Dirty Read):当一个事务读取另一个未提交的事务的数据时,可能会读取到不一致的数据。
不可重复读(Non-repeatable Read):当一个事务在多次读取同一数据时,可能会因为另一个并发的事务修改了数据而导致读取到的结果不一致。
幻读(Phantom Read):当一个事务在多次读取某一范围的数据时,可能会因为另一个并发的事务插入了新的数据而导致读取到的结果不一致。
死锁(Deadlock):当两个或更多的事务互相等待对方释放资源时,会导致事务无法继续执行。
性能问题:高并发下,如果事务处理不当,可能会导致数据库性能下降,影响整个系统的性能。

解决方案:

脏读:可以通过设置事务的隔离级别为READ COMMITTED或更高级别来避免脏读。READ COMMITTED级别会确保只读取已经提交的事务的数据。
不可重复读和幻读:可以通过设置事务的隔离级别为REPEATABLE READ或SERIALIZABLE来避免不可重复读和幻读。REPEATABLE READ级别会确保在一个事务内多次读取同一数据时,结果是一致的。SERIALIZABLE级别是最严格的隔离级别,会确保并发事务之间互不干扰。
死锁:可以通过设置合适的锁策略和锁超时时间来避免死锁。例如,可以设置锁的获取顺序,避免循环等待;可以设置锁的超时时间,避免无限等待。
性能问题:可以通过优化数据库设计、使用索引、减少锁的使用、使用乐观锁等方式来提高并发事务的性能。
总的来说,正确地处理并发事务需要理解它们可能带来的问题,并根据具体的应用场景和需求选择合适的解决方案。

2、请详细描述Redis持久化机制?

Redis的持久化机制指的是将Redis内存中的数据保存到硬盘中,以便在服务器重启或者宕机后可以快速地恢复数据。

Redis支持两种持久化方式:RDB(Redis DataBase)和AOF(Append Only File)。

RDB持久化机制: 是在指定的时间间隔内生成数据集的快照(Snapshot)。RDB持久化可以在指定的时间间隔内生成数据快照,这个时间间隔通常可以由用户在配置文件中进行配置。当Redis需要持久化时,它会 fork 出一个子进程,子进程会将内存中的数据写入硬盘中的一个临时文件,当持久化过程完成后,子进程会退出。你可以在配置文件中通过设置rdb_save_time_interval参数来控制RDB快照的生成时间间隔。

AOF持久化机制: 是记录Redis的所有写操作到磁盘中。当Redis重启时,可以通过执行AOF文件中的命令来恢复数据集。AOF持久化比RDB持久化更加实时,可以保证数据的实时备份和持久化。你可以在配置文件中通过设置aof_enabled参数来开启或关闭AOF持久化,aof_filename参数来指定AOF文件的名称,aof_interval参数来控制AOF文件的写入时间间隔,aof_no_checksum参数来禁用或启用AOF文件的checksum。

在什么情况下选择使用RDB或AOF持久化,需要根据具体的使用场景和需求进行选择。如果你的应用需要备份大规模的数据,并且需要快速地恢复数据,那么RDB持久化可能更加适合。如果你的应用需要实时备份数据,并且需要持久化的数据比较小,那么AOF持久化可能更加适合。

3、简述Redis缓存雪崩和缓存穿透的问题和解决方案?

缓存雪崩: 是指当缓存中大量数据同时过期时,这些请求会同时转发到数据库,导致数据库压力突然增大,就像雪崩一样。这种情况通常是由于缓存策略不当所导致的,例如缓存时间设置相同或相近,导致缓存集体失效。

缓存穿透: 是指查询一个不存在的key,由于缓存和数据库中都没有这个key,因此每次请求都会直接查询数据库,导致数据库压力增大。这种情况通常是由于恶意攻击或者数据一致性校验不严谨所导致的。

方式解决:

对应缓存雪崩:在缓存策略中引入随机过期时间,避免大量缓存同时失效。例如,在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
使用互斥锁,当缓存失效时,不是立即查询数据库,而是先使用互斥锁等待一段时间,如果缓存在此期间被更新,则重新获取缓存数据;如果没有被更新,则再查询数据库。

对于缓存穿透:使用布隆过滤器,将存储的数据放入布隆过滤器中,每次数据查询首先查询布隆过滤器,如果过滤器中判断存在,再到缓存查询,如果没有,再进入数据查询。这样能大大减轻数据库查询压力。
缓存空对象,当数据库数据不存在时,将返回的空对象缓存起来,同时设置一个过期时间。这样在访问数据时,将从缓存中获取,从而保护了数据库。但要注意对空值设置过期时间,否则会出现数据更新后缓存数据未及时更新的问题。
对热点数据进行永不过期处理,即不设置热点数据的过期时间。但这只适用于热点数据不会频繁更新的情况。

4、RabbitMQ消息丢失及对应解决方案

(1) RabbitMQ事务机制和confirm模式:

生产者在发送数据之前开启RabbitMQ事务机制,如channel.txSelect,然后发送消息。如果消息没有成功被RabbitMQ接收到,那么生产者会收到异常报错,此时可以回滚事务channel.txRollback,然后重试发送消息。如果收到了消息,那么可以提交事务channel.txCommit。这种机制类似于数据库事务机制,可以保证消息的可靠性传输。另外,开启confirm模式,生产者每次写的消息都会分配一个唯一的ID,如果写入了RabbitMQ中,RabbitMQ会回传一个ack消息,告诉生产者这个消息已经收到,这样可以避免消息丢失。

(2) Broker消息中间件自身丢失消息解决方案:

创建队列时设置队列持久化。在创建队列时,将持久化参数设置为true,这样可以保证RabbitMQ持久化queue的元数据,即使是RabbitMQ自身出现问题,恢复之后也会自动读取之前存储的数据。
设置消息的deliveryMode为2。在发送消息时,将deliveryMode设置为2,这样RabbitMQ会将消息持久化到磁盘上,即使RabbitMQ出现问题,再次重启后也会从磁盘上恢复queue和里面的数据。
以上是关于RabbitMQ消息丢失及对应解决方案的相关内容,希望能对你有所帮助。

5、什么叫线程安全?举例说明

线程安全是指多线程环境下,一个对象或函数能够在多个线程同时访问的情况下,保证其行为的一致性和正确性。线程安全的主要目标是避免数据竞争和保证并发操作的正确性。

例如,一个ArrayList类在添加一个元素时,可能有两步操作:一是在Items[Size]的位置存放此元素,二是增大Size的值。在单线程环境下,由于操作是顺序执行,所以没有问题。但是在多线程环境下,如果两个线程同时执行这两步操作,可能会产生问题。

在这种情况下,一种解决方法是使用同步机制,例如synchronized关键字或者Lock对象,来确保在任何时刻只有一个线程可以执行ArrayList类的添加元素操作,这样就可以避免数据竞争和并发操作的错误。这就是线程安全的一种实现方式。

6、举例说明常用的加密算法

AES加密算法:全称Advanced Encryption Standard,也就是高级加密标准,这是一种被广泛使用的加密算法,可以有效保护敏感信息。
RSA加密算法:这是一种非对称加密算法,使用公钥和私钥两个密钥,公钥用于加密数据,私钥用于解密数据,非常安全,广泛用于数据传输等场景。
DES加密算法:全称Data Encryption Standard,也就是数据加密标准,这是一种对称加密算法,被广泛用于保护敏感信息,但由于其密钥长度较短,现在已经被认为不够安全。
SHA-1和SHA-256加密算法:SHA是安全哈希算法,主要用于生成消息摘要,也就是将输入的数据通过哈希算法生成一个固定长度的字符串,无法逆向生成原始数据。SHA-1和SHA-256是SHA算法的不同版本,SHA-256因为其安全性更高而被更广泛使用。
RSA-SHA256混合加密算法:这是一种将SHA-256和RSA两种算法结合使用的加密方式,先用RSA算法生成一个签名,然后用SHA-256对数据进行摘要处理,同时用RSA公钥对SHA-256生成的摘要进行加密,可以同时实现签名和加密双重保护。
MD5加密算法:MD5的全称是Message-Digest Algorithm 5,它可以将任意长度的“字节串”变换成一个128位的大整数,并且它是一个不可逆的字符串变换算法。即使你看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串。

7、synchronized和ReentrantLock有什么区别?

(1) 用法的不同:synchronized是Java语言内置的关键词,由Java语言自动支持;而ReentrantLock是java.util.concurrent.locks包中的类,需要手动创建对象进行使用。

(2)锁的获取和释放:synchronized是隐式获取和释放锁,而ReentrantLock需要显式地调用方法来获取和释放锁。

(3)线程中断响应:synchronized不支持线程中断,当线程处于synchronized块中时,不能响应中断;而ReentrantLock支持线程中断,可以在锁被持有期间响应中断。

(4)锁的级别:synchronized是JVM级别的,而ReentrantLock是API级别的。synchronized是Java语言内置的,所有的Java代码都可以使用;而ReentrantLock需要手动实现,使用起来相对较复杂。

(5)公平性:synchronized不具有公平性,无法保证锁的获取顺序;而ReentrantLock可以通过构造函数参数来设定公平性,有公平锁和非公平锁两种模式。

(6)等待可中断性:synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。

(7)锁的底层实现:synchronized和ReentrantLock底层实现方式不同。synchronized是使用乐观并发策略实现的,而ReentrantLock是使用悲观并发策略实现的。

参考:synchronized和ReentrantLock有什么区别

8、synchronized和lock的区别

区别:

1.synchronized是关键字,Lock是接口;

2.synchronized是隐式的加锁,lock是显式的加锁;

3.synchronized可以作用于方法上,lock只能作用于方法块;

4.synchronized底层采用的是objectMonitor,lock采用的AQS;

5.synchronized是阻塞式加锁,lock是非阻塞式加锁支持可中断式加锁,支持超时时间的加锁;

6.synchronized在进行加锁解锁时,只有一个同步队列和一个等待队列, lock有一个同步队列,可以有多个等待队列;

7.synchronized只支持非公平锁,lock支持非公平锁和公平锁;

8.synchronized使用了object类的wait和notify进行等待和唤醒, lock使用了condition接口进行等待和唤醒(await和signal);

9.lock支持个性化定制, 使用了模板方法模式,可以自行实现lock方法;

参考:synchronized和lock的区别

9、如何保证接口的幂等性

幂等性:指的是同一操作执行多次时,结果是相同的,不会产生副作用或重复操作。

  • 在接口中添加幂等性检查逻辑。例如,对于更新操作,可以在接口中检查传入的参数是否与当前资源的状态一致,如果不一致则返回错误。
  • 使用乐观锁或者悲观锁来防止并发操作导致的问题。乐观锁通过在数据行中添加一个版本号来实现,每次更新都会增加版本号。悲观锁则是通过锁定数据行来实现,只有一个线程可以执行更新操作。
  • 在数据库层面保证幂等性。例如,使用数据库的唯一约束来保证创建操作的幂等性。
  • 使用令牌桶或者漏桶算法限制接口的调用频率,防止由于客户端的重复请求导致的问题。
  • 对于需要多次请求的操作,可以使用分布式事务来保证操作的原子性。

参考:怎么保证 java 语言接口的幂等性?

10、什么是分布式事务,如何解决

分布式事务:在分布式系统中一次操作需要由多个服务协同完成,这种由不同的服务之间通过网络协同完成的事务称为分布式事务。例如:小明给张三转账100,A服务器上要先去A数据库扣100,然后B服务器上B数据库加100,两个操作要么都成功,要么都失败。

常见的分布式事务解决有:TCC、本地事务表、MQ事务消息等。

参考:七种常见分布式事务详解

11、HashMap的结构和原理是什么

HashMap 基于 Map 接口实现,元素以键值对的方式存储,并且允许使用null建和null值,因为key不允许重复,因此只能有一个键为 null,另外 HashMap 不能保证放入元素的顺序,它是无序的,和放入的顺序并不能相同。HashMap是线程不安全的。

jdk1.7 底层实现是 数组 + 链表

jdk1.8 底层实现是 数组+链表+红黑树 。如果哈希表单向链表中元素超过8个,那么单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6个,会重新把红黑树变成单向链表数据结构。

参考:HashMap底层实现原理解析

12、CurrentHashMap是如何保证线程安全的

锁分段技术:HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,同个对象锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。ConcurrentHashMap使用分段锁Segment来保护不同段的数据,Segment继承ReentrantLock实现锁的功能。

参考:java——CurrentHashMap

ConcurrentHashMap是如何保证线程安全的

13、java线程池有哪些参数,都有什么含义

参考:线程池七大参数

14、java 中的volatile关键字是干嘛的

在 Java 中,volatile 是一个关键字,用于声明变量。volatile 变量的作用是确保被声明的变量在多个线程之间的可见性、有序性和禁止重排序,从而实现线程安全的操作。

具体来说,当一个变量被声明为 volatile 时,volatile 变量的写操作会立即刷新到主内存,而读操作会从主内存中读取最新值,而非从本地线程缓存中读取。

需要注意的是,虽然 volatile 变量可以保证可见性和禁止指令重排,但它并不能保证线程安全,因为它只保证了可见性和有序性,而不保证原子性。对于一些需要多个操作才能完成的操作,比如 i++ 这样的自增操作,就不能简单地使用 volatile关键字来保证线程安全,需要使用更为严格的同步机制,比如synchronized 关键字或 Lock 接口。

15、java中垃圾收集的方法有哪些

(1)、标记-清除

这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次GC动作。

(2)、复制算法

为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。 于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那份内存交Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中。(java堆又分为新生代和老年代)

(3)、标记-整理

该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。

(4)、分代收集

现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。

参考:堆内存分配及回收策略
JVM的4种垃圾回收算法

16、什么是类加载器,类加载器有哪些

类加载器是实现通过类的权限定名获取该类的二进制字节流的代码块。在Java中,类加载器主要有四种类型:

  • 启动类加载器:负责加载Java核心类库,无法被Java程序直接引用。
  • 扩展类加载器:类似于加载Java的扩展库,Java虚拟机提供一个扩展库目录,该类加载器在此目录里面查找并加载Java类。
  • 系统类加载器:根据Java应用的类路径来加载Java类,主要是加载自己写的那些类,可以通过ClassLoader.getSystemClassLoader()来获取它。
  • 自定义加载器:用户自己定义的类加载器。

17、什么是双亲委派

双亲委派(双亲优先)是Java类加载器的一种工作机制,用于保护Java程序的安全性和稳定性。

根据双亲委派模型,当一个类加载器需要加载某个类时,它首先将这个任务委派给它的父类加载器,只有当父加载器无法加载时,才由该加载器自己尝试加载。如果都无法加载,会委派给引导类加载器(Bootstrap Class Loader)进行加载。

双亲委派的好处在于,它能够保证Java虚拟机中的类的一致性和安全性,避免了不同类加载器之间可能出现的冲突和错误。同时,双亲委派模型也提高了Java虚拟机的性能和效率,因为它避免了不必要的类加载和重复加载。

18、抽象类和接口有什么区别

  • 定义:抽象类是一个类,可以有普通方法和抽象方法,其中抽象方法必须被子类实现;接口是一组抽象方法的集合,所有方法都是抽象方法,且没有具体实现。

  • 实现:子类只能继承一个抽象类,但可以实现多个接口。

  • 构造函数:抽象类可以有构造函数,接口没有构造函数。

  • 变量:抽象类可以有变量,接口只能定义常量。

  • 访问控制:抽象类中的方法可以是public、protected和default访问控制,而接口中的方法默认是public。

  • 默认实现:抽象类可以有普通方法的默认实现,而接口中所有的方法都没有默认实现。

  • 继承:子类继承抽象类时必须实现其中的抽象方法,否则该子类也必须是抽象类;子类实现接口时必须实现其中的所有方法。

抽象类是对类抽象,接口是对行为抽象。如果一个类具有一些共同的属性和行为,那么可以将这些共同的属性和行为放到抽象类中,让子类继承并实现其中的抽象方法。如果一个类只是具有一些共同的行为,那么可以将这些共同的行为定义到接口中,让实现该接口的类来实现其中的方法。

19、如何解决缓存和数据库双写数据一致性问题

每次读取数据:
如果读缓存里的数据,直接读缓存的;
如果缓存里没有数据 ,则从数据库里面读数据,并将数据更新到缓存;
将读取到的数据塞入到缓存中,下次读取时,就可以直接命中。
再来看一下写请求,规则是“先更新 db,再删除缓存”,详细步骤如下:

更新数据:
先更新数据库的数据,再删除缓存的数据;

极端情况的问题:

上面的这种策略在极端情况下也会存在并发问题么,只不过概率比较小,比如:

(1) 缓存刚好失效
(2) 请求 A 读取数据,缓存里没读取到,然后从数据库读取,得一个 a 值,并开始将 a 更新到缓存,但因为网络问题一直阻塞
(3) 请求 B 更新数据,将新值 b 写入数据库,然后删除缓存里的数据
(4) 此时 A 网络变好,不再阻塞,将 a 更新到了缓存
(5) 此时数据库里是 b ,而缓存里是 a ,就不一致了

如果对数据一致性和实时性要求比较高的话,可以基于消息队列的重试机制。具体来说,就是把操作缓存,或者操作数据库的请求暂存到队列中,通过消费队列来重新处理这些请求,因此上面的问题可以把所有更新缓存和删除缓存的操作加入到消息队列里,消息队列可以保证其有序的执行。

因此上面极端情况的解决方式:
(1) 缓存刚好失效
(2) 请求 A 读取数据,缓存里没读取到,然后从数据库读取,得一个 a 值,把 a 更新到缓存的这步操作加入到消息队列 Q 中
(3) 请求 B 更新数据,将新值 b 写入数据库,把从缓存里删除数据这步操作加入到消息队列 Q 中
(4) 消息队列 Q中将安装放入队列的顺序依次执行,从而确保一致性;

20、Arrays中sort排序运用了哪几种排序方式

(1)如果数组元素个数小于47,那么使用改进的插入排序进行排序,
(2)如果元素个数大于插入排序的阈值并且小于快速排序的阈值 286,则使用改进的双轴快速排序进行排序,
(3)如果元素个数大于快速排序的阈值,根据数组的无序程度来判定继续使用哪种算法,无序程度通过将数组划分为不同的有序序列的个数来判定,如果有序序列的个数大于 67,则认为原数组基本无序,则仍然使用双轴快速排序,如果小于MAX_RUN_COUNT,则认为原数组基本有序,使用归并排序进行排序。

Arrays中sort方法的黑科技

21、怎么判断一个对象能不能回收(如何判断对象“已死”)

引用计数法
对象持有一个引用计数器,当对象被引用时+1,当引用失效时-1,任何时刻对象引用计数器的值为0时,对象被认定为不可使用状态。 优点:原理简单。判定效率高 缺点:对象之间互相引用,会导致对象的引用计数器一直不为0,从而永远不被认定为不可使用状态。

可达性分析算法
首先会有一个 称之为“GC ROOT”的根对象作为起点,我们创建的对象的引用都会链接到“GC ROOT”上,可以想象下,这就是一棵树,“GC ROOT”就是树顶,创建对象的引用根据引用关系在下面开枝散叶。当引用对象到“GC ROOT"之间的引用链断开时,说明该对象不可达,即认定为不可被使用状态。

JVM的垃圾回收过程

22、Java对象一定是在堆上进行分配的吗

不一定,有可能在栈上分配。
对象栈上分配:我们通过JVM内存分配可以知道JAVA中的对象都是在堆上进行分配,当对象没有被引用的时候,需要依靠GC进行回收内存,如果对象数量较多的时候,会给GC带来较大压力,也间接影响了应用的性能。为了减少临时对象在堆内分配的数量,JVM通过逃逸分析确定该对象不会被外部访问。如果不会逃逸可以将该对象在栈上分配内存,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力。

对象逃逸分析: 就是分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中。

参考:Java对象一定是在堆上进行分配的吗?

23、HashMap是怎么解决哈希冲突的

通常解决hash冲突的方法有 4 种:
(1)开放定址法,也称为线性探测法,就是从发生冲突的那个位置开始,按照一定的次序从hash表中找到一个空闲的位置,然后把发生冲突的元素存入到这个空闲位置中。ThreadLocal就用到了线性探测法来解决hash冲突的。

(2)链式寻址法,这是一种非常常见的方法,简单理解就是把存在hash冲突的key,以单向链表的方式来存储,比如HashMap就是采用链式寻址法来实现的。

(3)再hash法,就是当通过某个hash函数计算的key存在冲突时,再用另外一个hash函数对这个key做hash,一直运算直到不再产生冲突。这种方式会增加计算时间,性能影响较大。

(4)建立公共溢出区, 就是把hash表分为基本表和溢出表两个部分,凡是存在冲突的元素,一律放入到溢出表中。

HashMap在JDK1.8版本中,通过链式寻址法+红黑树的方式来解决hash冲突问题,其中红黑树是为了优化Hash表链表过长导致时间复杂度增加的问题。当链表长度大于8并且hash表的容量大于64的时候,再向链表中添加元素就会触发转化。

参考:HashMap是怎么解决哈希冲突的

24、Minor GC和Full GC的区别

Minor GC : 又称为新生代 GC ,指的是发生在新生代的垃圾收集。因为 Java对 象大多都具备朝生夕灭的特 性,因此Minor GC(采用复制算法)非常频繁,一般回收速度也比较快。

Full GC : 又称为 老年代 GC 或者 Major GC ,指发生在老年代的垃圾收集。出现了 Major GC ,经常会伴随 至少一次的Minor GC (并非绝对,在 Parallel Scavenge 收集器中就有直接进行 Full GC 的策略选择过程)。 Major GC 的速度一般会比Minor GC 慢 10 倍以上。老年代:标记-整理算法(清理的时候做内存移动)

25、static加载顺序

(1)含有static的静态代码块和类变量先执行,执行一次之后就不再需要加载,静态代码块和类变量没有特定的先后顺序,按照代码顺序执行

(2)如果类之间存在继承关系,先加载父,在加载子的内容

(3)单独一个类的加载顺序应该是,静态代码块(类变量)--------成员变量-----------构造方法

参考:java static加载顺序

26、高并发项目中,有那些措施可以保证项目的高并发和高可用性

(1)线程池:
使用java.util.concurrent.ExecutorService和Executors来创建和管理线程池,从而避免频繁地创建和销毁线程,提高系统性能。
根据系统负载和工作性质,合理设置线程池的大小和类型(如固定线程池、缓存线程池等)。
(2)并发集合:
使用并发集合类(如ConcurrentHashMap、CopyOnWriteArrayList等)来避免在多线程环境下对集合的并发修改导致的数据不一致问题。
(3)异步处理:
使用Java的异步编程模型(如CompletableFuture)来执行耗时的操作,避免阻塞主线程。
通过回调函数或响应式编程模型来处理异步操作的结果。
(4)分布式系统:
搭建分布式环境,将系统拆分成多个独立的服务或组件,通过网络进行通信。
使用微服务架构,每个服务都可以独立扩展和部署,提高系统的可伸缩性和可靠性。
(5)负载均衡:
使用负载均衡技术(如Nginx、HAProxy等)将请求分发到多个服务器上,避免单个服务器过载。
可以通过硬件负载均衡器或软件负载均衡器来实现。
(6)缓存策略:
使用缓存技术(如Redis、Memcached等)来减少数据库访问次数,提高系统性能。
合理设计缓存策略,如缓存失效时间、缓存更新策略等。
(7)数据库优化:
优化SQL语句,减少全表扫描、避免使用子查询和复杂的连接操作等。
使用数据库索引来提高查询效率。
读写分离,将读写操作分散到不同的数据库或服务器上。
分库分表,当单个数据库无法满足并发需求时,将数据库拆分成多个数据库或表。
(8)服务治理与限流:
使用服务治理框架(如Spring Cloud、Dubbo等)来管理微服务之间的通信和依赖关系。
实施限流策略,如令牌桶算法、漏桶算法等,保护系统免受流量冲击。
(9)监控与告警:
建立完善的监控体系,实时监控系统的各项指标(如CPU使用率、内存占用、请求响应时间等)。
设置告警阈值,当系统出现异常或性能瓶颈时及时发出告警通知相关人员进行处理。
(10)水平扩展与垂直扩展:
根据系统需求,选择合适的扩展策略。水平扩展通过增加服务器数量来扩展系统处理能力;垂直扩展通过增加单个服务器的资源来提升性能。
(11)使用消息队列:
使用消息队列(如Kafka、RabbitMQ等)来实现异步通信和解耦,将请求放入队列中由消费者异步处理。
(12)代码优化:
审查和优化代码,避免不必要的计算和IO操作,减少资源消耗和响应时间。
使用合适的算法和数据结构来提高代码的执行效率。
(13)压力测试与性能调优:
进行压力测试和性能分析,找出系统的瓶颈和潜在问题。
根据测试结果进行性能调优和优化。

27、数据库中什么情况会导致索引失效

在数据库中,有多种情况可能导致索引失效,以下是可能导致索引失效的一些常见原因:

(1)在索引列上使用函数:当在查询条件中对索引列使用函数(如SUBSTR、DECODE、INSTR等)时,数据库可能无法直接利用索引进行查询优化,从而导致索引失效。

(2)索引列的数据类型不匹配:如果查询条件中的数据类型与索引列的数据类型不匹配,数据库可能无法利用索引进行查询优化。例如,当索引列是日期类型,而查询条件中使用了字符串类型的数据时,索引可能会失效。

(3)索引列的数据值重复度高:如果索引列的数据值非常集中,例如大部分数据都是某个固定值,那么索引的区分度就很低,可能导致数据库优化器选择不使用索引。

(4)不满足最左前缀规则:在使用复合索引时,如果查询条件没有使用复合索引的最左侧列,或者没有按照索引的顺序使用列,那么索引可能会失效。
使用范围查询且非最左列:当在复合索引的非最左列上使用范围查询(如>、<、BETWEEN等)时,索引可能会失效。

(5)LIKE语句以通配符开头:当使用LIKE语句进行模糊查询,并且通配符(%)位于查询字符串的开头时,索引可能会失效。

(6)统计信息不准确:如果数据库的统计信息(如数据分布、数据大小等)不准确,那么数据库优化器可能无法正确选择索引,导致索引失效。

(7)索引选择不当:在创建索引时,如果没有根据实际的查询需求选择合适的索引,或者创建了多个重复的索引,那么可能导致索引失效。

(8)数据量过小:对于数据量非常小的表,即使创建了索引,数据库优化器也可能选择全表扫描而不是使用索引,因为全表扫描的开销相对较小。

(9)索引列上的计算或类型转换:当在索引列上进行计算或类型转换时,数据库可能无法直接利用索引进行查询优化,从而导致索引失效。

为了解决索引失效的问题,可以采取以下措施:

(1)将函数操作提前,将结果存入一个新的列中,并在新列上建立索引。
(2)使用合适的条件表达式,尽量避免使用不等于操作符。
(3)确保查询条件与索引列类型匹配。
(4)根据实际情况选择合适的索引类型。
(5)对表的结构进行优化,如增加冗余列、分拆表等。
(6)在一些特殊情况下,可以使用索引提示来指导数据库优化器选择索引。

28、负载均衡策略

负载均衡策略是在多个计算机、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。以下是一些常见的负载均衡策略:

(1)轮询(Round Robin):按照顺序将每个新的请求分发给后端服务器,依次循环。这是一种最简单的负载均衡策略,适用于后端服务器的性能相近,且每个请求的处理时间大致相同的情况。

(2)随机选择(Random):随机选择一个后端服务器来处理每个新的请求。这种策略可用于确保来自同一客户端的请求都被发送到同一台后端服务器,适用于需要会话保持的情况。

(3)加权轮询(Weighted Round Robin):给每个后端服务器分配一个权重值,然后按照权重值比例来分发请求。这可以用来处理后端服务器性能不均衡的情况,将更多的请求分发给性能更高的服务器。

(4)加权随机选择(Weighted Random):与加权轮询类似,但是按照权重值来随机选择后端服务器。这也可以用来处理后端服务器性能不均衡的情况,但是分发更随机。

(5)最少连接法(Least Connections):将请求分配给当前连接数最少的服务器。这种策略可以确保每台服务器的负载相对均衡,适用于服务器性能相近但请求处理时间可能不同的情况。

这些策略各有优缺点,适用于不同的应用场景。在选择负载均衡策略时,需要考虑后端服务器的性能、请求的特性和业务需求等因素。此外,还需要考虑负载均衡器的硬件和软件配置、网络环境和安全性等方面的因素。

29、ElasticSearch原理

ElasticSearch介绍
elasticsearch数据更新与删除机制

30、线程池参数及配置

corePoolSize=>    线程池里的核心线程数量
maximumPoolSize=> 线程池里允许有的最大线程数量
keepAliveTime=>   空闲线程存活时间
unit=>            keepAliveTime的时间单位,比如分钟,小时等
workQueue=>       缓冲队列
threadFactory=>   线程工厂用来创建新的线程放入线程池
handler=>        线程池拒绝任务的处理策略,比如抛出异常等策略

线程池按以下行为执行任务
    1. 当线程数小于核心线程数时,创建线程。
    2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
    3. 当线程数大于等于核心线程数,且线程数小于等于最大核心数,且任务队列已满,会创建线程执行
    4. 当线程数大于等于核心线程数,且任务队列已满
        -1 若线程数小于最大线程数,创建线程
        -2 若线程数等于最大线程数,抛出异常,拒绝任务

参考:线程池参数及配置

31、软件设计七大原则

开闭原则、依赖倒置原则、里氏替换原则、单一职责原则、接口隔离原则、迪米特法则、合成复用原则

参考:
软件设计七大原则
软件设计原则的重要性

32、redis集群的三种模式

主从复制、哨兵模式、Cluster集群

参考:
redis集群的三种模式
Redis三大集群模式
Redis三种集群模式介绍及搭建

33、MySQL 中有哪 7 种日志

错误日志(errorlog)、一般查询日志(general log)、慢查询日志(slow query log)、二进制日志(binlog)、中继日志(relay log)、重做日志(redo log)、回滚日志(undo log)

参考:
MySQL 中的 7 种日志介绍
数据库日志
数据库日志有哪些

34、Redis过期策略

Redis过期策略

35、MySql查询、插入过程解析

MySql查询、插入过程解析
MYSQL查询和插入数据的流程是怎样的

36、其他知识点学习

一文搞定Redis分布式锁的实现和原理
MySQL索引从基础到原理,看这一篇就够了
可重入锁和不可重入锁的区别
MySQL的间隙锁
MySQL事务隔离级别详解
JVM的垃圾回收过程
如何破坏双亲委派模型
jvm有哪些垃圾回收器
一文读懂Java类加载全过程
常见的排序算法及其复杂度分析

37、Java中HashMap的put方法实现过程

Java-HashMap中put()方法是如何实现的

38、消息队列消息重复消费解决

(1)天然幂等性:某些业务操作本身就是天然幂等的,即无论执行多少次都不会产生副作用或影响结果。在这种情况下,允许重复调用和重试可以提高请求的成功率。

(2)利用数据库进行去重:可以通过在数据库中设置一个专门的去重表,或者利用唯一索引来去重。例如,对于需要写入订单流转日志的消息,在订单日志表中添加唯一索引,使用订单ID和时间戳作为联合唯一索引,这样重复的消息可以被数据库拒绝插入,保证了幂等性。

(3)设置全局唯一消息ID或任务ID:在消息投递时,为每条消息附加一个唯一的消息ID或任务ID。消费端可以利用这个唯一ID来实现类似分布式锁的机制,确保消息的消费只会发生一次。例如,在记录订单状态流转的消息中,为每条消息生成一个唯一ID,并在消费端使用缓存来标记已经消费过的ID,当其他消费端尝试消费该消息时,可以根据缓存中的记录判断是否已经处理过。

参考:
rabbitmq 消息重复消费的问题
如何保证消息不被重复消费

39、其他常见面试题

(1)Java面试题及答案整理

(2)JAVA 面试题经典

(3)Java面试题及答案整理

(4)史上最全Java面试题

(5)技术面试题–java基础

(6)我掏空了各大搜索引擎,给你整理了199道Java面试题,满满干货记得收藏

(7)Java 面试知识

(8)21道Java基础大厂面试题汇总

(9)大厂面试题系列

(10)MySQL必看15道面试题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值