Java面试题整理!面试20多家公司后呕心沥血总结(Java面试无论去哪个公司面试都逃不过这些面试题)

Java面试题整理!面试20多家公司后呕心沥血总结在很多面试中,都会遇到这些问题(Java面试无论去哪个公司面试都逃不过这些面试题)如今随着互联网不断发展在互联网时代,系统架构如何迎接高并发流量的挑战。而作为技术开发者,如何去应对技术变革带来的技能危机。基于传统架构到分布式架构演变过程所带来的技术变革进行全面深入讲解。

分享一个,有很多干货,包含netty,spring,线程,spring cloud等详细讲解,也有详细的学习规划图,面试题整理等,我感觉在面试这块讲的非常清楚:获取面试资料只需:点击这里领取!!! 暗号:CSDN在这里插入图片描述

Java 容器都有哪些

Collection 的子类 List、Set
List 的子类 ArrayList、LinkedList等
Set 的子类 HashSet、TreeSet等
Map 的子类 HashMap、TreeMao等

Collecion 和 Collections 有什么区别

java.util,Collection 是一个集合的顶级接口,它提供了对集合对象进行基本操作的通用接口方法,Collection 接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口的有 List 和 Set
java.util.Collections 是一个包装类(工具类),它包含了各种有关集合操作的静态多态方法。此类不能被实例化,用于对集合中元素进行排序、搜索以及线程安全等各种操作,服务于 Java 的 Collection 框架

List、Set、Map 之间的区别

List、Set 都继承 Collection 接口,而 Map 则不是
List 是一个有序集合,元素可重复,可有多个NULL值。可以使用各种循环遍历集合,因为它是有序的
Set 是一个无序集合,元素不可重复,重复元素会被覆盖掉(注意:元素虽然无放入顺序,但元素在 Set 中的位置是由该元素的 HashCode 决定的,其位置是固定的,加入 Set 的 Object 必须定义 equals()方法),Set 只能用迭代,因为它是无序的
Map 是由一系列键值对组成的集合,提供了 key 和 value 的映射。在 Map 中保证 key 与 value 一一对应的关系。一个 key 对应一个 value ,不能存在相同的 key,但 value 可以相同
Set 与 List 相比较
Set 检索元素效率较低,删除和插入效率高,因为删除和插入不会引起元素的位置变化
List 可动态增长,查找元素效率高,但是删除和插入效率低,因为删除或插入一条元素,会引起其他元素位置变化
Map 适合存储键值对的数据

HashMap 和 HashTable 的区别

继承的父类不同,但二者都实现了 Map接口
HashTable 继承自 Dictionary 类
HashMap 继承自 AbstractMap 类
线程安全不同
HashMap 在缺省的情况下是非Synchronize的
HashTable 的方法是Synchronize的
在多线程下直接使用 HashTable 不需要自己为它的方法实现同步。但使用 HashMap 时需要手动增加同步处理Map m = Collections.synchronizeMap(hashMap);
是否提供 contains() 方法
HashMap 把 HashTable 的 contains() 方法去掉了,改成了 containsValue() 和 containsKey(),因为 contains 容易让人误解
HashTable 则保留了 contains()、containsValue()、containsKey() 三个方法,其中 contains() 与 containsValue() 功能相同
key 和 value 是否可以为 null 值
HashTable 中,key 和 value 都不能为 null 值
HashMap 中,null 作为键,但这样的键只有一个。可以有多个 value 为 null 值的键。当 get() 方式返回 null 有可能是 HashMap 中没有该键,也有可能返回的 value 为 null。所以 HashMap 用containsKey()方法判断是否存在键
遍历方式不同
HashTable 与 HashMap 都是使用 Iterator 迭代器遍历,而由于历史的原因,HashTable 还使用了 Enumeration 的方式
hash 值不同
哈希值的使用不同,HashTable直接使用对象的HashCode,而 HashMap 重新计算哈希值
hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值
HashTable 使用的取模运算
HashMap 使用的与运算,先用 hash & 0x7FFFFFFF 后,再对 length 取模,&0x7FFFFFFF 的目的是为了将负的 hash 值转化为正值,因为 hash 值有可能为负数,而 &0x7FFFFFFF 后,只有符号外改变,而后面的位都不变
内部实现使用的数组初始化和扩容方式不同
HashTable 在不指定容量的情况下默认是11,而 HashMap 为16,HashTable 不要求底层数组的容量一定要是2的整数次幂,而 HashMap 底层数组则一定为2的整数次幂
HashTable 扩容时,将容量变成原来的2倍+1 (old * 2 + 1),而 HashMap 则直接改为原来的2倍 (old * 2)

