您现在的位置是:首页 > 原理深入 > Java面试总结Java面试总结

面试总结

第十三双眼睛2023-11-02【Java面试总结】人已围观

简介面试总结

1死锁
在java中,死锁指的是两个或者多个线程等待对方释放资源而造成无法继续执行的状态,在这种情况下,线程被永久阻塞,无法执行,形成了死锁。
死锁的发生通常需要满足4个条件,也被称为死锁的必要条件
1互斥条件:至少有一个资源在任意时刻只能被一个线程占有
2请求与保持条件:线程至少持有一个资源并且在等待其他线程释放资源
3不可剥夺条件:已经获得的资源,不能被强制剥夺,只能有持有者主动释放
4循环等待:存在一个线程链的闭环,每个线程都在等待其他线程释放资源
当上面的4个条件同时满足时,就会形成死锁。
解决死锁一般有一下几种方法:
1预防死锁:通过破坏死锁发生的条件来预防死锁,比如:按相同的顺序获取资源,设置超时
2避免死锁:通过动态的分配资源和避免循环等待来避免死锁
3检测与恢复:通过周期性的检测系统中的死锁情况并采取措施解除死锁
4忽略死锁:有些情况下,可以通过忽略死锁的发生而继续执行,或者通过重启系统来解决死锁
编写程序时,可以采用以下方法:
1尽量减少同步代码块的嵌套,并且合理安排锁的获取顺序,
2使用并发工具类,这些工具类已经考虑了死锁问题,并提供了更加安全的操作方式
3避免在持有锁的情况下进行耗时操作
4使用线程池来管理线程,避免手动创建线程和销毁线程的复杂性

2事务隔离级别
事务隔离级别指的是多个并发事务之间的隔离程度,一共有4个隔离级别
1读未提交:一个事务可以读取到其他事务未提交的数据,这可能导致脏读,不可重复读,幻读等问题
2读已提交:一个事务只能读取到其他事务已经提交的数据,不存在脏读的问题,但是仍然存在不可重复读,幻读问题,不可重复读指的是,在一个事务的执行过程中,对统一条数据的多次查询,结果却不同
3可重复读:一个事务在执行过程中多次读取相同的数据,能够保证数据不被其他事务修改,可以避免脏读和不可重复读,但还存在幻读问题,幻读指的是,在一个事务的执行过程中,多次查询统数据,结果却不相同,和不可重复读不同的地方不是同一条数据不同,而是数据的数量不一样
4串行化:这个级别下,事务一个一个按顺序执行,不允许并发执行,可以避免脏读,不可重复读,幻读问题,但是效率会降低

3事务的四个特征如何保证
原子性:用undolog日志保证的,在事务执行过程中,把修改之前的数据保存到undolog里面,一旦发生错误或者回滚,用undolog日志里面的数据恢复就行
一致性:这个有两方面,一个是数据的一致性,比如唯一性,外键,这个数据库来保证
隔离性:用mvcc机制解决了脏读和不可重复读的问题,用锁解决了幻读的问题
持久性:使用redo日志来解决,这个文件记录了数据被修改之后的值,当我们通过事务修改数据的时候,除了修改缓冲区里面的数据,还会把数据记录在redo日志中,当事务提交后,在一定的时机,把缓冲区的数据刷到磁盘上,一旦数据库宕机,在重新启动以后,将redo日志里面的数据读取出来,即可恢复。

4mysql in 和exist的区别
in在查询的时候,首先查询子查询的表,然后将内表和外表左笛卡尔积,然后按照条件进行筛选,所以内表比较小的时候,用in速度比较块
exists在查询的时候,首先查询外表,然后将每条结果放入子查询中进行匹配,根据验证结果来决定主查询结果是否保留
总结:当子查询的结果少,主查询结果大时用in ,反之,子查询结果大,主查询结果小,用exists.

5mvcc
在并发访问数据的时候,将数据做多版本处理,避免因写锁产生阻塞问题
mvcc通过保存数据的历史版本,根据一定的算法来决定历史数据是否可见,从而达到不加锁 就可以实现事务隔离的问题
核心知识点:
事务id
回滚指针
隐藏主键
readview

6表的内连接 外连接 交叉连接 笛卡尔积
内连接:取得两张表中存在连接关系的记录
外连接:取得两张表中存在连接关系的记录以及某张表中不满足匹配关系的记录
交叉连接:显示两张表中所有记录一一对应,没有筛选条件,也叫笛卡尔积
笛卡尔积:同交叉连接

7count(1) count(*) count(列名)有啥区别
count是一个聚合函数,统计数据的总条数,根据where条件进行扫描,扫描到一条数据,如果括号里的数据不是null,则加1,否则不加,所以括号里面的值在一定程度上会影响性能,count(*)也就是整条数据,整条数据肯定不会为null,因此不会去扫描整条数据,直接返回扫描到多少数据。
count(1)忽略字段,扫描到一条数据返回1,这两个是用的最多的,性能最好的
count(列名)会忽略字段为null的情况,如果扫描出来的字段为null,则忽略,如果不为null,则返回1.性能是最差的。

