java面试题2019(中国平安)

平安产险–ai部门

redis各种应⽤场景
a. 更多的数据结构;
b. 可持久化;
c. 计数器;
d. 发布-订阅功能;
e. 事务功能;
f. 过期回调功能;
g. 队列功能;
h. 排序、聚合查询功能。
redis持久化机制
a. RDB:快照形式是直接把内存中的数据保存到⼀个 dump ⽂件中,定时保存,保存策略。(会丢数据)
b. AOF:把所有的对Redis的服务器进⾏修改的命令都存到⼀个⽂件⾥,命令的集合。(影响性能)
mysql调优
a. explain select语句;
b. 当只要⼀条数据时使⽤limit 1;
c. 为搜索字段建索引;
d. 避免select *;
e. 字段尽量使⽤not null;
f. 垂直分割;
g. 拆分⼤的delete和insert语句:delete和insert会锁表;
h. 分表分库分区。
有没了解Docker,Docker和虚拟机有什么区别?
1、虚拟机:我们传统的虚拟机需要模拟整台机器包括硬件,每台虚拟机都需要有⾃⼰的操作系统,虚拟机⼀旦被开启,预分配
给他的资源将全部被占⽤。,每⼀个虚拟机包括应⽤,必要的⼆进制和库,以及⼀个完整的⽤户操作系统。
2、Docker:容器技术是和我们的宿主机共享硬件资源及操作系统可以实现资源的动态分配。
容器包含应⽤和其所有的依赖包,但是与其他容器共享内核。容器在宿主机操作系统中,在⽤户空间以分离的进程运⾏。
3、对⽐:
docker启动快速属于秒级别。虚拟机通常需要⼏分钟去启动。
docker需要的资源更少,docker在操作系统级别进⾏虚拟化,docker容器和内核交互,⼏乎没有性能损耗,性能优于通过
Hypervisor层与内核层的虚拟化。;
docker更轻量,docker的架构可以共⽤⼀个内核与共享应⽤程序库,所占内存极⼩。同样的硬件环境,Docker运⾏的镜
像数远多于虚拟机数量。对系统的利⽤率⾮常⾼
与虚拟机相⽐,docker隔离性更弱,docker属于进程之间的隔离,虚拟机可实现系统级别隔离;
安全性: docker的安全性也更弱。Docker的租户root和宿主机root等同,⼀旦容器内的⽤户从普通⽤户权限提升为root
权限,它就直接具备了宿主机的root权限,进⽽可进⾏⽆限制的操作。虚拟机租户root权限和宿主机的root虚拟机权限是分离的,并且
虚拟机利⽤如Intel的VT-d和VT-x的ring-1硬件隔离技术,这种隔离技术可以防⽌虚拟机突破和彼此交互,⽽容器⾄今还没有任何形式
的硬件隔离,这使得容器容易受到攻击。
可管理性:docker的集中化管理⼯具还不算成熟。各种虚拟化技术都有成熟的管理⼯具,例如VMware vCenter提供完备
的虚拟机管理能⼒。
⾼可⽤和可恢复性:docker对业务的⾼可⽤⽀持是通过快速重新部署实现的。虚拟化具备负载均衡,⾼可⽤,容错,迁移
和数据保护等经过⽣产实践检验的成熟保障机制,VMware可承诺虚拟机99.999%⾼可⽤,保证业务连续性。
快速创建、删除:虚拟化创建是分钟级别的,Docker容器创建是秒级别的,Docker的快速迭代性,决定了⽆论是开发、测
试、部署都可以节约⼤量时间。
交付、部署:虚拟机可以通过镜像实现环境交付的⼀致性,但镜像分发⽆法体系化;Docker在Dockerfile中记录了容器构
建过程,可在集群中实现快速分发和快速部署;
同⼀个宿主机中多个Docker容器之间如何通信?多个宿主机中Docker容器之间如何通信?
1、这⾥同主机不同容器之间通信主要使⽤Docker桥接(Bridge)模式。
2、不同主机的容器之间的通信可以借助于 pipework 这个⼯具。
平安产险-核⼼系统部
1、简历:
介绍简历上主要项⽬,画框架图,说流程。
针对简历上的技能进⾏提问。
2、队列:
说说rabbitmq的结构。
a. 消息处理过程:
b. 四种交换机:
i. 直连交换机,Direct exchange:带路由功能的交换机,根据routing_key(消息发送的时候需要指定)直接绑定到队列,
⼀个交换机也可以通过过个routing_key绑定多个队列。
ii. 扇形交换机,Fanout exchange:⼴播消息。
iii. 主题交换机,Topic exchange:发送到主题交换机上的消息需要携带指定规则的routing_key,主题交换机会根据这个规
则将数据发送到对应的(多个)队列上。
iv. ⾸部交换机,Headers exchange:⾸部交换机是忽略routing_key的⼀种路由⽅式。路由器和交换机路由的规则是通过
Headers信息来交换的,这个有点像HTTP的Headers。将⼀个交换机声明成⾸部交换机,绑定⼀个队列的时候,定义⼀
个Hash的数据结构,消息发送的时候,会携带⼀组hash数据结构的信息,当Hash的内容匹配上的时候,消息就会被写⼊队
列。
rabbitmq队列与消费者的关系?
a. ⼀个队列可以绑定多个消费者;
b. 消息分发:若该队列⾄少有⼀个消费者订阅,消息将以循环(round-robin)的⽅式发送给消费者。每条消息只会分发给⼀个
订阅的消费者(前提是消费者能够正常处理消息并进⾏确认)。
rabbitmq交换器种类。
fanout交换器:它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中;
direct交换器:direct类型的交换器路由规则很简单,它会把消息路由到哪些BindingKey和RoutingKey完全匹配的队列中;
topic交换器:匹配规则⽐direct更灵活。
headers交换器:根据发送消息内容的headers属性进⾏匹配(由于性能很差,不实⽤)。
项⽬中哪⾥⽤到了kafka,kafka特性?
a. 场景:
i. ⼤数据部⻔流数据处理;
ii. elk;
b. 特性:
它被设计为⼀个分布式系统,易于向外扩展;
它同时为发布和订阅提供⾼吞吐量;
它⽀持多订阅者,当失败时能⾃动平衡消费者;
它将消息持久化到磁盘,因此可⽤于批量消费,例如ETL,以及实时应⽤程序。
rabbitmq、RocketMq、kafka对⽐。
中⼩型公司⾸选RabbitMQ:管理界⾯简单,⾼并发。
⼤型公司可以选择RocketMQ:更⾼并发,可对rocketmq进⾏定制化开发。
⽇志采集功能,⾸选kafka,专为⼤数据准备。
3、SpringCloud:
介绍springcloud核⼼组件及其作⽤,以及springcloud⼯作流程。
M.
springcloud由以下⼏个核⼼组件构成:
Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉
取注册表,从⽽知道其他服务在哪⾥
Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从⼀个服务的多台机器中选择⼀台
Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求
Hystrix:发起请求是通过Hystrix的线程池来⾛的,不同的服务⾛不同的线程池,实现了不同服务调⽤的隔离,避免了服务雪崩
的问题
Zuul:如果前端、移动端要调⽤后端系统,统⼀从Zuul⽹关进⼊,由Zuul⽹关转发请求给对应的服务
介绍springcloud⼼跳机制,以及消费端如何发现服务端(Ribbon)?
特性 ActiveMQ RabbitMQ RocketMQ kafka
开发语⾔ java erlang java scala
单机吞吐量 万级 万级 10万级 10万级
时效性 ms级 us级 ms级 ms级以内
可⽤性 ⾼(主从架构) ⾼(主从架构) ⾮常⾼(分布式架构) ⾮常⾼(分布式架构)
功能特性
成熟的产品,在很多公司
得到应⽤;有较多的⽂
档;各种协议⽀持较好
基于erlang开发,所以并
发能⼒很强,性能极其
好,延时很低;管理界⾯较
丰富
MQ功能⽐较完备,扩展
性佳
只⽀持主要的MQ功能,
像⼀些消息查询,消息回
溯等功能没有提供,毕竟
是为⼤数据准备的,在⼤
数据领域应⽤⼴。
a. 当⼀个服务实例启动,会将它的ip地址等信息注册到eureka;
b. 当a服务调⽤b服务,a服务会通过Ribbon检查本地是否有b服务实例信息的缓存;
c. Ribbon会定期从eureka刷新本地缓存。
eureka的缺点。
a. 某个服务不可⽤时,各个Eureka Client不能及时的知道,需要1~3个⼼跳周期才能感知,但是,由于基于Netflix的服务调⽤端
都会使⽤Hystrix来容错和降级,当服务调⽤不可⽤时Hystrix也能及时感知到,通过熔断机制来降级服务调⽤,因此弥补了基于
客户端服务发现的时效性的缺点。
eureka缓存机制?
a. 第⼀层缓存:readOnlyCacheMap,本质上是ConcurrentHashMap:这是⼀个JVM的CurrentHashMap只读缓存,这个主要是
为了供客户端获取注册信息时使⽤,其缓存更新,依赖于定时器的更新,通过和readWriteCacheMap 的值做对⽐,如果数据不
⼀致,则以readWriteCacheMap 的数据为准。readOnlyCacheMap 缓存更新的定时器时间间隔,默认为30秒
b. 第⼆层缓存:readWriteCacheMap,本质上是Guava缓存:此处存放的是最终的缓存, 当服务下线,过期,注册,状态变
更,都会来清除这个缓存⾥⾯的数据。 然后通过CacheLoader进⾏缓存加载,在进⾏readWriteCacheMap.get(key)的时候,⾸
先看这个缓存⾥⾯有没有该数据,如果没有则通过CacheLoader的load⽅法去加载,加载成功之后将数据放⼊缓存,同时返回数
据。 readWriteCacheMap 缓存过期时间,默认为 180 秒 。
c. 缓存机制:设置了⼀个每30秒执⾏⼀次的定时任务,定时去服务端获取注册信息。获取之后,存⼊本地内存。
rpc和http的区别,使⽤场景?
a. 区别:
传输协议
RPC,可以基于TCP协议,也可以基于HTTP协议
HTTP,基于HTTP协议
传输效率
RPC,使⽤⾃定义的TCP协议,可以让请求报⽂体积更⼩,或者使⽤HTTP2协议,也可以很好的减少报⽂的体积,提
⾼传输效率
HTTP,如果是基于HTTP1.1的协议,请求中会包含很多⽆⽤的内容,如果是基于HTTP2.0,那么简单的封装以下是可
以作为⼀个RPC来使⽤的,这时标准RPC框架更多的是服务治理
性能消耗,主要在于序列化和反序列化的耗时
RPC,可以基于thrift实现⾼效的⼆进制传输
HTTP,⼤部分是通过json来实现的,字节⼤⼩和序列化耗时都⽐thrift要更消耗性能
负载均衡
RPC,基本都⾃带了负载均衡策略
HTTP,需要配置Nginx,HAProxy来实现
服务治理(下游服务新增,重启,下线时如何不影响上游调⽤者)
RPC,能做到⾃动通知,不影响上游
HTTP,需要事先通知,修改Nginx/HAProxy配置
b. 总结:RPC主要⽤于公司内部的服务调⽤,性能消耗低,传输效率⾼,服务治理⽅便。HTTP主要⽤于对外的异构环境,浏览
器接⼝调⽤,APP接⼝调⽤,第三⽅接⼝调⽤等。
分布式事务如何保持⼀致性?
⼆阶段提交:
a. 概念:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中⽌操
作。
b. 作⽤:主要保证了分布式事务的原⼦性;第⼀阶段为准备阶段,第⼆阶段为提交阶段;
c. 缺点:不仅要锁住参与者的所有资源,⽽且要锁住协调者资源,开销⼤。⼀句话总结就是:2PC效率很低,对⾼并发很不
友好。
三阶段提交:
a. 概念:三阶段提交协议在协调者和参与者中都引⼊超时机制,并且把两阶段提交协议的第⼀个阶段拆分成了两步:询问,
然后再锁资源,最后真正提交。这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
b. 缺点:如果进⼊PreCommit后,Coordinator发出的是abort请求,假设只有⼀个Cohort收到并进⾏了abort操作,
⽽其他对于系统状态未知的Cohort会根据3PC选择继续Commit,此时系统状态发⽣不⼀致性。
柔性事务:
a. 概念:所谓柔性事务是相对强制锁表的刚性事务⽽⾔。流程⼊下:服务器A的事务如果执⾏顺利,那么事务A就先⾏提
交,如果事务B也执⾏顺利,则事务B也提交,整个事务就算完成。但是如果事务B执⾏失败,事务B本身回滚,这时事务A已
经被提交,所以需要执⾏⼀个补偿操作,将已经提交的事务A执⾏的操作作反操作,恢复到未执⾏前事务A的状态。
b. 缺点:业务侵⼊性太强,还要补偿操作,缺乏普遍性,没法⼤规模推⼴。
消息最终⼀致性解决⽅案之RabbitMQ实现:
a. 实现:发送⽅确认+消息持久化+消费者确认。
什么情况下⽤到分布式开发?
a. 优点:
i. 模块解耦:把模块拆分,使⽤接⼝通信,降低模块之间的耦合度.
ii. 项⽬拆分,不同团队负责不同的⼦项⽬:把项⽬拆分成若⼲个⼦项⽬,不同的团队负责不同的⼦项⽬.
iii. 提⾼项⽬扩展性:增加功能时只需要再增加⼀个⼦项⽬,调⽤其他系统的接⼝就可以。
iv. 分布式部署:可以灵活的进⾏分布式部署.
v. 提⾼代码的复⽤性:⽐如service层,如果不采⽤分布式rest服务⽅式架构就会在⼿机wap商城,微信商城,pc,android,ios每
个端都要写⼀个service层逻辑,开发量⼤,难以维护⼀起升级,这时候就可以采⽤分布式rest服务⽅式,公⽤⼀个service层。
b. 缺点:
i. 系统之间的交互要使⽤远程通信,接⼝开发增⼤⼯作量;
ii. ⽹络请求有延时;
iii. 事务处理⽐较麻烦,需要使⽤分布式事务。
4、jvm:
jvm内存模型,各个部分的特点?
PC寄存器:
a. 每个线程拥有⼀个pc寄存器;
b. 指向下⼀条指令的地址。
⽅法区:
a. 保存装载的类的元信息:类型的常量池,字段、⽅法信息,⽅法字节码;
jdk6时,String等常量信息置于⽅法区,jdk7移到了堆中;
b. 通常和永久区(Perm)关联在⼀起;
堆:
a. 应⽤系统对象都保存在java堆中;
b. 所有线程共享java堆;
c. 对分代GC来说,堆也是分代的;
栈:
a. 线程私有;
b. 栈由⼀系列帧组成(因此java栈也叫做帧栈);
c. 帧保存⼀个⽅法的局部变量(局部变量表)、操作数栈、常量池指针;
d. 每⼀次⽅法调⽤创建⼀个帧,并压栈。
类加载器,双亲委派模型?
BootStrap ClassLoader 启动ClassLoader
Extension ClassLoader 扩展ClassLoader
App ClassLoader 应⽤ClassLoader/系统ClassLoader
Custom ClassLoader ⾃定义ClassLoader
除了BootStrap ClassLoader,每个ClassLoader都有⼀个Parent作为⽗亲。
⾃底向上检查类是否已经加载;
⾃顶向下尝试加载类。
⾃顶向下尝试加载类。
5、双亲委派机制:当⼀个类收到了类加载请求,他⾸先不会尝试⾃⼰去加载这个类,⽽是把这个请求委派给⽗类去完成,每⼀
个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当⽗类加载器反馈⾃⼰⽆法完成这个请求的时候
(在它的加载路径下没有找到所需加载的Class),⼦类加载器才会尝试⾃⼰去加载。
类加载机制。
概念:虚拟机把描述类的数据⽂件(字节码)加载到内存,并对数据进⾏验证、准备、解析以及类初始化,最终形成可以被虚
拟机直接使⽤的java类型(java.lang.Class对象)。
类⽣命周期:
类加载过程:读取⼆进制字节流到jvm—>验证格式语义等—>为静态变量分配内存空间—>常量池引⽤解析—>执⾏static标
识的代码
a. 加载过程:通过⼀个类的全限定名来获取定义此类的⼆进制字节流,将这个字节流所代表的静态存储结构转化为⽅法区的
运⾏时数据结构。在内存中(⽅法区)⽣成⼀个代表这个类的java.lang.Class对象,作为⽅法区这个类的各种数据的访问⼊⼝;
b. 验证过程:为了确保Class⽂件的字节流中包含的信息符合当前虚拟机的要求,⽂件格式验证、元数据验证、字节码验
证、符号引⽤验证;
c. 准备过程:正式为类属性分配内存并设置类属性初始值的阶段,这些内存都将在⽅法区中进⾏分配;
准备阶段,static对象会被设置默认值,static final对象会被赋上给予的值。
d. 解析阶段:虚拟机将常量池内的符号引⽤替换为直接引⽤的过程。
i. 符号引⽤:字符串,引⽤对象不⼀定被加载;
ii. 直接引⽤:指针或者地址偏移量,引⽤对象⼀定在内存中。
e. 初始化阶段:类初始化阶段是类加载过程的最后⼀步。初始化阶段就是执⾏类构造器()⽅法的过程。
f. 使⽤阶段:
g. 卸载阶段:
java堆的结构,⼀个bean被new出来之后,在内存空间的⾛向?
1、JVM中堆空间可以分成三个⼤区,新⽣代、⽼年代、永久代
2、新⽣代可以划分为三个区,Eden区,两个Survivor区,在HotSpot虚拟机Eden和Survivor的⼤⼩⽐例为8c1
如何让栈溢出,如何让⽅法区溢出?
运⾏时产⽣⼤量的类去填满⽅法区,直到溢出。
写出⼏个jvm优化配置参数。
设定堆内存⼤⼩,这是最基本的。
-Xms:启动JVM时的堆内存空间。
-Xmx:堆内存最⼤限制。
设定新⽣代⼤⼩。
新⽣代不宜太⼩,否则会有⼤量对象涌⼊⽼年代。
-XX:NewRatio:新⽣代和⽼年代的占⽐。
-XX:NewSize:新⽣代空间。
-XX:SurvivorRatio:伊甸园空间和幸存者空间的占⽐。
-XX:MaxTenuringThreshold:对象进⼊⽼年代的年龄阈值。
设定垃圾回收器
年轻代:-XX:+UseParNewGC。
⽼年代:-XX:+UseConcMarkSweepGC。
CMS可以将STW时间降到最低,但是不对内存进⾏压缩,有可能出现“并⾏模式失败”。⽐如⽼年代空间还有300MB空间,
但是⼀些10MB的对象⽆法被顺序的存储。这时候会触发压缩处理,但是CMS GC模式下的压缩处理时间要⽐Parallel GC⻓很多。
G1采⽤”标记-整理“算法,解决了内存碎⽚问题,建⽴了可预测的停顿时间类型,能让使⽤者指定在⼀个⻓度为M毫秒的时
间段内,消耗在垃圾收集上的时间不得超过N毫秒。
有哪⼏种GC机制?
引⽤计数法(没有被java采⽤):
a. 原理:对于⼀个对象A,只要有任何⼀个对象引⽤了A,则A的引⽤计数器就加1,当引⽤失效时,引⽤计数器就减1,只要
对象A的引⽤计数器的值为0,则对象A就会被回收。
b. 问题:
i. 引⽤和去引⽤伴随加法和减法,影响性能;
ii. 很难处理循环引⽤。
标记清除法:
a. 原理:现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。⼀种可⾏的实现
是,在标记节点,⾸先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引⽤的垃圾对象。
然后在清除阶段,清除所有未被标记的对象。
b. 问题:
i. 标记和清除两个过程效率不⾼,产⽣内存碎⽚导致需要分配较⼤对象时⽆法找到⾜够的连续内存⽽需要触发⼀次GC操
作。
标记压缩法:
a. 原理:适合⽤于存活对象较多的场合,如⽼年代。它在标记-清除算法的基础上做了⼀些优化。标记阶段⼀样,但之后,
将所有存活对象压缩到内存的⼀端。之后,清除边界外所有的空间。
b. 优点:
i. 解决了标记- 清除算法导致的内存碎⽚问题和在存活率较⾼时复制算法效率低的问题。
复制算法:
a. 原理:将原有的内存空间分为两块,每次只使⽤其中⼀块,在垃圾回收时,将正在使⽤的内存中的存活对象复制到未使⽤
的内存块中,之后清除正在使⽤的内存块中的所有对象,交换两个内存的⻆⾊,完成垃圾回收。
b. 问题:
i. 不适⽤于存活对象⽐较多的场合,如⽼年代。
分代回收法:
a. 原理:根据对象存活周期的不同将内存划分为⼏块,⼀般是新⽣代和⽼年代,新⽣代基本采⽤复制算法,⽼年代采⽤标记
整理算法。
5、spring:
springboot启动过程。
通过 SpringFactoriesLoader加载 META-INF/spring.factories⽂件,获取并创建
SpringApplicationRunListener对象
然后由 SpringApplicationRunListener来发出 starting 消息
创建参数,并配置当前 SpringBoot 应⽤将要使⽤的 Environment
完成之后,依然由 SpringApplicationRunListener来发出 environmentPrepared 消息
创建 ApplicationContext
初始化 ApplicationContext,并设置 Environment,加载相关配置等
由 SpringApplicationRunListener来发出 contextPrepared消息,告知SpringBoot 应⽤使⽤的
ApplicationContext已准备OK
将各种 beans 装载⼊ ApplicationContext,继续由 SpringApplicationRunListener来发出 contextLoaded 消息,告
知 SpringBoot 应⽤使⽤的 ApplicationContext已装填OK
refresh ApplicationContext,完成IoC容器可⽤的最后⼀步
由 SpringApplicationRunListener来发出 started 消息
完成最终的程序的启动
由 SpringApplicationRunListener来发出 running 消息,告知程序已运⾏起来了
说说⼏个常⽤的注解?
@RestController :@ResponseBody和@Controller的合集。
@EnableAutoConfiguration :尝试根据你添加的jar依赖⾃动配置你的Spring应⽤。
@ComponentScan:表示将该类⾃动发现(扫描)并注册为Bean,可以⾃动收集所有的Spring组件,包括@Configuration类。
@ImportResource :⽤来加载xml配置⽂件。
@Configuration :相当于传统的xml配置⽂件,如果有些第三⽅库需要⽤到xml⽂件,建议仍然通过@Configuration类作为项⽬
的配置主类——可以使⽤@ImportResource注解加载xml配置⽂件。
@SpringBootApplication:相当于@EnableAutoConfiguration、@ComponentScan和@Configuration的合集。
spring事件的实现原理,写出常⽤的⼏个事件。
事件实现原理:理解这篇即可:https://www.jianshu.com/p/dcbe8f0afbdb
常⽤事件:
(1)ContextRefreshedEvent:当ApplicationContext初始化或者刷新时触发该事件
(2)ContextClosedEvent:ApplicationContext被关闭时触发该事件.容器被关闭时,其管理的所有单例Bean都被销毁
(3)RequestHandleEvent:在Web应⽤中,当⼀个Http请求结束时触发该事件
(4)ContextStartedEvent:当容器调⽤start()⽅法时触发
(5)ContextStopEvent:当容器调⽤stop()⽅法时触发
spring的bean的⽣命周期?
BeanFactory和FactoryBean的区别。
详⻅“6.1、框架—Spring”
spring中使⽤到了FactoryBean的哪个⽅法?
详⻅“6.1、框架—Spring”
6、数据结构:
说说HashMap、ConcurrentHashMap数据结构,1.7与1.8的区别?
1、数据结构:
以下是ConcurrentHashMap的类图:
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是⼀种可重⼊锁ReentrantLock,在
ConcurrentHashMap中扮演锁的⻆⾊,HashEntry则⽤于存储键值对数据。⼀个ConcurrentHashMap中包含⼀个Segment数组,
Segment的结构和HashMap类似,是⼀种数组和链表结构。⼀个Segment⾥⾯包含⼀个HashEntry数组,每个HashEntry是⼀个链表的元
素。每个Segment拥有⼀个锁,当对HashEntry数组的数据进⾏修改时,必须先获得对应的Segment锁,如图所示:
2、concurrenthashmap 1.7和1.8区分:
去除 Segment + HashEntry + Unsafe的实现,
改为 Synchronized + CAS + Node + Unsafe的实现
其实 Node 和 HashEntry 的内容⼀样,但是HashEntry是⼀个内部类。
⽤ Synchronized + CAS 代替 Segment ,这样锁的粒度更⼩了,并且不是每次都要加锁了,CAS尝试失败了在加锁。
put()⽅法中 初始化数组⼤⼩时,1.8不⽤加锁,因为⽤了个 sizeCtl变量,将这个变量置为-1,就表明table正在初始化。
谈谈数据结构,⽐如TreeMap。
详⻅:https://my.oschina.net/u/566591/blog/1548176
B-tree、B+tree?
详⻅“⾯试题库/java基础”
红⿊树左旋与右旋的区别?
当在对红⿊树进⾏插⼊和删除等操作时,对树做了修改可能会破坏红⿊树的性质。为了继续保持红⿊树的性质,可以通过对结点
进⾏重新着⾊,以及对树进⾏相关的旋转操作,即通过修改树中某些结点的颜⾊及指针结构,来达到对红⿊树进⾏插⼊或删除结点等操作
后继续保持它的性质或平衡的⽬的。
树的旋转分为左旋和右旋,下⾯借助图来介绍⼀下左旋和右旋这两种操作。
1.左旋
如上图所示,当在某个结点pivot上,做左旋操作时,我们假设它的右孩⼦y不是NIL[T],pivot可以为任何不是NIL[T]的左⼦结
点。左旋以pivot到Y之间的链为“⽀轴”进⾏,它使Y成为该⼦树的新根,⽽Y的左孩⼦b则成为pivot的右孩⼦。
1 LeftRoate(T, x)
2 y ← x.right //定义y:y是x的右孩⼦
3 x.right ← y.left //y的左孩⼦成为x的右孩⼦
4 if y.left ≠ T.nil
5 y.left.p ← x
6 y.p ← x.p //x的⽗结点成为y的⽗结点
7 if x.p = T.nil
8 then T.root ← y
9 else if x = x.p.left
10 then x.p.left ← y
11 else x.p.right ← y
12 y.left ← x //x作为y的左孩⼦
13 x.p ← y
2.右旋
右旋与左旋差不多,再此不做详细介绍。
树在经过左旋右旋之后,树的搜索性质保持不变,但树的红⿊性质则被破坏了,所以,红⿊树插⼊和删除数据后,需要利⽤旋转
与颜⾊重涂来重新恢复树的红⿊性质。
7、并发:
concurrent包下有哪些常⽤类?
1.CountDownLatch:api解释:⼀个同步辅助类,在完成⼀组正在其他线程中执⾏的操作之前,它允许⼀个或多个线程⼀直等待。
个⼈理解是CountDownLatch让可以让⼀组线程同时执⾏,然后在这组线程全部执⾏完前,可以让另⼀个线程等待。
2.ReentrantLock:可重⼊互斥锁
3.Condition:此类是同步的条件对象,每个Condition实例绑定到⼀个ReetrantLock中,以便争⽤同⼀个锁的多线程之间可以通
过Condition的状态来获取通知。
注意:使⽤Condition前,⾸先要获得ReentantLock,当条件不满⾜线程1等待时,ReentrantLock会被释放,以能让其他线程争
⽤,其他线程获得reentrantLock,然后满⾜条件,唤醒线程1继续执⾏。
三种分布式锁。
1、Zookeeper:基于zookeeper瞬时有序节点实现的分布式锁,其主要逻辑如下(该图来⾃于IBM⽹站)。⼤致思想即为:每个客户端对
某个功能加锁时,在zookeeper上的与该功能对应的指定节点的⽬录下,⽣成⼀个唯⼀的瞬时有序节点。判断是否获取锁的⽅式很简单,只需要判断
有序节点中序号最⼩的⼀个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁⽆法释放,⽽产⽣的死锁问题。
2、优点
锁安全性⾼,zk可持久化,且能实时监听获取锁的客户端状态。⼀旦客户端宕机,则瞬时节点随之消失,zk因⽽能第⼀时间释放
锁。这也省去了⽤分布式缓存实现锁的过程中需要加⼊超时时间判断的这⼀逻辑。
3、缺点
性能开销⽐较⾼。因为其需要动态产⽣、销毁瞬时节点来实现锁功能。所以不太适合直接提供给⾼并发的场景使⽤。
4、实现
可以直接采⽤zookeeper第三⽅库curator即可⽅便地实现分布式锁。
5、适⽤场景
对可靠性要求⾮常⾼,且并发程度不⾼的场景下使⽤。如核⼼数据的定时全量/增量同步等。
2、memcached:memcached带有add函数,利⽤add函数的特性即可实现分布式锁。add和set的区别在于:如果多线程并发set,则每
个set都会成功,但最后存储的值以最后的set的线程为准。⽽add的话则相反,add会添加第⼀个到达的值,并返回true,后续的添加则都会返回
false。利⽤该点即可很轻松地实现分布式锁。
2、优点
并发⾼效
3、缺点
memcached采⽤列⼊LRU置换策略,所以如果内存不够,可能导致缓存中的锁信息丢失。
memcached⽆法持久化,⼀旦重启,将导致信息丢失。
4、使⽤场景
⾼并发场景。需要 1)加上超时时间避免死锁; 2)提供⾜够⽀撑锁服务的内存空间; 3)稳定的集群化管理。
3、redis:redis分布式锁即可以结合zk分布式锁锁⾼度安全和memcached并发场景下效率很好的优点,其实现⽅式和memcached类
似,采⽤setnx即可实现。需要注意的是,这⾥的redis也需要设置超时时间。以避免死锁。可以利⽤jedis客户端实现。
1 ICacheKey cacheKey = new ConcurrentCacheKey(key, type);
2 return RedisDao.setnx(cacheKey, “1”);
8、线程池:
34. 你知道哪些常⽤的阻塞队列?
JDK7 提供了 7 个阻塞队列。分别是
ArrayBlockingQueue :⼀个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :⼀个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :⼀个⽀持优先级排序的⽆界阻塞队列。
DelayQueue:⼀个使⽤优先级队列实现的⽆界阻塞队列。
SynchronousQueue:⼀个不存储元素的阻塞队列。
LinkedTransferQueue:⼀个由链表结构组成的⽆界阻塞队列。
LinkedBlockingDeque:⼀个由链表结构组成的双向阻塞队列。
35. newFixedThreadPool使⽤到了哪个阻塞队列?
看Executors源码可知:newFixedThreadPool使⽤了LinkedBlockingQueue。
1 public static ExecutorService newFixedThreadPool(int var0) {
2 return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
3 }
9、数据库:
36. 说说mysql存储引擎innodb和myisam的区别和使⽤场景。
a. InnoDB:

⽀持事务处理
⽀持外键
⽀持⾏锁
不⽀持FULLTEXT类型的索引(在Mysql5.6已引⼊)
不保存表的具体⾏数,扫描表来计算有多少⾏
对于AUTO_INCREMENT类型的字段,必须包含只有该字段的索引
DELETE 表时,是⼀⾏⼀⾏的删除
InnoDB 把数据和索引存放在表空间⾥⾯
跨平台可直接拷⻉使⽤
表格很难被压缩
b. MyISAM:
不⽀持事务,回滚将造成不完全回滚,不具有原⼦性
不⽀持外键
⽀持全⽂搜索
保存表的具体⾏数,不带where时,直接返回保存的⾏数
DELETE 表时,先drop表,然后重建表
MyISAM 表被存放在三个⽂件 。frm ⽂件存放表格定义。 数据⽂件是MYD (MYData) 。 索引⽂件是MYI (MYIndex)引伸
跨平台很难直接拷⻉
AUTO_INCREMENT类型字段可以和其他字段⼀起建⽴联合索引
表格可以被压缩
c. 选择:因为MyISAM相对简单所以在效率上要优于InnoDB.如果系统读多,写少。对原⼦性要求低。那么MyISAM最好的选择。
且MyISAM恢复速度快。可直接⽤备份覆盖恢复。如果系统读少,写多的时候,尤其是并发写⼊⾼的时候。InnoDB就是⾸选了。
两种类型都有⾃⼰优缺点,选择那个完全要看⾃⼰的实际类弄。
说说mysql查询优化。
1、选择最合适的字段属性:类型、⻓度、是否允许NULL等;尽量把字段设为not null,⼀⾯查询时对⽐是否为null;
2.要尽量避免全表扫描,⾸先应考虑在 where 及 order by 涉及的列上建⽴索引。
3.应尽量避免在 where ⼦句中对字段进⾏ null 值判断、使⽤!= 或 <> 操作符,否则将导致引擎放弃使⽤索引⽽进⾏全表扫描
4.应尽量避免在 where ⼦句中使⽤ or 来连接条件,如果⼀个字段有索引,⼀个字段没有索引,将导致引擎放弃使⽤索引⽽进⾏全
表扫描
5.in 和 not in 也要慎⽤,否则会导致全表扫描
6.模糊查询也将导致全表扫描,若要提⾼效率,可以考虑字段建⽴前置索引或⽤全⽂检索;
7.如果在 where ⼦句中使⽤参数,也会导致全表扫描。因为SQL只有在运⾏时才会解析局部变量,但优化程序不能将访问计划的选择
推迟到运⾏时;它必须在编译时进⾏选择。然 ⽽,如果在编译时建⽴访问计划,变量的值还是未知的,因⽽⽆法作为索引选择的输⼊项。
9.应尽量避免在where⼦句中对字段进⾏函数操作,这将导致引擎放弃使⽤索引⽽进⾏全表扫描。
10.不要在 where ⼦句中的“=”左边进⾏函数、算术运算或其他表达式运算,否则系统将可能⽆法正确使⽤索引。
11.在使⽤索引字段作为条件时,如果该索引是复合索引,那么必须使⽤到该索引中的第⼀个字段作为条件时才能保证系统使⽤该索
引,否则该索引将不会被使⽤,并且应尽可能的让字段顺序与索引顺序相⼀致。
12.不要写⼀些没有意义的查询,如需要⽣成⼀个空表结构:
13.Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调⽤会引起明显的性能消耗,同时带来⼤量⽇志。
14.对于多张⼤数据量(这⾥⼏百条就算⼤了)的表JOIN,要先分⻚再JOIN,否则逻辑读会很⾼,性能很差。
15.select count() from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是⼀定要杜绝的。
16.索引并不是越多越好,索引固然可以提⾼相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert
或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况⽽定。⼀个表的索引数最好不要超过6个,若太多则应考虑⼀些不常使
⽤到的列上建的索引是否有 必要。
17.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,⼀旦该列值改
变将导致整个表记录的顺序的调整,会耗费相当⼤的资源。若应⽤系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为
clustered 索引。
18.尽量使⽤数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为
引擎在处理查询和连 接时会逐个⽐较字符串中每⼀个字符,⽽对于数字型⽽⾔只需要⽐较⼀次就够了。
19.尽可能的使⽤ varchar/nvarchar 代替 char/nchar ,因为⾸先变⻓字段存储空间⼩,可以节省存储空间,其次对于查询来
说,在⼀个相对较⼩的字段内搜索效率显然要⾼些。
20.任何地⽅都不要使⽤ select * from t ,⽤具体的字段列表代替“”,不要返回⽤不到的任何字段。
21.尽量使⽤表变量来代替临时表。如果表变量包含⼤量数据,请注意索引⾮常有限(只有主键索引)。
避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使⽤,适当地使⽤它们可以使某些例程更有效,例如,
当需要重复引⽤⼤型表或常⽤表中的某个数据集时。但是,对于⼀次性事件, 最好使⽤导出表。
23.在新建临时表时,如果⼀次性插⼊数据量很⼤,那么可以使⽤ select into 代替 create table,避免造成⼤量 log ,以提
⾼速度;如果数据量不⼤,为了缓和系统表的资源,应先create table,然后insert。
24.如果使⽤到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可
以避免系统表的较⻓时间锁定。
25.尽量避免使⽤游标,因为游标的效率较差,如果游标操作的数据超过1万⾏,那么就应该考虑改写。
26.使⽤基于游标的⽅法或临时表⽅法之前,应先寻找基于集的解决⽅案来解决问题,基于集的⽅法通常更有效。
27.与临时表⼀样,游标并不是不可使⽤。对⼩型数据集使⽤ FAST_FORWARD 游标通常要优于其他逐⾏处理⽅法,尤其是在必须引⽤
⼏个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要⽐使⽤游标执⾏的速度快。如果开发时 间允许,基于游标的⽅法和基于集的⽅法
都可以尝试⼀下,看哪⼀种⽅法的效果更好。
28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。⽆需在执⾏存储过程和触
发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29.尽量避免⼤事务操作,提⾼系统并发能⼒。
30.尽量避免向客户端返回⼤数据量,若数据量过⼤,应该考虑相应需求是否合理。
说说脏读、不可重复读、幻读;
ISOLATIONREADUNCOMMITTED 这是事务最低的隔离级别,它允许另外⼀个事务可以看到这个事务未提交的数据。这种隔
离级别会产⽣脏读,不可重复读和幻读。
ISOLATIONREADCOMMITTED 保证⼀个事务修改的数据提交后才能被另外⼀个事务读取。另外⼀个事务不能读取该事务未提
交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻读。
ISOLATIONREPEATABLEREAD 这种事务隔离级别可以防⽌脏读,不可重复读。但是可能出现幻读。它除了保证⼀个事务不能
读取另⼀个事务未提交的数据外,还保证了避免不可重复读。
ISOLATION_SERIALIZABLE 这是花费最⾼代价但是最可靠的事务隔离级别。事务被处理为顺序执⾏。除了防⽌脏读,不可重
复读外,还避免了幻读。
说说事务的四种特性(ACID)。
1)原⼦性(Atomic):事务中各项操作,要么全做要么全不做,任何⼀项操作的失败都会导致整个事务的失败;
2)⼀致性(Consistent):事务结束后系统状态是⼀致的;
3)隔离性(Isolated):并发执⾏的事务彼此⽆法看到对⽅的中间状态;
4)持久性(Durable):事务完成后所做的改动都会被持久化,即使发⽣灾难性的失败。通过⽇志和同步备份可以在故障发⽣后重建数
据。
codis与redis集群的区别。
redis cluster基于smart client和⽆中⼼的设计,client必须按key的哈希将请求直接发送到对应的节点。这意味着:使⽤官⽅
cluster必须要等对应语⾔的redis driver对cluster⽀持的开发和不断成熟;client不能直接像单机⼀样使⽤pipeline来提⾼效率,想同时执
⾏多个请求来提速必须在client端⾃⾏实现异步逻辑。 ⽽codis因其有中⼼节点、基于proxy的设计,对client来说可以像对单机redis⼀样
去操作proxy(除了⼀些命令不⽀持),还可以继续使⽤pipeline并且如果后台redis有多个的话速度会显著快于单redis的pipeline。同时
codis使⽤zookeeper来作为辅助,这意味着单纯对于redis集群来说需要额外的机器搭zk,不过对于很多已经在其他服务上⽤了zk的公司
来说这不是问题)
10、设计:
要缓存⽹站登录的⽤户信息,你有⼏种⽅式?
cookie+session、cookie+redis。
让你设计⼀套分布式缓存,如何设计可以同时更新所有服务器的缓存?
说说你在⼯作中遇到的困难或者挑战。

posted @ 2021-04-23 10:55  爱笑的小杰  阅读(237)  评论(0编辑  收藏  举报