如何决定使用 HashMap 还是 TreeMap

如果需要得到一个有序的 Map 集合就应该使用 TreeMap (因为 HashMap 的排序顺序不是固定的)除此之外,由于 HashMap 有比 TreeMap 更好的性能,在不需要使用排序的情况下使用 HashMap 会更好

HashMap 的实现原理

利用key的hashCode重新hash计算出当前对象的元素在数组中的下标 存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。 理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

HashSet 的实现原理

HashSet 实际上是一个 HashMap 实例,都是一个存放链表的数组。它不保证存储元素的迭代顺序;此类允许使用 null 元素。HashSet 中不允许有重复元素,这是因为 HashSet 是基于 HashMap 实现的,HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是统一的一个固定对象 private static final Object PRESENT = new Object();
HashSet 中 add() 方法调用的是底层 HashMap 中的 put() 方法,而如果是在 HashMap 中调用 put() ,首先会判断 key 是否存在,如果 key 存在则修改 value 值,如果 key 不存在这插入这个 key-value。而在 set 中,因为 value 值没有用,也就不存在修改 value 值的说法,因此往 HashSet 中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样 HashSet 中就不存在重复值。所以判断 key 是否存在就要重写元素的类的 equals() 和 hashCode() 方法,当向 Set 中添加对象时,首先调用此对象所在类的 hashCode() 方法,计算次对象的哈希值,此哈希值决定了此对象在Set中存放的位置;若此位置没有被存储对象则直接存储,若已有对象则通过对象所在类的 equals() 比较两个对象是否相同,相同则不能被添加。

ArrayList 与 LinkList 的区别是什么

AarrayList 是动态数组构成 LinkList 是链表组成
AarrayList 常用于经常查询的集合,因为 LinkList 是线性存储方式,需要移动指针从前往后查找
LinkList 常用于新增和删除的集合,因为 ArrayList 是数组构成,删除某个值会对下标影响,需要进行数据的移动
AarrayList 自由度较低,需要手动设置固定的大小,但是它的操作比较方便的,①直接创建②添加对象③根据下标进行使用
LinkList 自由度较高,能够动态的随数组的数据量而变化
ArrayList 主要开销在List需要预留一定空间
LinkList 主要开销在需要存储结点信息以及结点指针信息

如何实现数组与 List 之间的转换

List to Array : 可以使用 List 的 toArray() 方法,传入一个数组的类型例如 Stirng[] strs = strList.toArray(new String[strList.size()]);
Array to List : 可以使用 java.util.Arrays 的 asList()方法 例如 List strList = Arrays.asList(strs);

ArrayList 与 Vector 的区别是什么

ArrayList 是非线程安全的,而 Vector 使用了 Synchronized 来实现线程同步的
ArrayList 在性能方面要优于 Vector
ArrayList 和 Vector 都会根据实际情况来动态扩容的,不同的是 ArrayList 扩容到原大小的1.5倍,而 Vector 扩容到原大小的2倍

Array 与 ArrayList 有什么区别

Array 是数组,当定义数组时,必须指定数据类型及数组长度
ArrayList 是动态数组,长度可以动态改变,会自动扩容,不使用泛型的时候,可以添加不同类型元素

在 Queue 中 poll() 与 remove() 有什么区别

poll() 和 remove() 都是从队列头删除一个元素,如果队列元素为空,remove() 方法会抛出NoSuchElementException异常,而 poll() 方法只会返回 null

哪些集合类是线程安全的

Vector :比 ArrayList 多了同步化机制(线程安全)
HashTable :比 HashMap 多了线程安全
ConcurrentHashMap :是一种高效但是线程安全的集合
Stack :栈,继承于 Vector 也是线程安全

迭代器 Iterator 是什么

Iterator 是集合专用的遍历方式
Iterator iterator() : 返回此集合中元素的迭代器,通过集合的iterator()方法得到,所以Iterator是依赖于集合而存在的

Iterator 怎么使用 ? 有什么特点
Iterator 的使用方法

java.lang.Iterable 接口被 java.util.Collection 接口继承,java.util.Collection 接口的 iterator() 方法返回一个 Iterator 对象
next() 方法获取集合中下一个元素
hasNext() 方法检查集合中是否还有元素
remove() 方法将迭代器新返回的元素删除

Iterator 的特点

Iterator 遍历集合过程中不允许线程对集合元素进行修改
Iterator 遍历集合过程中可以用remove()方法来移除元素,移除的元素是上一次Iterator.next()返回的元素
Iterator 的next()方法是通过游标指向的形式返回Iterator下一个元素