8mysql主从复制原理
主要经历5个步骤
1主库的更新事件被写道binlog
2从库发起连接,连接到主库
3主库开启一个线程,把binlog发送到从库
4从库将接受到的binlog写到realylog
5从库开启线程将realy的数据写到从库

9redis过期策略,内存淘汰策略
定时过期:给每个设置了过期时间的key设置一个定时器,到了过期时间就马上对数据进行清除,该策略对内存友好,但是会对cpu造成一定的损耗
惰性过期:只有当访问一个key时,才会判断是否过期,才会进行清除,与上面的定时过期相反,对cpu友好,内内存不友好
定期过期:每隔一定时间,对一部分需要过期的key进行扫描,进行清除,用到了expire字典,字典中保存了所有设置了过期时间的key,和过期时间
redis中同时使用惰性过期和定期过期两种策略。
内存淘汰策略:8种内存淘汰策略
1最近最少使用的设置了过期时间的key进行清除
2在所有key种使用最近最少使用策略
3报错
4随机策略,从设置了过期时间的key种随机清除
5所有key随机,从所有key中使用随机策略进行清除
7根据过期时间进行淘汰
8lfu

10redis主从同步
redis主从复制包括两种情况,全量复制和增量复制
全量复制发生在初始化过程中,从节点会向主节点发送同步请求,主节点在收到同步请求后,会生成一份当前数据的快照,并发送给从节点,从节点接受到快照数据以后,完成全量复制
增量复制发生在主节点数据每次发生变化时,主节点会把发生变化的数据发送给从节点,增量复制通过维护offset这个复制偏移量来实现

11如何保证缓存和数据库的一致性
写操作同步:为了保证缓存和数据库的一致性,可以在写操作时进行同步,这种操作虽然简单,但是会增加服务器压力
读操作双重检查:先从redis读取数据,如果数据不存在,就从数据库中读取最新数据,并放入redis中
定时同步:定时将数据库中的最新数据同步到redis中
消息队列异步同步:将写操作发送到消息队列中,再由消息队列将数据同步到Mysql和redis中


12布隆过滤器
布隆过滤器是一种占用空间很小的数据结构,由一个很长的二进制向量和一组hash函数构成,用于检索一个元素是否存在,有一定的误识别率和删除困难
原理:假设有一个集合,集合中有N个元素,利用k个hash函数将每个元素散列到有一个长度为A的数组中去,数组中的二进制位设置为1,如果待检查元素经过hash函数运算后,这k个二进制位上都为1,则这个元素可能存在,如果不为1,则一定不存在

13redis哨兵机制和集群有何不同
哨兵机制是基于主从复制来实现的,可以实现读写分离,分担主节点的压力
cluster集群的从节点是冷备节点,只有在主节点宕机之后才会启动,哨兵集群无法在线扩容,哨兵机制是一主多从,cluster是多主多从

14rocketMQ工作原理

15三次握手过程
1客户端发送同步序列,seq = x 发送完毕后,进入SYN_SEND状态
2SYN = 1  seq = y ACK = x + 1 发送完毕后,服务端进入SYN_REC状态
3SYN = 1 ACK = Y +1 发送完毕后,客户端进入连接状态,服务端收到后,也进入连接状态

16四次挥手过程
0刚开始双方处理established状态
1客户端要断开了,向服务端发送FIN报文,发送后客户端编程FIN-WAIT-1状态,这时候,客户端变成了半关闭状态,无法向服务端发送报文
2服务端接收后向客户端确认,变成了CLOSE-WAIT状态
3客户端接收到了服务端的确认,变成了FIN-WAIT-2状态
4服务端向客户端发送FIN,自己进入LAST-ACK状态
5客户端收到服务端发送来的FIN报文后,自己变成了TIME-WAIT状态,然后发送ACK给服务端
这个时候,客户端要等待足够长的时间,是两个报文最大生存周期,如果在这段时间内,客户端没有收到服务端的重发请求,就认为ACK成功到达,挥手结束,否则客户端重新发送ACK

17tcp拥塞控制
1慢启动算法
2拥塞避免
3拥塞发生
4快速恢复

18bio nio aio有何区别
bio:线程发起io请求,不管内核是否准备好io操作,从发起请求开始,线程一直阻塞,知道io完成
nio:线程发起io请求,理解返回,内核在做好io操作准备之后,通过回调函数通知线程做io操作,线程开始阻塞,知道操作完成
aio:线程发起io请求,立即返回,内核做好io操作准备之后,开始做io操作,知道操作完成或者失败,通过回调函数通知线程完成或者失败

19comparator 与comparable有啥区别
他们二者的区别如下:
1一个时比较的意思,另一个时比较器的意思
2一个是类内部实现排序,另一个是类外部实现排序

20java中多态的实现原理
实现方式:子类继承父类 类实现接口
当调用某个对象的某个方法时,查找该对象的类,方法表,找到直接引用地址,最后决定调用的方法是哪个

21java对象有几种创建方式
5种:
直接new
反射方式
对象.clone()
使用反序列化手段
Unsafe

22谈谈异常的层次结构
有位老人叫throwable,生了两个孩子,一个叫 error,表示错误,一个叫exception,表示异常,error发生时,程序不可运行,虚拟机退出,发生exception时,程序可以运行。

