Java常见面试题

目录

  • 1、mysql并发事务会带来哪些问题,如何解决?
  • 2、请详细描述Redis持久化机制?
  • 3、简述Redis缓存雪崩和缓存穿透的问题和解决方案?
  • 4、RabbitMQ消息丢失及对应解决方案
  • 5、什么叫线程安全?举例说明
  • 6、举例说明常用的加密算法
  • 7、synchronized和ReentrantLock有什么区别?
  • 8、synchronized和lock的区别
  • 9、如何保证接口的幂等性
  • 10、什么是分布式事务,如何解决
  • 11、HashMap的结构和原理是什么
  • 12、CurrentHashMap是如何保证线程安全的
  • 13、java线程池有哪些参数,都有什么含义
  • 14、java 中的volatile关键字是干嘛的
  • 15、java中垃圾收集的方法有哪些
  • 16、什么是类加载器,类加载器有哪些
  • 17、什么是双亲委派
  • 18、其他常见面试题

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)、分代收集

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

参考:堆内存分配及回收策略

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

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

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

17、什么是双亲委派

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

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

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

18、其他常见面试题

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

(2)JAVA 面试题经典

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

(4)[史上最全Java面试题](https://www.cnblogs.com/aademeng/articles/11104249.html)

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

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

(7)Java 面试知识

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

(9)大厂面试题系列

(10)MySQL必看15道面试题

你可能感兴趣的:(java,java,面试题,Java常见面试题,Java面试题)