Iterator 与 LinkIterator 有什么区别

使用范围不同
Iterator 适用于所有集合, Set、List、Map以及这些集合的子类型,而 ListIterator 只适用于 List 及其子类型
ListIterator 有 add() 方法,可以向 List 中添加元素,而 Iterator 不能
ListIterator 和 Iterator 都有 hasNext() 和 next() 方法,来实现顺序向后遍历。而 ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向遍历,但是 Iterator 不能
ListIterator 可以使用 nextIdnex() 和 previousIndex() 方法定位到当前索引位置,而 Iterator 不能
它们都可以实现 remove() 删除操作,但是 ListIterator 可以使用 set() 方法实现对象修改,而 Iterator 不能

怎么确保一个集合不能被修改

可以采用 java.util.Collections 工具类
Collections.unmodifiableMap(map)
Collections.unmodifiableList(list)
Collections.unmodifiableSet(set)
如诺修改则会报错java.lang.UnsupportedOperationException

多线程部分面试题

并发和并行有什么区别

并发:不同的代码块交替执行
并行:不同的代码块同时执行
个人理解
并发就是放下手头的任务A去执行另外一个任务B,执行完任务B后,再回来执行任务A,就比如说吃饭时来电话了,去接电话,打完电话后又回来吃饭
并行就是执行A的同时,接受到任务B,然后我一起执行,就比如说吃饭时来电话了,一边吃饭一边打电话

线程和进程的区别

根本区别 :进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
在操作系统中能同时运行多个进程,进程中会执行多个线程
线程是操作系统能够进行运算调度的最小单位

守护线程是什么

JVM内部的实现是如果运行的程序只剩下守护线程的话,程序将终止运行,直接结束。所以守护线程是作为辅助线程存在的

创建线程有哪几种方式

继承Thread类创建线程类
定义Thread类的子类,并重写该类的run()方法
创建Thread子类的实例,即创建了线程对象
调用线程对象的start()方法来启动该线程
实现Runnable接口创建线程类
创建runnable接口的实现类,并重写该接口的run()方法
创建Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该 Thread对象才是真正的线程对象
调用线程对象的start()方法来启动该线程
通过 Callable 和 Future 创建线程
创建Callable接口的实现类,并重写call()方法,该call()方法将作为线程执行体,并且有返回值
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
使用FutureTask对象作为Thread对象的target创建并启动新线程
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

怎么验证 MySQL 的索引是否满足需求

使用explain函数验证索引是否有效

事务的隔离级别

Read uncommitted (读未提交):最低级别
Read committed (读已提交):读已提交,可避免脏读情况发生。
Repeatable Read(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题
Serializable (串行化):最严格的事务隔离级别,要求所有事务被串行执行,不能并发执行,可避免脏读、不可重复读、幻读情况的发生

MySQL 常用的引擎

InnoDB 和 Myisam 都是用 B+Tree 来存储数据的

InnoDB 支持事务,且支持四种隔离级别(读未提交、读已提交、可重复读、串行化),默认的为可重复读.
Myisam 只支持表锁,且不支持事务.Myisam 由于有单独的索引文件,在读取数据方面的性能很高.

MySQL 的行锁、表锁、页锁

行级锁
是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。

行级锁的特点
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

表级锁
表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)

表级锁的特点
开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。

页级锁
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁

页级锁的特点
开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

扩展
MyISAM和MEMORY采用表级锁(tabl-level locking)
BDB采用页面锁(page-level locking)或表级锁,默认为页面锁
InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁

乐观锁和悲观锁

乐观锁认为一般情况下数据不会造成冲突,所以在数据进行提交更新时才会对数据的冲突与否进行检测。如果没有冲突那就OK;如果出现冲突了,则返回错误信息并让用户决定如何去做.常见的做法有两种:版本号控制及时间戳控制
悲观锁,正如其名,它指的是对数据被外界(包括当前系统的其它事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排它性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)常见做法:select … for update悲观锁语法锁住记录 两个事务同时修改的话,事务A先执行事务B就会被阻塞,事务A执行update完后,事务B就会看到事务A执行完后更新的结果

MySQL 问题排查都有哪些手段

使用 show processlist 命令查看当前所有连接信息
使用 explain 命令查询 SQL 语句执行计划
开启慢查询日志,查看慢查询的 SQL

如何做 MySQL 的性能优化