23静态内部类与非静态内部类有啥区别
1静态内部类可以有静态成员,包括属性和方法,非静态内部类不能有静态成员
2静态内部类只能访问外部类的静态成员,非静态内部类可以访问外部类的所有成员
3实例化方式不同,一个需要外部类对象,一个不需要
4访问静态内部类的静态成员,直接用类名即可

24你常见的设计模式有哪些
三种类型
结构型:适配器模式,代理模式,装饰着模式,桥接模式,组合模式
创建型:单例模式,构建者模式,原型模式
行为型:策略模式,观察者模式,责任链模式

25抽象工厂类与工厂方法模式的区别


26如何实现克隆对象
实现Cloneable接口,实现clone方法
使用序列化和反序列化方式可以实现深拷贝
第三方工具:如BeanUtils,SerilizationUtils

27写出几种单例模式
饿汉式:构造方法私有,提前实例化好,提供获取方法
懒汉式:获取时进行判断
枚举法:和饿汉式差不多
静态内部类模式:
/**
 * 静态内部类单例模式.
 *
 * @author jialin.li
 * @date 2019-12-30 22:13
 */
public class Singleton {

    private Singleton() {
    }

    public static Singleton getInstance() {
        return Inner.singleton;
    }

    private static class Inner {
        private static Singleton singleton = new Singleton();
    }
}



28java8新特性
1lamda表达式,允许一个方法作为参数
2流式编程
3方法引用
4接口里面可以有非抽象方法

29面向对象6原则,1法则
单一职能原则:一个类只做该做的事情
开闭原则:对扩展开放,对修改关闭
依赖倒转原则:面向接口编程,在需要的地方不直接放实现类,而放接口
接口隔离原则:接口要小而专,不能大而全
合成聚合复用原则:优先使用聚合或者组合模式复用代码
迪米特法则:一个对象应该对其他对象尽可能少的了解

30synchronized实现原理以及锁优化
修饰代码块时,采用monitorenter 与moniterexit两个指令来实现同步
修饰方法时,采用acc_synchronized标记符来实现同步,他们都是基于monitor实现的,对象的对象头里面由mark word,指向了monitor
优化:在jdk1.6之前,直接采用Monitorenter ,这种实现被称为重量级锁,1.6开始,增加了适应性自旋,锁解除,锁粗化,轻量级锁,偏向锁

31threadLocal原理及注意点
threadlocal是一种线程隔离机制,它提供了多线程环境下访问共享变量的安全性,它的实现原理是,在Thread类里面有一个成员变量ThreadLocalMap,它专门用来存储当前线程的共享变量副本,但是如果不及时移除共享变量,会造成内存泄漏,因为存储时,Key对应的对象使用的是弱引用对象,这种对象的特点是,只要虚拟机发生垃圾回收,就一定会回收,但是,对应的引用还在,只要当前线程没有被回收,内存空间一直没有被释放。内存泄漏的常见场景是线程复用导致的。

32countdownlatch 与cyclibarier区别
countdownlatch的计数器只能使用一次,而cyclibarier的计数器可以使用多次
countdownlatch计数方式为-1,而cyclibarier的计数方式为+1
countdownlatch初始值大于0,cyclibarier初始值等于0
countdownlatch会阻塞主线程,cyclibarier不会阻塞主线程,指挥阻塞子线程

33fork/join框架理解
java7提供的一个并行执行任务的框架,一个大任务分成多个小任务,最终将多个小任务的结果汇总
需要理解两个点,分治,工作窃取
工作窃取,大任务拆分为小任务,放到不同队列执行,有的线程自己负责的任务做完了,就抢别的线程的任务来做,为了减少锁竞争,通常使用双端队列,即快线程和慢线程各执行一端

34知道哪些收集器,各自优缺点,重点讲下G1和cms
serial:单线程收集器,垃圾收集时,必须stop the world,使用复制算法
Parnew:serial收集器的多线程版本,垃圾收集时,也必须stop the world,使用复制算法
serial old:serial收集器老年代版本,单线程收集,使用标记整理算法
paralell:新生代收集器,复制算法,多线程收集器
paralellold:paralell老年代收集器,使用标记整理算法
cms收集器:一种以获得最短回收停顿为目标的收集器,标记清除算法,运作过程,初始标记,并发标记,重新标记,并发清除,收集结束后,会产生大量碎片空间
g1收集器:标记整理算法实现,运作过程,初始标记,并发标记,最终标记,最终标记,不会产生碎片,可精准控制停顿

