HashMap的put过程
1 因为HashMap在初始化的时候, 没有初始化table, 所以在第一次插入时需要初始化table;
.2 判断table[(n - 1) & hash]是否为空, 如果为空则证明是首节点, 直接插入即可;
.3 若不为空, 则需判断挂载的是链表还是红黑树, 若是红黑树, 则走红黑树的插入;
.5 遍历链表, 如果key相同且hash相同, 则直接退出循环;
.6 如果已经到了尾节点, 则直接插入, 再判断链表的长度是否大于8; 若大于8需要转成红黑树;
.7 退出循环后再判断相同key能否覆盖, 能覆盖时直接覆盖, 并返回结果;
.8 插入完成后再判断HashMap.size 是否大于 threshold; 为真时需要扩容;
.9 HashMap::put正常插入的返回结果都为null;
1.8以后引入红黑树为了解决什么问题
红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
ArrayList和LinkedList区别
-
Arraylist
: 底层是Object[]
数组 -
Vector
:底层是Object[]
数组 -
LinkedList
:底层是 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
只有vector是线程安全的,其他两个都不是线程安全的.
arrayList因为是数组的特性,所以删除和增加的效率很慢,复杂度为O(n), 而查找的速度很快
而LinkedList的增删复杂度为O(1),而查找的速度却没arrayList快.
用for循环打印LinkedList有什么问题
随着数据量的增大,用for循环打印LinkedList 的时间会几何倍的增长,这是因为底层数据结构(双向链表)决定的, 而用foreach或者iterator迭代器则不会出现这个情况,因为它们是直接找到地址值进行查找数据.
而ArrayList则没有这个情况.
ConcurrentHashMap实现
ConcurrentHashMap JDK1.8之后进行改动, 直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap.
AQS实现
aqs全称为AbstractQueuedSynchronizer,它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLock、CountDownLatch等。
AQS的实现依赖内部的同步队列,也就是FIFO的双向队列,如果当前线程竞争锁失败,那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
AQS的实现主要是基于非公平锁的独占锁实现。在获得同步锁时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列(或停止自旋)的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒头节点的后继节点。
怎么实现两个线程交替打印
使用notify和wait来进行交互, 或者使用sync/lock来完成
CMS和G1收集器的区别
CMS收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,CMS收集器是老年代的收集器, 它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。基于 标记-清除”算法实现. 实现步骤: 初始标记->并发标记->重新标记->并发清除
缺点: 对 CPU 资源敏感; 无法处理浮动垃圾; 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
G1: G1收集器收集范围是老年代和新生代。是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征. 实现步骤: 初始标记->并发标记->最终标记->筛选回收
有A,B,C三个子类继承自D,每个子类对应一个tag,如1,2,3,现在输入tag返回一个对象,可以基于Spring去实现这个工厂模式吗
通过Spring上下文ApplicationContext可以根据父类获取到所有子类的
Redis使用场景,集群模式,负载均衡特点,缓存穿透及解决方法
这又是一个大问题,内容较多
使用场景
热点数据的缓存
由于redis访问速度块、支持的数据类型比较丰富,所以redis很适合用来存储热点数据,另外结合expire,我们可以设置过期时间然后再进行缓存更新操作
分布式锁
这个主要利用redis的setnx命令进行,setnx:"set if not exists"就是如果不存在则成功设置缓存同时返回1,否则返回0
限时业务的运用
redis中可以使用expire命令设置一个键的生存时间,到时间后redis会删除它。利用这一特性可以运用在限时的优惠活动信息、手机验证码等业务场景。
排行榜相关问题
关系型数据库在排行榜方面查询速度普遍偏慢,所以可以借助redis的SortedSet进行热点数据的排序。
Redis集群模式
为了避免单台服务器故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此, Redis 提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。
在复制的概念中,数据库分为两类,一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
哨兵模式
第一种主从同步/复制的模式,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
缓存穿透及解决方法
缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
解决办法最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
1)缓存无效 key
如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下: SET key value EX 10086
2)布隆过滤器
把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,
负载均衡的策略一般是根据Hash来做的
Redis热点场景有了解过吗
保证redis中一直是热点数据的方法, redis淘汰机制
Redis 提供 6 种数据淘汰策略:
- volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
4.0 版本后增加以下两种:
- volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
- allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
常用的过期数据的删除策略就两个
- 惰性删除 :只会在取出key的时候才对数据进行过期检查。这样对CPU最友好,但是可能会造成太多过期 key 没有被删除。
- 定期删除 : 每隔一段时间抽取一批 key 执行删除过期key操作。并且,Redis 底层会并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
定期删除对内存更加友好,惰性删除对CPU更加友好。两者各有千秋,所以Redis 采用的是 定期删除+惰性/懒汉式删除 。
A并发同时推了三条数据,三条消息同时更新一个数据,比如第一次改为3,第二次改为6,第三次改为8,但在分布式场景下这三条消息可能是乱序的,如何保证我们数据的正确更改呢?不一定要完整的消息消费顺序,只要最终的更改是正确的即可。或者是怎么做到第三条消息先到,但是第一条消息到了后舍弃掉
用分布式锁,1在消费的时候版本号是比3小的,这种时候就可以舍弃掉1。PS:其实面试管想问的多服务并发修改共享资源的解决方法,而不是消息顺序问题
分布式框架有了解过吗
一、RPC
RPC(Remote Process Call),即远程服务调用,被广泛地应用在很多企业应用中,是早期主要的服务治理方案,其流程较为简单,客户端consumer携带参数发送RPC请求到服务提供方provider,provider根据参数路由到具体函数,方法,并将执行获得的结果返回,至此一次RPC调用完成。
二、SOA
由于简单的RPC调用已经不能随着时代发展满足需求,因此复杂的业务逻辑对于分布式应用架构体系的需求愈发强烈,
SOA粗暴理解:把系统按照实际业务,拆分成刚刚好大小的、合适的、独立部署的模块,每个模块之间相互独立。
介绍一下注册中心,服务提供方,服务消费方三者关联,如果注册中心挂掉,整个服务提供方和消费方还能正常连接吗
无论是那种SOA的架构设计,都离不开几个模块的功能,即Provider,Consumer,Registry,Gateway,负载均衡,服务分流,监控等,通过上述所讲,应该对这些功能模块有了初步的认识,下面就这些名词再作下介绍
(1)Provider:服务提供者,无论是业务服务,还是一个系统中公用的SAAS,都属于Provider
(2)Consumer:即发起调用的客户端
(3)Registry:服务注册中心,是分布式服务系统中的一个重要组成模块,管理Provider的Manager,在实际的运行环境中,服务注册中心Registry被动通知或Consumer主动询问,在Provider有节点宕机或新增节点时,客户端也可实时感知到,从而避免了某个Provider被无限调用或是无限闲置
(4)Gateway:网关也是分布式服务框架中不可或缺的部分,每种系统与框架都有自己的一套协议,当异构系统互相调用时,网关的作用即显现出来,Gateway接受各种外部HTTP请求,完成相应的权限校验,报文适配,路由转发到对应的Provider,再将Provider返回的结果传递给异构系统的Consumer,完成异构系统的互相调用
(5)负载均衡,服务分流:Consumer从Registry获得具体的Provider列表后,如何选取合适的Provider,取决与一定的负载均衡算法,常见的算法有轮询法,随机法,源地址哈希,加权轮询,加权随机等
(6)监控:接收来自Consumer和Provider异步上报的性能监控数据,对有风险的节点发出告警