创建索引 尽量避免全盘扫描 首先考虑在 where 和 order by 涉及的列上创建索引
避免在索引上使用计算 注意就是IN关键字不走索引,它是走全盘扫描
使用预编译防止 sql 注入
尽量将多条 SQL语句压缩到一条 SQL 语句中
最最最好的就是少用 * , 应该写成要查询的字段名,尽量避免在 where 条件中判断 null
尽量不用like 的前置百分比
对于连续的数值,能用 between 就不要用 in
在新建临时表时,如果一次性插入数据量较大.可以用 select into 代替 create table
选择正确的存储引擎
垂直/水平分割、分库分表、读写分离

Redis部分面试题

Redis 是什么?有什么优点?都有哪些使用场景

Reids 是完全开源免费的,用C语言编写的,遵守BSD协议, 是一个高性能的(key/value)分布式内存数据库,基于内存运行 并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一, 也被人们称为数据结构服务器
优点
Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
Redis支持数据的备份,即master-slave模式的数据备份
应用场景
内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
模拟类似于HttpSession这种需要设定过期时间的功能
发布、订阅消息系统
定时器、计数器

Redis 为什么是单线程的

Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈

Redis 的缓存预热

在项目配置文件中生命自定义的key ,在项目启动时会判断redis是否存在key ,如果没有就会创建一个key传入null值
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中

redis 缓存雪崩是什么,怎么解决 ?

缓存雪崩是指,缓存层出现了错误,不能正常工作了.于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况.

解决方案

redis 高可用 就是搭建 redis 集群,其中一台redis挂掉后 可以使用其他的 redis
限流降级 就是每一个 key 只能一个线程来查询数据和缓存,其他线程等待
数据预热 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中.在即将发生大并发访问前手动触发加载缓存不同的 key ,设置不同的过期时间,让缓存失效的时间点尽量均匀.*

缓存穿透是什么?如何解决

就是访问redis数据库,查不到数据,就是没有命中,会去持久化数据库查询,还是没有查到.假如高并发的情况下,持久化数据库一下增加了很大压力,就相当于出现了缓存穿透

解决方案

缓存空对象 在存储层命中失败后,即使返回空对象也将其缓存,并设置一个过期时间,之后访问的这个数据将会从缓存中取出,很好的保护了后端数据源,这样也会有出现问题 例如空值被缓存也就会增加大量的缓存空间,设置了过期时间还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响
布隆过滤器 对所有可能查询的参数以 hash 形式存储,查询时发现值不存在就直接丢弃,不会去持久层查询

Redis 支持的数据类型有哪些

String、List、Set、Hash、ZSet这5种

Redis 支持的 Java 客户端有哪些

Redisson、Jedis、lettuce 等等,官方推荐使用 Redisson

Jedis 与 Redisson 有哪些区别

Jedis 和 Redisson 都是Java中对Redis操作的封装。Jedis 只是简单的封装了 Redis 的API库,可以看作是Redis客户端,它的方法和Redis 的命令很类似。Redisson 不仅封装了 redis ,还封装了对更多数据结构的支持,以及锁等功能,相比于Jedis 更加大。但Jedis相比于Redisson 更原生一些,更灵活

怎么保证缓存与数据库数据的一致性

对删除缓存进行重试,数据的一致性要求越高,我越是重试得快。
定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载。
给所有的缓存一个失效期

Redis 持久化有几种方式

快照方式(RDB, Redis DataBase)将某一个时刻的内存数据,以二进制的方式写入磁盘
文件追加方式(AOF, Append Only File),记录所有的操作命令,并以文本的形式追加到文件中
混合持久化方式,Redis 4.0 之后新增的方式,混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,这样既能保证 Redis 重启时的速度,又能简单数据丢失的风险

Redis 怎么实现分布式锁

SET key value [EX seconds] [PX milliseconds] [NX|XX]
EX second :设置键的过期时间为second秒
PX millisecond :设置键的过期时间为millisecond毫秒
NX :只在键不存在时,才对键进行设置操作
XX :只在键已经存在时,才对键进行设置操作
SET操作成功完成时,返回OK ,否则返回nil

Redis 分布式锁有什么缺陷

死锁
设置锁的过期时间,且需要保证setNx和设置过期时间操作的原子性
错位解锁
加锁时记录当前线程ID,解锁时判断ID是否一致
解锁时,查询redis里记录锁的ID,以及删除redis中锁的记录,这两步操作可以使用lua脚本保持原子性
业务并发执行问题
加锁成功后开启守护线程,当临近过期时间,业务还未完成时,开始续时,重复此步骤直到业务完成

Redis 如何做内存优化