35jvm内存模型了解多少,比如重排序,内存屏障,happens-before,主内存
java内存模型规定了所有变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了该线程要用到的变量副本,线程对变量的修改必须在工作内存中进行,而不能直接操作主内存
在不影响运行结果的前提性,编译器和处理器会改变代码的运行顺序,提高程序的运行顺序,这就是重排序。
内存屏障也叫内存栅栏,一种cpu指令,用于控制特定条件下重排序和内存可见性问题
LoadLoad 屏障:对于这样的语句 Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证 Load1 要读取的数据被读取完毕
StoreStore 屏障:对于这样的语句 Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证 Store1 的写入操作对其它处理器可见
LoadStore 屏障:对于这样的语句 Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证 Load1 要读取的数据被读取完毕
StoreLoad 屏障:对于这样的语句 Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证 Store1 的写入对所有处理器可见。它的开销在四种屏障中最大。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障功能
happen-before原则:
单线程happen-before原则:在同一个线程中,书写在前面的操作happen- before 后面的操作。 
锁happen-before 原则:同一个锁unlock 操作happen-before 此锁lock 操作。
volatile happen-before 原则:对一个 volatile 变量写操作happen-before 对此变量任意操作(当然也包括写操作了)。 
happen-before 传递性原则:如果 A 操作 happen-before B 操作,B操作happen-before C 操作,那么 A 操作happen-before C 操作。 
线程启动happen-before 原则:同一个线程start 方法happen-before此线程其它方法。
线程中断happen-before 原则 :对线程 interrupt 方法调用happen-before 被中断线程检测到中断发送✁代码。
线程终结happen-before 原则: 线程中✁所有操作都happen-before线程终止检测。
对象创建happen-before 原则: 一个对象初始化完成先于他finalize方法调用


36简单说说类加载机制,双亲委派机制可以打破吗,如何打破
类加载器,根据类的全限定名,将class文件加载到内存中,并形成class对象
有三种,启动类加载器,也叫引导类加载器,负责lib目录下的类加载,扩展类加载器,负责ext目录下的类加载,应用程序类加载器,负责加载用户的类
当有一个类需要加载时,先交给父加载器进行加载,如果父类不加载,才进行自己加载,这种方式叫做双亲委派模式
有两种方式可以破坏双亲委派机制
1继承classLoader抽象类,重写LoadClass方法,在这个方法里可以自定义要加载的类使用的类加载器
2使用线程上下文加载器,使用java.lang.Thread类的setContextClassLoader方法来设置当前类使用的类加载器

37强软弱虚四种引用区别
强引用,Object o = new Object(),这种引用就是强引用,只要不是垃圾,即使内存不够也不能回收
软引用,弱引用,虚引用,是三个类
如果一个对象只有软引用,如果内存空间够,就不会回收,如果内存空间不够了,就会回收
弱引用,只要发生回收,不管内存够不够,都会被回收
虚引用,如果一个对象仅仅持有虚引用,那么它就和没有引用一样,任何时候都能被垃圾回收器回收,主要用来跟踪对象被垃圾回收器回收的活动

38常见分布式事务解决方案

39rocketMQ如何保证数据不丢失
要保证消息不丢失,从三个阶段保证
发送阶段:采用同步发送,因为只要返回了,肯定已经发送成功了
存储阶段:同步刷盘,等消息持久化到磁盘了,才返回成功响应
消费阶段:执行完消费逻辑,才通知broker消费成功

40spring声明式事务原理,哪些情况会失效
声明式事务,即@Transaction注解,可以帮我们自动开启提交回滚事务,通过aop方式进行管理,在初始化过程中,对bean进行代理
失效:
方法修饰符必须位public
方法不能加final
在同一个类内部直接调用
多线程调用,两个方法不在同一个线程中,获取到的数据库连接不一样
内部try{}catch(){}了异常
表本身不支持事务


41redis可以存储的最大值分别为多少,key 和value
512M

42怎么利用redis实现数据去重
用redis的set可以去除重复数据

43mysql B+tree高度如何计算,比如有100W数据,int类型
innodb存储引擎最小存储单元,页,每页的大小为16k,B+tree 叶子节点存放数据,非叶子节点存放指针,假设b+tree的高度为2的话,即有一个根节点和若干个子节点,这棵b+tree存放的记录数为总指针数*每个页存放的记录数,如果一行记录的大小为1k,每页就能存16条数据,非叶子节点能存放多少指针呢,我们假设主键的类型为bigint类型,长度为8字节,而指针大小为6字节,
16k/14b = 1470,因此一棵高度为2的b+tree,能存放1170*16 = 18720条数据,同理,一颗高度为3的b+tree,能存放1170*1170*16=21902400条数据,也就是说,高度为3的b+tree已经能存2000多万的数据了

44线程池有哪些状态,获取多线程并发执行结果有哪些方式
线程池有5各状态,分别是
running:能接受新的任务,也能处理阻塞队列中的任务
shutdown:关闭状态,不再接受新的任务,但是会处理阻塞队列中的任务,再running状态的线程池,调用方法shutdown会进入此状态
stop:不能接受新任务,也不会处理阻塞队列中的任务,会中断正在处理任务的线程,调用shutdownNow方法会进入此状态
tidying:如果所有任务都已经终止了,并且工作线程数为0,线程池进入该状态后会调用terminated方法进入terminated状态
terminated:再terminate方法执行完毕后,会进入此状态,

45有几种实现线程池的方式
通过 Executors 工厂方法创建
通过 new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) 自定义创建
推荐使用第二种,因为Executors 创建的线程池内部很多地方用到了无界任务队列,在高并发场景下,无界任务队列会接收过多的任务对象,严重情况下会导致 JVM 崩溃
newFixedThreadPool 和 newSingleThreadExecutor 的主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM,newCachedThreadPool 和 newScheduledThreadPool 的主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM

46线程池拒绝策略
ThreadPoolExecutor.CallerRunsPolicy:再调用者的线程种进行运行
ThreadPoolExecutor.AbortPolicy:直接丢弃任务,并抛出异常
ThreadPoolExecutor.DiscardPolicy:直接丢弃任务,并且什么也不做
ThreadPoolExecutor.DiscardOldestPolicy:该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

47threadlocal使用场景,原理,内存泄漏?

48四种引用的区别是什么

49kalfka为什么这么块

50kalfka如何保证消息的有序性

51nacos动态更新原理
首先,nacos采用的是长轮询的方式,也就是说,nacos客户端向服务端发起查询配置更新请求,所谓长轮询,就是服务端的配置没有更新的时候,这个连接会一直打开,知道服务端有配置变更或者超时之后才返回,nacos客户端需要获取服务端变更的配置内容,但是前提是需要进行比较,也就是说需要将客户端本地缓存的配置信息和从服务端获取的配置信息进行比较,一旦发现本地缓存的配置内容和服务器端的配置内容有差异,就表示服务器端的配置有更新,就需要把更新的配置拉到本地,在这个过程中,有可能需要拉去的配置比较多,使得拉取的效率比较低,nacos做了两个方面的优化
1减少网络通信的数据量,客户端把需要进行比对的配置项进行分片,每个分片大小为3000项
2分阶段进行比对和更新,第一阶段,客户端把3000个配置项的Key和对应的value的MD5值拼接为一个字符串发送到服务端,服务端会比较出MD5不同的Key,返回给客户端,第二阶段,客户端拿到这些有变更的key,去服务端获取对应的value值

52什么叫零拷贝

53一共有几种io模型,nio和多路复用的区别

54future实现阻塞等待获取结果的原理
FutureTask实现了RunableFuture接口,RunableFuture又继承了Runable和Future接口,Future表示一个任务声明周期,并提供了相应的方法来判断是否已经完成获取,以及任务结果的获取以及取消,当runable没有运行完的时候,Future获取结果就会阻塞,当运行完毕后,就会通知Future获取结果,

55reentrantlock和synchronized的区别
synchronized是java关键字,依赖jvm实现,reentrantlock是javaapi
在synchronized优化以前,效率比reentrantlock低很多,引入了偏向锁,自旋锁以后,性能相差不大
synchronized使用比较方便,由jvm自己保证加锁和解锁,reentrantlock需要程序员自己通过编程的方式来加锁和释放锁
reentrantlock可以被中断,synchronized不可被中断

56聊聊AQS,reentrantlock实现原理
reentrantlock的底层实现有几个非常关键的技术
锁的竞争,是通过互斥变量,使用CAS机制来实现的
没有竞争到锁的线程,会被存储到一个AbstractQueuedSynchronizer的队列同步器种,底层是通过双向链表来实现的,当锁被释放之后,会从AQS的头部唤醒一个等待的线程
公平和非公平的特性,主要体现在竞争锁的时候,是否需要判断AQS队列种有等待的线程
最后,关于锁的重入性,是在AbstractQueuedSynchronizer内部有一个变量来保存当前获得锁的线程,当同一个线程下一次再来获取锁的时候,就不会走锁的竞争逻辑,而是直接增加锁的重入次数。

57tcp如何实现拥塞控制

58jvm调优

59数据库分库分表后有啥缺点
1事务问题,已经不再是本地事务了,需要用分布式事务
2跨节点join问题,无法链表查询
3id问题,不能使用数据库自己的主键生成策略
4跨分片,数据分页问题

60三个数求和

61聊聊binlog日志

62hashmap实现原理,为啥要用红黑树,而不用平衡二叉树,为什么大于8的时候要转
jdk1.7时,底层实现为数组加链表
jdk1.8时,底层实现为数组加链表加红黑树,当链表长度大于8,数组大小大于等于64,链表会转为红黑树
红黑树是一种平衡二叉树,插入,删除,查找最坏时间复杂度都是O(logn),而二叉树最坏时间复杂度为n
平衡二叉树比红黑树更加严格,为了保持平衡,需要旋转的次数更多,效率更低

63concurrenthashmap实现原理
concurrenthashmap相当于hashmap的多线程版本,内部分为16个段,并发的时候,对每个段单独枷锁,保证了线程安全,jdk1.8开始,改成了红黑树实现

64http与https有何区别
1端口不一样 一个是80 一个是443
2一个是明文传输,一个是加密传输
3一个不需要证书,一个需要证书

65redis哨兵机制

66无重复字符最长字串

67谈谈你对seta的理解

68谈谈arrayList自动扩容
arrayList内部使用数组实现,默认情况下,数组的长度是10,当然也可以在创建的时候设置数组长度,随着程序的运行,当达到数组的最大容量的时候,就需要扩容,需要新创建一个数组,然后把之前的元素拷贝到新的数组种,然后将要添加的元素添加到数组中。

69谈谈你对jvm中主要gc的理解

