20240805面经背诵

  1. ArrayList和LinkedList区别?

    • 数据结构上的区别:
      • ArrayList底层是通过数组实现的
      • LinkedList底层是通过链表实现的
    • 时间复杂度区别:
      • ArrayList查询一个数据的平均时间复杂度是O(1)
      • ArrayList插入一个数据的平均时间复杂度是O(n),因为有可能插入在中间位置,需要后移后面的元素
      • ArrayList删除一个数据的平均时间复杂度是O(n),原理同上
      • ArrayList修改一个数据的平均时间复杂度是O(1)
      • LinkedList查询,插入,删除,修改的平均时间复杂度都是O(n)
  2. Java中有哪些垃圾回收器,有什么区别?

    • serial GC:串行GC,GC过程中需要暂停应用线程,单线程执行
    • parallel GC:并行GC,GC过程中也要暂停应用线程,多线程执行
    • 以上说的两种在清理年轻代时采用标记复制算法,清理老年代时采用标记整理算法
    • CMS GC:流程为:
      • 初始标记,单线程,stw
      • 并发标记,单线程,不stw
      • 重新标记,多线程,stw
      • 并发清理:单线程,不stw
      • 采用标记-清除算法,会产生大量内存碎片
    • G1收集器:
      • 初始标记,单线程,stw
      • 并发标记,单线程,不stw
      • 最终标记,多线程,stw
      • 筛选回收,多线程,stw
      • 将内存分成等大小的内存空间,统一采用标记-复制算法
  3. AOP底层原理,适用场景?

    • 底层原理:采用动态代理实现,对目标切点方法前,后,返回值后,抛出异常后进行增强。在类实现接口时,将采用java的动态代理来实现,在类未实现接口时,采用cglib的动态代理来实现。
    • 适用场景:
      • 接口入参打印日志,抛出异常打印日志
      • 入参解析
      • 手动开启事务可以采用AOP,但建议采用@Transactional注解
  4. 产生死锁的必要条件?

    • 互斥条件:在同一时刻,资源不能被两个线程或进程同时持有
    • 不剥夺条件:在线程或进程未使用完资源时,资源不能被剥夺
    • 请求与保持条件:线程持有一定量资源,并申请其他资源
    • 循环等待条件:基于上个条件,请求的资源已经被其他资源持有,首尾相连,形成一个循环等待链
  5. BIO,NIO,AIO的概念和区别?

    • 一般IO要经历,发起select/epoll系统调用,数据准备,数据传输过程
    • BIO:阻塞式IO,在发起系统调用后,就阻塞当前线程,直到数据传输完成
    • NIO:非阻塞式IO,发起系统调用后,不阻塞,可以去执行其他任务,直到数据准备就绪,线程才阻塞,然后进行数据传输
    • AIO:异步IO,发起系统调用后不管IO过程,而转区执行其他逻辑,直到数据传输完成,整个过程完全不阻塞
  6. Redis常见数据结构和区别?

    • string
      • 底层结构是SDS,简单动态字符串
      • 可以用来存储序列化之后的java对象
    • List
      • 底层数据结构是ZipList和LinkedList
      • 可以用作简单的消息队列
    • Hash
      • 底层数据结构是Dict和ZipList
      • 可以用作Redisson的可重入分布式锁
    • Set
      • 底层数据结构是Dict和IntSet
      • 可以用作统计网页UV或者说统计点赞数,共同关注,通过交集实现
    • Zset
      • 底层数据结构是ZipList和SkipList
      • 可以用作排行榜
    • Bitmap
      • 位图,可以用于统计网站UV
    • hyperLoglog
      • 也可用于统计网站UV,适用于大数据量情况
    • GeoSpatial
      • 经纬度
  7. 缓存穿透,缓存击穿,缓存雪崩分别的概念和解决方法?

    • 缓存穿透:某一个数据在数据库中不存在,但是出现了大量的针对这个数据的访问,导致大量的请求到达数据库
    • 解决:缓存null值或者布隆过滤器
    • 缓存击穿:某一个数据在数据库中存在,但是在缓存中不存在,导致大量的请求到达数据库
    • 解决:使用幂等性方法确保一段时间内只有一个线程能访问这个数据,让其去数据库中访问数据,并将其写入缓存。或者为热点数据设置永不失效或者较长的ttl,确保热点数据在大量请求到达时能够在缓存中存在。
    • 缓存雪崩:由于大量key在同一时间同时失效,或者redis崩了导致大量请求到达数据库
    • 解决方案:为key设置的ttl基础上加上一个随机值。架设redis集群。设置多级缓存。
    • 以上三种情况都可以通过降级,限流来解决。
  8. Thread.run()和Thread.start()有什么区别?

    • Thread.start()才是真正的启动线程,让线程进入Runnable状态去运行
    • 而Thread.run()就是普通的调用run方法,并没有真正开启线程
  9. HashMap何时扩容?

    • HashMap中有一个loadFactor,负载因子,还有一个capacity是数组的容量,还有个字段为threshold阈值,threshold=capacity*loadFactor,而loadFactor默认为0.75。当HashMap中元素数量size>=threshold时触发扩容。
  10. 扩容原理?

    • 先计算新容量newCapacity,设置新容量newCapacity=2*oldCapacity(旧容量),再判断newCapacity是否>=HashMap默认的最大容量,如果满足,则newCapacity=HashMap默认的最大容量。确定心容量后,创建一个新的容量为newCapacity的数组,然后将原数组中的节点依次复制过去
    • 遍历原数组所有的位置,当遍历到i位置时,判断当前位置的节点的next是否为null,如果为null则直接将这个节点复制到新数组i位置。如果不为null,判断这个节点是否是树节点,如果是树节点,则将左子树拆分到新数组i位置,根节点及右子树拆分到新数组i+oldCapacity位置,如果拆分后的红黑树长度小于等于6,那么退化为链表。如果不为树节点,则为链表,遍历这个链表,完成拆分,对每个节点的key重新计算hash值,hash值小于oldCapacity的节点插入新数组i位置,>=oldCapacity的节点插入新数组i+oldCapacity位置
  11. HashMap扩容是否线程安全?

    • 非线程安全
    • 在jdk1.7的时候,因为采用的是头插法,会造成循环链表。例如在i位置有1,2,3这3个节点,有两个线程都来完成链表节点的复制,线程1将指针指向1,但没来得及进行复制,线程切换到线程2,此时线程2将1,2分别复制过去了,此时新数组的链表为2,1,头结点为2,而此时切换到线程1,线程1的指针还在1节点处,其复制到新数组需要头插法,因此节点1的next节点指向节点2,而节点2此时的next又执行节点1,形成循环链表,死循环
    • 在jdk1.8之后,采用尾插法,解决了这个问题,但是仍然存在数据覆盖的问题,例如有两个线程对数组某个位置进行插入数据,该位置为空,线程1判断该位置为空,但未插入数据,切换到线程2,线程2判断位置为空,插入数据,并切换回线程1,线程1之前判断的是该位置为空,因此直接插入了数据,导致了数据的覆盖。
  12. 线程安全的map?

    • ConcurrentHashMap
      • jdk1.7对一段数组位置设置为一个segment,针对每个segment上锁,使用的是reentrantlock,底层是数组加链表的数据结构,并发粒度要=segment数量
      • jdk1.8底层数据结构变成了数组+链表/红黑树,上锁的位置变成了每个链表或者红黑树的头结点,采用CAS+synchronized的方式来保证线程安全,并发粒度等于数组的容量
      • 为什么要这样改进呢,因为基于segment的并发粒度并不是很够,基于数组容量的上锁可以进一步提高并发粒度
    • HashTable
      对整个数组进行上锁,并发粒度为1,因此实际项目中基本不会用到。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值