缩减键值对象:满足业务要求下 key 越短越好;value 值进行适当压缩
共享对象池:即 Redis 内部维护[0-9999]的整数对象池,开发中在满足需求的前提下,尽量使用整数对象以节省内存
尽可能使用散列表(hashes)
编码优化,控制编码类型
控制 key 的数量

Redis 淘汰策略有哪些

noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。 大多数写命令都会导致占用更多的内存(有极少数会例外, 如 DEL )
allkeys-lru: 所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key
volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key
allkeys-random: 所有key通用; 随机删除一部分 key
volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key
volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key

Redis 常见的问题有哪些? 该如何解决

缓存和数据库双写一致性问题

就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性
采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列
缓存穿透问题
利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
采用异步更新策略,无论 Key 是否取到值,都直接返回。Value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作
提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的 Key。迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回
缓存雪崩问题
给缓存的失效时间,加上一个随机值,避免集体失效
使用互斥锁,但是该方案吞吐量明显下降
双缓存。我们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作(从A中读不到,就去B读,返回数据时需要异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B)
缓存的并发竞争问题
如果对这个 Key 操作,不要求顺序
这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做 set 操作即可,比较简单。
如果对这个 Key 操作,要求顺序
假设有一个 key1,系统 A 需要将 key1 设置为 valueA,系统 B 需要将 key1 设置为 valueB,系统 C 需要将 key1 设置为 valueC
期望按照 key1 的 value 值按照 valueA > valueB > valueC 的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。
系统A key 1 {valueA 3:00}
系统B key 1 {valueB 3:05}
系统C key 1 {valueC 3:10}
那么,假设这会系统 B 先抢到锁,将 key1 设置为{valueB 3:05}。接下来系统 A 抢到锁,发现自己的 valueA 的时间戳早于缓存中的时间戳,那就不做 set 操作了,以此类推。
其他方法,比如利用队列,将 set 方法变成串行访问也可以。总之,灵活变通。

RabbitMQ部分面试题

RabbitMq 的使用场景有哪些

多个应用之间的耦合
跨系统的异步通信
流量削峰
比如:注册用户、发送激活邮箱、订单下单等

RabbitMq 有哪些重要的角色

生产者:消息的创建者,负责创建和推送数据到消息服务器
消费者:消息的接收方,负责处理数据和确认消息
代理:就是RabbiMQ本身,不生产不消费,只是快递消息

RabbitMq 有哪些重要的组件

ConnectionFactory(连接管理器):应用程序与Rabbit之间建立连接的管理器,程序代码中使用
Channel(信道):消息推送使用的通道
Exchange(交换器):用于接受、分配消息
Queue(队列):用于存储生产者的消息
RoutingKey(路由键):用于把生成者的数据分配到交换器上
BindingKey(绑定键):用于把交换器的消息绑定到队列上

RabbitMQ的消息存储方式

RabbitMQ 对于 queue 中的 message 的保存方式有两种方式:disc 和 ram.如果采用disc,则需要对 exchange/queue/delivery mode 都要设置成 durable 模式. Disc 方式的好处是当 RabbitMQ 失效了, message 仍然可以在重启之后恢复.而使用 ram 方式, RabbitMQ 处理 message 的效率要高很多, ram 和 disc 两种方式的效率比大概是 3:1.所以如果在有其它 HA 手段保障的情况下,选用 ram 方式是可以提高消息队列的工作效率的.

RabbitMq 中 vhost 的作用是什么

vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的队列、绑定、交换器和权限控制
从 RabbitMQ 的全局角度 vhost可以作为不同权限隔离的手段(一个典型的例子,不同的应用可以跑在不同的vhost中)

RabbitMq 的消息是怎么发送的

生产者把生产的小心通过channel发送到Exchange上,Exchange通过绑定的router key来选择Queue,消费者监听到Queue上有新的消息,就消费调此消息

RabbitMq 怎么保证消息的稳定性

提供了事务的功能,通过将 channel 设置为 confirm(确认模式)

RabbitMq 怎么避免丢失消息

消息持久化
消费端的ack签收机制
设置集群镜像模式
消息补偿机制

要保证消息持久化成功的条件有哪些

声明队列必须设置持久化 durable 设置为 true
消息推送投递模式必须设置持久化,deliveryMode 设置为 2(持久)
消息已经到达持久化交换器
消息已经到达持久化队列
以上四个条件都满足才能保证消息持久化成功

RabbitMq 持久化有什么缺点