70为什么引入偏向锁,轻量级锁
synchronized在jdk1.6以前通过重量级锁的方式来解决线程之间锁的竞争,之所以称它为重量级锁,是因为它依靠操作系统的Mutex lock来实现互斥功能,Mutex 是系统功能,由于权限隔离的原因,应用程序调用系统方法需要切换到内核态来执行,这里涉及到用户态和内核态的切换,这个切换会待来性能的损耗
在jdk1.6版本,synchronized增加了锁升级的机制,来平衡数据安全性和性能,简单来说,就是一个线程去访问synchronized同步代码块的时候,会根据线程竞争的情况,会先尝试不加重量级锁的情况下保证线程安全,所以引入了偏向锁和轻量级锁,偏向锁就是锁偏向于某个线程,简单来说就是通过CAS修改偏向锁标记,这种锁适合同一个线程多次去申请同一个锁资源且没有其他线程竞争的情况,
轻量级锁也可以称为自旋锁,基于自适应自旋的机制,通过多次自旋重试去竞争锁,自旋锁的优点在于它避免了用户态到内核态的切换导致的性能损耗,
synchronized引入了锁升级以后,如果有线程去竞争锁,首先会尝试用偏向锁的方式去竞争锁资源,如果能竞争到偏向锁,表示枷锁成功,如果竞争失败,表示当前锁以前偏向其他线程,需要将锁升级为轻量级锁,在轻量级锁的情况下,竞争锁的线程根据自旋锁的次数去竞争锁资源,如果在轻量级锁的情况下还没有竞争到锁,就升级为重量级锁,就会阻塞,线程的状态就是阻塞状态,需要获得锁的线程去唤醒

71存放MD5用char还是varchar
因为md5字符串是16位或者32位,选一种,因此是char存储

72binlog的undolog和redolog的区别

73看门狗的原理是什么
在获取锁的时候,不指定leaseTime或者指定为-1,这样才能开启看门狗机制,
如果获取锁成功,则开启看门狗机制,并且为当前锁添加一个延迟任务,就是在10秒后将锁的有效期刷新为30秒,并且再次添加一个延迟任务


74讲讲java jnm volitale实现原理
jmm表示java内存模型,jvm的内存分为主内存和工作内存,变量都存储在主内存种,线程的工作内存种保存了变量的副本,对变量的操作必须都在工作内存中,而不能直接操作主内存,
volitale能保证变量的改变能立刻刷新到主内存,并且禁止指令的重排序

75高并发下如何设计秒杀系统
页面静态化:秒杀活动页面,大多数内容都一样,没有必要每次都请求服务器,可以做程静态的,减少对服务端的访问,秒杀用户分布在全国各地,可以使用cdn
按钮置灰:没有到秒杀时间的时候,按钮置灰,因为秒杀用户在几秒前会疯狂点击,还没开始秒杀呢,服务就挂了
服务单一职责:服务单一,减小压力
限流:对同一用户限流,对同一ip限流
分布式锁:为了解决超卖的问题,加分布式锁来保证不超卖
mq异步处理:瞬间请求过大,可以先放入mq,异步处理
限流,降级,熔断,防止请求过大压垮服务器,秒杀服务有问题了,降级处理,不要影响别的服务,服务有问题就熔断

76排序链表
新建一个链表,用插入的方式,来进行排序

77延时场景处理
1jdk 延迟队列,delayqueue
2时间轮算法
3定时任务
4redis zset
5mq延时消息

78spring中有两个id相同的bean,会报错吗,如果会,在什么阶段报错
如果在一个xml文件中定义两个id相同的bean,会在启动阶段报错,报相同的beanName已经被使用
如果时不同的xml文件,会覆盖
如果使用@Bean注解的方式定义bean,只会注册第一个bean

79如何理解springboot中的starter
starter以功能为维度,维护对应的jar包版本,使得开发者不用去关心依赖哪些jar包,和版本冲突的问题
starter中的bean会自动注入到spring容器
依赖starter组件以后,这个组件对应的功能所需要维护的外部配置,都会自动集成到springboot里面


80为什么要使用spring
ioc/di:通过ioc容器实现了bean声明周期管理,实现了对象依赖的松耦合
面向切片编程:把业务逻辑与系统服务分开,如打日志
mvc:提供了功能更加强大的web支持
声明式事务管理
方便与其他技术进行整合

81spring如何解决循环依赖
spring使用三级缓存来解决循环依赖的问题,所谓三级缓存,就是3个map,分别用来存放不同类型的bean
一级缓存用来存放初始化好的bean
二级缓存用来存放半成品,也就是创建好,但是属性还没有赋值的对象
三级缓存用来存放Bean工厂对象
过程描述:
先创建A,将A放到三级缓存中,接着开始注入b,发现工厂中没有B,就创建B,这时要注入A属性,先去一级缓存查询,发现没有,再查二级缓存,还是没有,再查3级缓存,发现有,将A放到二级缓存里面,并删除三级缓存中的A,并设置A属性,这时,B初始化完毕,放到一级缓存中,这时接着创建A,还是查找一级缓存,发现有,拿来赋值,完成创建,并放入一级缓存

82spring中BeanFactory和FactoryBean的区别
BeanFactory是spring Bean工厂的顶级接口,可以创建Bean,并且解决属性注入的问题
FactoryBean 是一个工厂bean,是一个接口,主要功能是动态生成某一类型的Bean实例

