常用面试题

面试题:

HashMap底层实现原理,红黑树,B+树,B树的结构原理,volatile关键字,CAS(比较与交换)实现原理
Spring的AOP和IOC是什么?使用场景有哪些?Spring事务,事务的属性,传播行为,数据库隔离级别
Spring和SpringMVC,MyBatis以及SpringBoot的注解分别有哪些?SpringMVC的工作原理,SpringBoot框架的优点,MyBatis框架的优点
SpringCould组件有哪些,他们的作用是什么?(说七八个)微服务的CAP是什么?BASE是什么?
设计模式(说五六个)
Redis支持的数据类型以及使用场景,持久化,哨兵机制,缓存击穿,缓存穿透
线程是什么,有几种实现方式,它们之间的区别是什么,线程池实现原理,JUC并发包,ThreadLocal与Lock和Synchronize区别
分布式事务(不同系统之间如何保证数据的一致性(A系统写入数据,B系统因为某些原因没有写入成功,造成数据不一致))
安全性问题(数据篡改(拿到别人的URL,篡改数据(金额)发送给系统))
索引使用的限制条件,sql优化有哪些,数据同步问题(缓存,数据库数据同步)
初始化Bean对象有几个步骤,它的生命周期
JVM内存模型,算法,垃圾回收器,调优,类加载机制(双亲委派),创建一个对象,这个对象在内存中是怎么分配的?
如何设计一个秒杀系统,(高并发高可用分布式集群)
悲观锁,乐观锁,读写锁,行锁,表锁,自旋锁,死锁,分布式锁,线程同步锁,公平锁,非公平锁分别是什么
堆溢出,栈溢出的出现场景以及解决方案
说出几种MQ之间的区别,以及为什么使用这种MQ,消息重复发送(幂等性),消息发送失败,消息掉包,长时间收不到消息,发送的消息太大造成接收不成功
单点登录实现原理
假如有上亿条数据,你如何快速找到其中一条你想要的数据(几种简单的算法)
Dubbo的运行原理,支持什么协议,与SpringCould相比它为什么效率要高一些,Zookeeper底层原理
假如你带一个团队,让你设计一个系统,你需要考虑哪些
答案:

HashMap底层实现原理,红黑树,B+树,B树的结构原理,volatile关键字,CAS(比较与交换)实现原理
首先HashMap是Map的一个实现类,而Map存储形式是键值对(key,value)的。可以看成是一个一个的Entry。Entry所存放的位置是由key来决定的。

Map中的key是无序的且不可重复的,所有的key可以看成是一个set集合,如果出现Map中的key如果是自定义类的对象,则必须重写hashCode和equals方法,因为如果不重写,使用的是Object类中的hashCode和equals方法,比较的是内存地址值不是比内容。

Map中的value是无序的可重复的,所有的value可以看成是Collection集合,Map中的value如果是自定义类的对象必须重写equals方法。

至于要重写hashCode和equals分别做什么用,拿hashMap底层原理来说:

当我们向HashMap中存放一个元素(k1,v1),先根据k1的hashCode方法来决定在数组中存放的位置。

如果这个位置没有其它元素,将(k1,v1)直接放入Node类型的数组中,这个数组初始化容量是16,默认的加载因子是0.75,也就是当元素加到12的时候,底层会进行扩容,扩容为原来的2倍。如果该位置已经有其它元素(k2,v2),那就调用k1的equals方法和k2进行比较二个元素是否相同,如果结果为true,说明二个元素是一样的,用v1替换v2,如果返回值为false,二个元素不一样,就用链表的形式将(k1,v1)存放。

不过当链表中的数据较多时,查询的效率会下降,所以在JDK1.8版本后做了一个升级,hashmap就是当链表中的元素达到8并且元素数量大于64时,会将链表替换成红黑树才会树化时,会将链表替换成红黑树,来提高查找效率。因为对于搜索,插入,删除操作多的情况下,使用红黑树的效率要高一些。

原因是因为红黑树是一种特殊的二叉查找树,二叉查找树所有节点的左子树都小于该节点,所有节点的右子树都大于该节点,就可以通过大小比较关系来进行快速的检索。

在红黑树上插入或者删除一个节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树,可以通过左旋和右旋,使这颗树重新成为红黑树。红黑树的5条性质(根节点是黑色,每个节点是黑色或者是红色,每个叶子节点是黑色,如果一个节点是红色它的子节点必须是黑色的,从一个节点到该节点的子孙外部节点的所有路径上包含相同数目的黑点)

img

而且像这种二叉树结构比较常见的使用场景是Mysql二种引擎的索引,Myisam使用的是B树,InnoDB使用的是B+树。

首先B树它的每个节点都是Key.value的二元组,它的key都是从左到右递增的排序,value中存储数据。这种模式在读取数据方面的性能很高,因为有单独的索引文件,Myisam 的存储文件有三个.frm是表的结构文件,.MYD是数据文件,.MYI是索引文件。不过Myisam 也有些缺点它只支持表级锁,不支持行级锁也不支持事务,外键等,所以一般用于大数据存储。