持久化的缺点就是降低了服务器的吞吐量,因为使用的是磁盘而非内存存储,从而降低了吞吐量。可尽量使用 ssd 硬盘来缓解吞吐量的问题

RabbitMq 有几种广播方式

fanout广播模式:所有bind到此exchange的queue都可以接收消息
direct直接交换:通过routingKey和exchange决定的那个唯一的queue可以接收消息
topic通配符模式:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息

RabbitMq 怎么实现延迟消息队列

通过消息过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能
使用 RabbitMQ-delayed-message-exchange 插件实现延迟功能。

RabbitMq 集群有什么用

高可用:某个服务器出现问题,整个 RabbitMQ 还可以继续使用
高容量:集群可以承载更多的消息量。

RabbitMq 节点的类型有哪些

磁盘节点:消息会存储到磁盘
内存节点:消息都存储在内存中,重启服务器消息丢失,性能高于磁盘类型

RabbitMq 集群搭建需要注意哪些问题

各节点之间使用–link连接,此属性不能忽略
各节点使用的 erlang cookie 值必须相同,此值相当于秘钥的功能,用于各节点的认证
整个集群中必须包含一个磁盘节点

RabbitMq 每个节点是其他节点的完整拷贝吗
不是

如果每个节点都拥有所有队列的完全拷贝,这样新增节点,不但没有新增存储空间,反而增加了更多的冗余数据
如果每条消息都需要完整拷贝到每一个集群节点,那新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟

RabbitMq 集群中唯一一个磁盘节点崩溃了会发生什么

唯一磁盘节点崩溃了,集群是可以保持运行的,但不能更改任何东西
不能创建队列
不能创建交换器
不能创建绑定
不能添加用户
不能更改权限
不能添加和删除集群节点

RabbitMq 对集群停止顺序有要求吗

RabbitMQ 对集群的停止的顺序是有要求的,应该先关闭内存节点,最后再关闭磁盘节点。如果顺序恰好相反的话,可能会造成消息的丢失

JVM 主要的组成部分?及其作用

类加载器(Class Loader):加载类文件到内存。Class loader只管加载,只要符合文件结构就加载,至于能否运行,它不负责,那是有Exectution Engine 负责的
执行引擎(Execution Engine):也叫解释器,负责解释命令,交由操作系统执行
本地库接口(Native Interface):本地接口的作用是融合不同的语言为java所用
运行时数据区(Runtime Data Area)

JVM 运行时数据区是什么

堆是java对象的存储区域,任何用new字段分配的java对象实例和数组,都被分配在堆上,java堆可用-Xms和-Xmx进行内存控制,
常量池:运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放,jdk1.7以后,运行时常量池从方法区移到了堆上
方法区:用于存储已被虚拟机加载的类信息,常量,静态变量,即是编译器编译后的代码等数据
虚拟机栈:虚拟机栈中执行每个方法的时候,都会创建一个栈桢用于存储局部变量表,操作数栈,动态链接,方法出口等信息
本地方法栈:与虚拟机发挥的作用相似,相比于虚拟机栈为Java方法服务,本地方法栈为虚拟机使用的Native方法服务,执行每个本地方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息
程序计数器:指示Java虚拟机下一条需要执行的字节码指令

堆和栈的区别

栈内存用来存储局部变量和方法调用
而堆内存用来存储 Java 中的对象.无论是成员变量,局部变量,还是类变量.,它们指向的对象都存储在堆内存中.
栈内存归属单个线程,一个栈对应一个线程,其中储存的变量只能在该线程中可以访问到,也可以理解为私有内存
而堆内存中的对象 所有线程均可见,堆内存中对象可以被所有线程访问到
栈内存要远小于堆内存

队列和栈是什么?有什么区别

队列和栈是两种不同的数据结构
操作名称不同
队列的插入称为入队,队列的删除称为出队。栈的插入称为进栈,栈的删除称为出栈
可操作的方式不同
队列是在队尾入队,队头出队,即两边都可操作。而栈的进栈和出栈都是在栈顶进行的,无法对栈底直接进行操作。

队列先进先出 FIFO,栈是后进先出 LIFO

类加载器有哪些?什么是双亲委派模型

启动类加载器 Bootstrap ClassLoader:加载<JAVA_HOME>\lib目录下核心库
扩展类加载器 Extension ClassLoader:加载<JAVA_HOME>\lib\ext目录下扩展包
应用程序类加载器 Application ClassLoader: 加载用户路径(classpath)上指定的类库在这里插入图片描述
双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。这里的双亲其实就指的是父类,没有mother。父类也不是我们平日所说的那种继承关系,只是调用逻辑是这样