83spring中Bean的作用域有哪些
理论上来说,常规的声明周期只有两种
1单例,就是spring容器中只有一个此类型的对象
2原型,就是每次从容器中获取,都会是一个新的对象
但是在基于spring的web应用里面,增加了一个会话维度来控制bean的生命周期,主要有三种
1request 每次请求创建一个
2session 会话维度,同一个会话一个实例,不同的会话不同的实例
3globalSession 全局会话维度

84spring bean的声明周期的执行流程
大致分为5个阶段
创建前的准备阶段
从上下文和相关配置中,查找bean有关的扩展实现,比如init-method,destory-method
创建阶段
通过反射来创建Bean实例
依赖注入阶段
如果bean实例中有对其他对象的依赖情况,就需要对这些属性进行注入
容器缓存阶段
将bean对象存储到bean工厂中,共开发者使用
销毁阶段
容器关闭时,销毁容器中的所有对象

85spring中有哪些方式可以把bean注入道容器中
有7种方式
1使用xml的方式声明bean的定义
2使用ComponentScan来扫描@Controller 等注解的类
3使用@Configuration标记配置类,并使用@Bean注解实现bean的定义,
4使用@Import注解,导入配置类或者普通的bean
5使用FactoryBean工厂bean,创建一个Bean
6现ImportBeanDefinitionRegistrar接口,可以动态注入Bean实例
7实现ImportSelector接口,动态批量注入配置类或者Bean对象

86springboot自动装配原理
自动装配就是spring自动把其他组件中的Bean加入到容器当中,不需要开发人员在配置文件中添加大量的配置
实现原理:基于springfactory机制获取对应依赖META-INF目录下的spring.factorys文件,得到里面配置的类,然后将其中的Bean放入到ioc容器中

87springboot中约定优于配置,你的理解是什么
约定优于配置是一种软件设计的思想,目的是减少开发人员对程序中配置项的维护,springboot就是这一思想的产物,比如spring开发时,需要在xml中配置很多的东西,web.xml中配置也不能少,但是在springboot中,不需要,比如springboot的启动依赖,会帮我们管理很多依赖包,自动装配机制的实现中,通过扫描约定路径下的spring.factories文件来识别配置类,实现bean的自动装配

88springcloud的理解
springcloud是一套微服务解决方案,是一套规范,定义了一个微服务中有哪些东西,如服务注册与发现,流量控制,链路追踪等,具体实现有两家netflex 和alibaba

89springmvc的理解
springmvc是spring框架的一个模块,是一个基于servlet的一个web框架
它的主要目的是简化servlet+jsp的开发,并且多web中的mvc模式做了增强和扩展,比如
控制器分为了前端控制前和后端控制器
把model分成了service和repository
视图层也支持更多类型的视图,jsp,freemaker,velocity
具体工作流程是,浏览器发出的请求到了前端控制器,前端控制器找到处理该请求的后端控制器,后端控制器返回modelandview,前端控制器找到视图解析器,将modelandview解析之后,形成html,返回给客户端浏览器

90spring中的bean是线程安全的吗
spring种的bean是否安全取决于bean本身,如果是多例,就是安全的,如果是单例,要分情况,如果查询,就是安全的,如果涉及到内部属性的修改,就是不安全的

91为什么spring中每个bean都要定义作用域
spring中的bean有5种作用域,分别为:
prototype,singleton,request,session,globalsession
其中,第一个和第二个是表叫常用的,后面的三个属于web环境中的,
定义bean的作用域,用户就可以通过配置的方式限定bean的作用范围,以保护bean的使用安全

92详述springmvc的执行流程

93springboot自动装配原理

94哪些情况下会破坏单例
多线程破坏单例:懒汉式单例
指令重排序破坏单例:
克隆破坏单例:在java中,所有的类都继承了Object,调用克隆方法,是不是就会返回一个新的对象呢?我们可以在克隆方法里面,将引用指向当前对象。
反序列化破坏单例:需要重写readResolve方法,将返回的引用指向已经存在的单例对象
反射破坏单例:需要在构造方法种进行判断,是否已经被创建,如果已经创建,则抛出异常,或者用枚举,不允许反射访问枚举。

95autowored和resource的区别
autowired这个注解是由spring提供的,可以标记在构造方法,属性,set方法上,而resource注解是由jdk提供的,可以标注在类上,属性上
默认的装配方式不同:autowired默认按类型装配,resource默认按名称进行装配,
autowired首先按照类型装配,接着按照名称进行装配,如果发现还由多个,则报错
resource根据是否指定name和type分成了4种
如果同时指定了类型和名称,则从容器种找到唯一匹配的对象进行装配,如果找不到,则报错
如果指定了名称,则从容器种找唯一的对象进行装配,如果找不到,则报错
如果指定了类型,则从容器种找唯一的对象进行装配,找到多个或者找不到,都会报错
如果都没有指定,则先按名曾进行装配,如果没有找到,则按类型装配

96什么是mvcc

97秒杀场景中常用的限流算法
1计数器限流算法
2滑动窗口限流算法
3漏桶限流算法
4令牌桶限流算法