img

然后是InnoDB,它的存储文件相比Myisam少一个索引文件,它是以 ID 为索引的数据存储,数据现在都被存在了叶子结点,索引在非叶结点上。而这些节点分散在索引页上。在InnoDB里,每个页默认16KB,假设索引的是8B的long型数据,每个key后有个页号4B,还有6B的其他数据,那么每个页的扇出系数为16KB/(8B+4B+6B)≈1000,即每个页可以索引1000个key。在高度h=3时,s=1000^3=10亿!!也就是说,InnoDB通过三次索引页的I/O,即可索引10亿的key,而非叶节点这一行存储的索引,数量就多了,I/O的次数就少了。而Myisam在每个节点都存储数据和索引,这样就减少了每页存储的索引数量。而且InnoDB它还支持行级,表级锁,也支持事务,外键.

img

另外对于HashMap实际使用过程中还是会出现一些线程安全问题:

HashMap是线程不安全的,在多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,而且会抛出并发修改异常,导致原因是并发争取线程资源,修改数据导致的,一个线程正在写,一个线程过来争抢,导致线程写的过程被其他线程打断,导致数据不一致。

HashTable是线程安全的,只不过实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁。多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。

为了应对hashmap在并发环境下不安全问题可以使用,ConcurrentHashMap大量的利用了volatile,CAS等技术来减少锁竞争对于性能的影响。

在JDK1.7版本中ConcurrentHashMap避免了对全局加锁,改成了局部加锁(分段锁),分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。不过这种结构的带来的副作用是Hash的过程要比普通的HashMap要长。

所以在JDK1.8版本中CurrentHashMap内部中的value使用volatile修饰,保证并发的可见性以及禁止指令重排,只不过volatile不保证原子性,使用为了确保原子性,采用CAS(比较交换)这种乐观锁来解决。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。

如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。

volatile有三个特性:可见性,不保证原子性,禁止指令重排。

可见性:线程1从主内存中拿数据1到自己的线程工作空间进行操作(假设是加1)这个时候数据1已经改为数据2了,将数据2写回主内存时通知其他线程(线程2,线程3),主内存中的数据1已改为数据2了,让其他线程重新拿新的数据(数据2)。

不保证原子性:线程1从主内存中拿了一个值为1的数据到自己的工作空间里面进行加1的操作,值变为2,写回主内存,然后还没有来得及通知其他线程,线程1就被线程2抢占了,CPU分配,线程1被挂起,线程2还是拿着原来主内存中的数据值为1进行加1,值变成2,写回主内存,将主内存值为2的替换成2,这时线程1的通知到了,线程2重新去主内存拿值为2的数据。

禁止指令重排:首先指令重排是程序执行的时候不总是从上往下执行的,就像高考答题,可以先做容易的题目再做难的,这时做题的顺序就不是从上往下了。禁止指令重排就杜绝了这种情况。

(一般面试官开始问你会从java基础问起,一问大多数会问到集合这一块,而集合问的较多的是HashMap,这个时候你就可以往这些方向带着面试官问你,而且扩展的深度也够,所以上面的干货够你说个十来分钟吧,第一个问题拿下后,面试官心里至少简单你的基础够扎实,第一印象分就留下了)

Spring的AOP和IOC是什么?使用场景有哪些?Spring事务与数据库事务,传播行为,数据库隔离级别
AOP:面向切面编程。

即在一个功能模块中新增其他功能,比方说你要下楼取个快递,你同事对你说帮我也取一下呗,你就顺道取了。在工作中如果系统中有些包和类中没有使用AOP,例如日志,事务和异常处理,那么就必须在每个类和方法中去实现它们。 代码纠缠每个类和方法中都包含日志,事务以及异常处理甚至是业务逻辑。在一个这样的方法中,很难分清代码中实际做的是什么处理。AOP 所做的就是将所有散落各处的事务代码集中到一个事务切面中。

场景

比方说我现在要弄一个日志,记录某些个接口调用的方法时间。使用Aop我可以在这个接口前插入一段代码去记录开始时间,在这个接口后面去插入一段代码记录结束时间。

又或者你去访问数据库,而你不想管事务(太烦),所以,Spring在你访问数据库之前,自动帮你开启事务,当你访问数据库结束之后,自动帮你提交/回滚事务!

异常处理你可以开启环绕通知,一旦运行接口报错,环绕通知捕获异常跳转异常处理页面。

动态代理

Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。它的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

IOC:依赖注入或者叫做控制反转。

正常情况下我们使用一个对象时都是需要new Object()的。而ioc是把需要使用的对象提前创建好,放到spring的容器里面。

所有需要使用的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。DI(依赖注入)其实就是IOC的另外一种说法,其实它们是同一个概念的不同角度描述。

场景:

正常情况下我们使用一个对象时都是需要new Object() 的。而ioc是把需要使用的对象提前创建好,放到spring的容器里面。需要使用的时候直接使用就行,而且可以设置单例或多例,非常灵活。

我们在service层想调用另外一个service的方法,不需要去new了,直接把它交给spring管理,然后用注解的方式引入就能使用。

IOC三种注入方式

(1)XML:Bean实现类来自第三方类库,例如DataSource等。需要命名空间等配置,例如:context,aop,mvc。

(2)注解:在开发的类使用@Controller,@Service等注解

(3)Java配置类:通过代码控制对象创建逻辑的场景。例如:自定义修改依赖类库。

什么是事务?

事务是访问并可能更新数据库中各种数据项的一个程序执行单元。

Spring事务与数据库事务关系?

Spring的事务是对数据库的事务的封装,最后本质的实现还是在数据库,假如数据库不支持事务的话,Spring的事务是没有作用的。所以说Spring事务的底层依赖MySQL的事务,Spring是在代码层面利用AOP实现,执行事务的时候使用TransactionInceptor进行拦截,然后处理。本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

属性(特性)
A(原子性):要么全部完成,要么完全不起作用
C(一致性):一旦事务完成(不管成功还是失败),业务处于一致的状态,而不会是部分完成,部分失败。
I(隔离性):多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
D(持久性):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,事务的结果被写到持久化存储器中。

什么叫事务传播行为?

传播,至少有两个东西,才可以发生传播。单体不存在传播这个行为。事务传播行为就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

Spring支持7中事务传播行为
propagation_required(需要传播):当前没有事务则新建事务,有则加入当前事务
propagation_supports(支持传播):支持当前事务,如果当前没有事务则以非事务方式执行
propagation_mandatory(强制传播):使用当前事务,如果没有则抛出异常
propagation_nested(嵌套传播):如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行需要传播行为。
propagation_never(绝不传播):以非事务的方式执行,如果当前有事务则抛出异常
propagation_requires_new(传播需要新的):新建事务,如果当前有事务则把当前事务挂起
propagation_not_supported(不支持传播):以非事务的方式执行,如果当前有事务则把当前事务挂起

数据库事务的隔离级别

数据库事务的隔离级别有4个,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。

√: 可能出现 ×: 不会出现

说明 脏读 不可重复读 幻读
Read uncommitted
Read committed ×
Repeatable read × ×
Serializable × × ×

注意:我们讨论隔离级别的场景,主要是在多个事务并发的情况下,因此,接下来的讲解都围绕事务并发。
Read uncommitted 读未提交
公司发工资了,领导把20000元打到廖志伟的账号上,但是该事务并未提交,而廖志伟正好去查看账户,发现工资已经到账,是20000元整,非常高兴。可是不幸的是,领导发现发给廖志伟的工资金额不对,是16000元,于是迅速回滚了事务,修改金额后,将事务提交,最后廖志伟实际的工资只有16000元,廖志伟空欢喜一场。

出现上述情况,即我们所说的脏读,两个并发的事务,“事务A:领导给廖志伟发工资”、“事务B:廖志伟查询工资账户”,事务B读取了事务A尚未提交的数据。当隔离级别设置为Read uncommitted时,就可能出现脏读,如何避免脏读,请看下一个隔离级别。

Read committed 读提交
廖志伟拿着工资卡去消费,系统读取到卡里确实有2000元,而此时她的老婆也正好在网上转账,把廖志伟工资卡的2000元转到另一账户,并在廖志伟之前提交了事务,当廖志伟扣款时,系统检查到廖志伟的工资卡已经没有钱,扣款失败,廖志伟十分纳闷,明明卡里有钱,为何…

出现上述情况,即我们所说的不可重复读,两个并发的事务,“事务A:廖志伟消费”、“事务B:廖志伟的老婆网上转账”,事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。当隔离级别设置为Read committed时,避免了脏读,但是可能会造成不可重复读。大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle。如何解决不可重复读这一问题,请看下一个隔离级别。

Repeatable read 重复读
当廖志伟拿着工资卡去消费时,一旦系统开始读取工资卡信息(即事务开始),廖志伟的老婆就不可能对该记录进行修改,也就是廖志伟的老婆不能在此时转账。这就避免了不可重复读。廖志伟的老婆工作在银行部门,她时常通过银行内部系统查看廖志伟的信用卡消费记录。有一天,她正在查询到廖志伟当月信用卡的总消费金额(select sum(amount) from transaction where month = 本月)为80元,而廖志伟此时正好在外面胡吃海喝后在收银台买单,消费1000元,即新增了一条1000元的消费记录(insert transaction … ),并提交了事务,随后廖志伟的老婆将廖志伟当月信用卡消费的明细打印到A4纸上,却发现消费总额为1080元,廖志伟的老婆很诧异,以为出现了幻觉,幻读就这样产生了。当隔离级别设置为Repeatable read时,可以避免不可重复读,但会出现幻读。注:MySQL的默认隔离级别就是

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值