类加载的执行过程

加载
加载指的是把class字节码文件从各个来源通过类加载器装载入内存中
字节码来源:一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译类加载器。一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器
链接
验证
主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误,包括对于文件格式的验证

准备
主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值。特别需要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值:8种基本类型的初值,默认为0;引用类型的初值则为null;

解析
将常量池内的符号引用替换为直接引用的过程。在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用

初始化
这个阶段主要是对类变量初始化,是执行类构造器的过程

怎么判断对象是否可以收回

引用计数算法

判断对象的引用数量
通过判断对象的引用数量来决定对象是否可以被回收
每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1
任何引用计数为0的对象实例可以被当作垃圾收集
优缺点
优点:执行效率高,程序执行受影响较小
缺点:无法检测出循环引用的情况,导致内存泄漏
可达性分析算法
通过判断对象的引用链是否可达来决定对象是否可以被回收
可以作为GC Root对象的对象有
虚拟机栈中引用的对象(栈帧中的本地变量表)
方法区中的常量引用对象
方法区中类静态属性引用对象
本地方法栈中JNI(Native方法)的引用对象
活跃线程中的引用对象

Java 中有哪些引用类型

强引用(strongreference)
就是指在程序代码之中普遍存在的,类似“Object obj=new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象实例

软引用(softreference)
是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象, 在系统将要发生内存溢出异常之前,将会把这些对象实例列进回收范围之中进行 第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后,提供了SoftReference 类来实现软引用

弱引用(weakreference)
也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱 引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了WeakReference 类来实现弱引用

虚引用(phantomreference)
也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用

JVM 中垃圾回收算法

标记-清除算法

标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如下图所示。标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片在这里插入图片描述

复制除算法

复制算法的提出是为了克服句柄的开销和解决内存碎片的问题。它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集合(GC Roots)中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存在这里插入图片描述

标记-整理(压缩)算法

标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题在这里插入图片描述

JVM 有哪些垃圾回收器

新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:CMS、Serial Old、Parallel Old
整堆收集器: G1

介绍一下 CMS 垃圾回收器

CMS收集器 :一种以获取最短回收停顿时间为目标的收集器
特点:基于标记-清除算法实现。并发收集、低停顿
应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务
CMS收集器的运行过程分为下列4步:
初始标记:标记GC Roots能直接到的对象,速度很快但是仍存在Stop The World问题
并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行
重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题
并发清除:对标记的对象进行清除回收
CMS收集器的内存回收过程是与用户线程一起并发执行的
CMS收集器的缺点:
对CPU资源非常敏感
无法处理浮动垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生
因为采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC

CMS收集器的工作过程图:在这里插入图片描述

新生代垃圾回收器和老生代垃圾回收器有哪些?有什么区别

新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:CMS、Serial Old、Parallel Old
区别:
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收

简述分代垃圾回收器是怎么工作的

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。 新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1
执行流程
把 Eden + From Survivor 存活的对象放入 To Survivor 区
清空 Eden 和 From Survivor 分区; From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代
老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法
以上这些循环往复就构成了整个分代垃圾回收的整体执行流程

JVM 调优的工具有哪些

jconsole jdk自带的工具:是一个基于JMX(java management extensions)的GUI性能监测工具(jdk/bin目录下点击jconsole.exe即可启动)
VisualVM:它提供了一个可视界面,用于查看 Java 虚拟机 (Java Virtual Machine, JVM) 上运行的基于 Java 技术的应用程序(Java 应用程序)的详细信息(jdk/bin目录下面双击jvisualvm.exe既可使用)
MAT 第三方调优工具:一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗(MAT以eclipse 插件的形式来安装)
GChisto:专业分析gc日志的工具,可以通过gc日志来分析:Minor GC、full gc的时间、频率等等,通过列表、报表、图表等不同的形式来反应gc的情况(配置好本地的jdk环境之后,双击GChisto.jar,在弹出的输入框中点击 add 选择gc.log日志)
gcviewer:分析小工具,用于可视化查看由Sun / Oracle, IBM, HP 和 BEA Java 虚拟机产生的垃圾收集器的日志

MySQL 链接数据库常用的几种方式 ?

Mybatis 框架
Hibernate 框架
JDBC 技术
c3p0 连接池
dbcp 连接池

SpringBoot 如何集成 Redis ?

在 pom.xml 文件引入 redis 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

在 application 配置文件中 书写 redis 配置

spring.redis.host=127.0.0.1
#Redis服务器连接端口
spring.redis.port=6379
#Redis服务器连接密码(默认为空)
#spring.redis.password=