98谈谈你对时间轮的理解
时间轮,简单理解,就是一个存储定时任务的环状数组,它由两部分组成,一部分是环状数组,另一部分是遍历环状数组的指针,当我们向时间轮里面添加定时任务的时候,会根据定时任务的执行时间来计算它的下标,当然,在一个下标上,可能存在多个定时任务,这个时候使用双向链表,当指针指向某个下标时,就去除下标种的定时任务,遍历挨个执行,
优点是,添加和删除定时任务的比较简单,缺点是,对于执行时间非常严格的定时任务,时间轮不是很适合,因为时间轮算法的精度取决于最小时间单元的粒度。

99AQS为什么要用双向链表
双向链表由前驱节点和后继节点,因此,在插入和删除的时候,不需要从头节点开始遍历,效率比较高
第一个原因是,没有竞争到所得线程被加入到阻塞队列并且阻塞等待得前提是,当前线程所在得前置节点是正常节点,这样设计是为了避免前驱节点的线程存在异常导致无法唤醒后续线程的问题
所以,线程阻塞之前要判断前驱节点的状态,如果是单项链表,就得从头开始遍历,效率比较低
第二个原因,没有竞争到锁得线程被加入到阻塞队列是允许被中断得,这时被中断得线程状态会改为cancle,这个时候,线程不需要去竞争锁,但是还存在队列里面,这个线程就需要被删除,否则会导致后续得线程无法被唤醒,这个时候,如果时单向链表,只能从头节点开始遍历,效率低
第三个原因,刚刚加入到队列得线程,会通过自旋去尝试获得锁,但是按照公平锁得设计,除了头节点后第一个元素,其他得没有必要去竞争锁,所以,加入到队列中得线程在尝试竞争锁之前,需要判断前驱节点是不是头节点,如果时单向链表,只能从头节点开始遍历,效率低

100什么是云原生
云原生4要素
1必须是微服务
2必须实现容器化
3支持devops
4必须满足持续交付

101java有几种实现线程池的方式

102hashmap什么时候会扩容,如何扩容
当元素的个数达到阈值的时候进行扩容,阈值=数组大小*负载因子,
如何扩容:新建一个数组,把元素拷贝过去。

103谈谈arraylist自动扩容原理

104谈谈你对jvm中主要gc算法的理解

105谈谈你对nacos动态配置原理的了解

106谈谈你对Oauth的理解
1客户端向资源拥有者发送授权请求
2客户端会收到资源服务器的授权许可
3客户端拿到许可之后,向授权服务器发送验证,授权服务器会颁发一个令牌给客户端
4客户端拿到令牌后,交给资源服务器
5资源服务器拿到令牌之后,向授权服务器验证令牌的有效性
6资源服务器验证令牌通过之后,返回受保护的资源

107hashmap与treemap的区别
他们的区别,主要表现在以下4个方面,
数据结构方面,hashmap用数组+链表实现,treemap用红黑树实现
效率方面,Hashmap的时间复杂度时O(1),treemap的时间复杂度是对数阶
线程安全方面,他们都是非线程安全的
应用场景方面,hashmap是无序的,treemap是有序的

108谈谈你对rpc框架的理解

109谈谈你对iaas paas saas的理解

110spring中用了哪些设计模式
工厂模式,单例模式,装饰器模式,策略模式,适配器模式,代理模式,模板方法模式,观察者模式

111spring中实现异步调用的方式

112springmvc9大组件的理解
MultipartResolver:多文件上传组件
LocaleResolver:多语言支持组件
ThemeResolver:主题模板处理组件
HandlerMappingURL:将请求url和controller映射的组件
HandlerAdapter:业务逻辑适配组件
HandlerExceptionResolver:异常处理组件
RequestToViewNameTranslator:视图名称提取组件
ViewResolvers:视频渲染组件
FlashMapManage:闪存管理组件

113一个空的javaObject占用多大内存
一个java对象主要由三部分组成,对象头,实例数据,对齐填充
markword64位操作系统中8个字节,32位操作系统中占4个字节,类元指针,开启指针压缩,占用4个字节,不开启占用8个字节,数组长度,当时对象数组,才有
实例数据,就是各字段所占字节的和
填充:按照8的倍数来填充

114数据量大了一定要分库分表吗
只分库部份表:当数据的读写压力大了,连接不够用了,这个时候可以分库,来获取更多的连接,提高系统性能
只分表不分库:数据库的压力里不大,连接够用,但是单表数据量太大,插入,读取效率低,这时候需要分表
既分库也分表:综合上面的两种情况。

115说说innodb索引数据结构的理解

116说说线程池的执行原理

线程池有如下几个核心参数
核心线程数,最大线程数,最大空余线程数
线程池启动的时候,会创建出若干个核心线程数,当有任务提交到线程池时,先交给核心线程来出里,如果核心线程中没有空闲线程,则加入任务队列,如果任务队列也满的话,创建一个新线程来处理任务,最多能创建的线程就是最大线程数,当达到最大线程后,并且任务队列满的时候,开始执行拒绝策略。





 

Tags:

很赞哦! ()

上一篇:消息队列面试专栏

下一篇:返回列表

文章评论

    共有条评论来说两句吧...

    用户名:

    验证码:

本站推荐

站点信息

  • 网站名称:JavaStudy
  • 建站时间:2019-1-14
  • 网站程序:帝国CMS7.5
  • 文章统计242篇文章
  • 标签管理标签云
  • 统计数据百度统计
  • 微信公众号:扫描二维码,关注我们