SpringCloud 的优点 ?

服务之间采用Restful等轻量级通讯
精准的制定优化服务方案,提高系统的可维护性
服务之间拆分细致,资源可重复利用,提高开发效率

SpringCloud 用了哪些组件 ?

netflix Eureka 注册中心
netflix Ribbon 负载均衡
netflix Zuul 网关
netflix Hystrix 熔断器
feign 服务调用

List 和 Set 的区别

List 允许有多个重复对象,而 Set 不允许有重复对象
List 允许有多个NULL值,而 Set 只允许有一个NULL值
List 是一个有序的容器,输出顺序即是输入顺序
Set 是一个无序的容器无法保证每个元素的顺序,但是可以用 TreeSet 通过 Comparator 或者 Comparable 维护排序顺序
List的实现类有 ArrayList、LinkList、Vector 其中 ArrayList 最常用于查询较多,随意访问.LinkList 同于新增和删除较多的情况,Vector 表示底层数组,线程安全象
Set的实现类有 HashSet、TreeSet、LinkedHashSet 其中基于 HashMap 实现的 HashSet 最为流行,TreeSet 是一个有序的Set容器象
扩展 Map的实现类有HashMap、HashTable、TreeMap

Java 中 static 的作用

表示全局或静态的意思、用来修饰成员变量和成员方法,也可以形成静态代码块
达到了不用实例化就可以使用被 public static 修饰的变量或方法

什么单例模式 ?

保证整个项目中一个类只有一个对象的实例,实现这种功能就叫做单例模式

单例模式的好处:
单例模式节省公共资源
单例模式方便控制
如何保证是单利模式 ?
构造私有化
以静态方法返回实例
确保对象实例只有一个
单例模式有哪几个 ?
饿汉模式
把对象创建好,需要使用的时候直接拿到就行

懒汉模式
等你需要的时候在创建对象,后边就不会再次创建

SpringBoot 常用的几个注解 ?

@SpringBootApplication SpringBoot的核心注解 启动类
@EnableAutoConfiguration 开启SpringBoot自动配置
@RestController 在控制层 是@ResponseBody注解与@Controller注解的合集
@RequestMapper 处理请求地址映射的注解
@RequestParam 获取url上传过来的参数
@Configuration 声明配置类
@Component 通用注解
@Service 业务逻辑层

Java 八大数据类型

char 字符型 byte 字节型 boolean 布尔型

float 单浮点型 double 双浮点型

int 整数型 short 短整数型 long 长整数型

MySQL分页和升序降序如何实现 ?

可以用 limit
select name,age,sex from t_student limit(0,5);

升序 order by xx asc
select name,age,sex from t_student order by age asc;

降序 order by xx desc
select name,age,sex from t_student order by age desc;

maven 是干什么的,它有什么好处 ?

maven 专门构建和管理Java项目的工具
maven的好处在于可以将项目过程规范化、自动化、高效化以及强大的可扩展性

MySQL 如何添加索引 ?

PRIMARY KEY (主键索引)
添加INDEX(普通索引) ALTER TABLE table_name ADD INDEX index_name ( column )
添加UNIQUE(唯一索引) ALTER TABLE table_name ADD UNIQUE index_name ( column )
添加FULLTEXT(全文索引) ALTER TABLE table_name ADD FULLTEXT ( column)
添加多列索引 ALTER TABLE table_name ADD INDEX index_name ( column1, column2, column3 )

MySQL 索引的实现方式?

MySQL 索引底层的实现方式是 B+Tree也就是B+树 具体查看 B+Tree实现方式

Vue的数据双向绑定原理
使用v-mode属性, 它的原理是利用了Object.defineProperty()方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的

ActiveMQ的消息存储方式

采取先进先出模式,同一时间,消息只会发送给某一个消费者,只有当该消息被消费并告知已收到时,它才能在代理的存储中被删除.
对于持久性订阅来说,每一个消费者都会获取消息的拷贝.为了节约空间,代理的存储介质中只存储了一份消息,存储介质的持久订阅对象为其以后的被存储的消息维护了一个指针,消费者消费时,从存储介质中复制一个消息.消息被所有订阅者获取后才能删除.

KahaDB消息存储

基于文件的消息存储机制,为了提高消息存储的可靠性和可恢复性,它整合了一个事务日志.KahaDB拥有高性能和可扩展性等特点.由于KahaDB使用的是基于文件的存储,所以不需要使用第三方数据库

猜你喜欢

转载自blog.csdn.net/a646705816/article/details/109084398