¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥

Java面试题小小汇总

文章目录

一、Java常见面试题

1. 封装

  • 比如说定义一个实体类, 属性都是私有的, 对外提供公开的构造方法, 公开的getset方法, 外部调用无需知道内部如何实现

2. 继承

  • 子类公用父类属性, 对父类做扩展或者改变

3.多肽

  • (必要条件就是继承或者实现接口, 方法重写 , 父类引用指向子类对象)比如两个类继承一个父类, 实现同一个方法, 方法名相同, 实现内容不同, 即基于对象所有类不同, 外部调用同一个方法, 执行逻辑不同

4. final

  • 修饰类: 不可被继承
  • 修饰方法: 不可被重写
  • 修饰变量: 表示一旦赋值就不可改变他的值
    • 修饰类变量: 只能在静态初始化中指定初始值或者声明该类变量时指定初始值
    • 修饰成员变量: 可以在非静态初始化块声明该变量或者构造器中执行初始值

5. 重载和重写的区别

  1. 重载: 在同一个类中, 方法名名相同, 参数类型, 个数, 或者顺序不同, 返回值或者修饰符不同
  2. 重写: 在父子类中, 子类重写父类非私有方法 , 返回值返回小于等于父类, 抛出异常范围小于等于父类

6. 接口和抽象类区别

  1. 抽象类可以有普通带方法体的方法, 接口只能有抽象方法
  2. 抽象类中的成员变量可以任意声明, 接口中的变量只能是public static final类型
  3. 抽象类只能继承一个, 而接口可以实现多个
  4. 接口可以对实现他的类做行为有无的约束, 不限制实现方式
  5. 抽象类主要做代码复用, 很多子类有相同特性

7. List和Set区别

  1. List可以用迭代器,for循环遍历, set只能迭代器遍历

  2. List有序可重复, Set无序不可重复

  3. HashCode和equals

    1. HashSet解决重复, 先判断hashCode是否相同, hashCode相同再用equals比较, hashCode就是对象在堆上索引
    2. hashCode相同对象不一定相同, 两个对象equals相等, 那么hashCode一定相等
    3. 两个对象相等那么equals也一定相等

8. ArrayList和LinkedList

  1. ArrayList: 底层数据结构是初始长度为10的动态数据, 扩容机制是原数组长度的1.5倍, 数组在内存中是一串连续的内存, 适合下标访问, 时间复杂度是O(1), 指定初始容量, 插入数据用尾插法, 可以大大提升性能, 甚至比LinkedList还要好, LinkedList要创建大量node
  2. LinkedList: 数据结构是双向链表, 插入删除效率较高, 查询相比较慢, 指针移动, 时间复杂度O(n)
  3. ArrayList读的效率比LinkedList高, 连续内存, 时间复杂度O(1), 写入效率没有LinkedList高, 会存在数据移动, 数组扩容复制操作

9. Map

基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键

初始容量16, 加载因子0.75

查询的时间复杂度

  • 数组: O(1)
  • 链表: O(n)
  • 红黑树: O(logn)

1. 数据结构

  • JDK 1.7 数据结构: 数组 + 链表 hash冲突后, 就都会形成链表, 是不想出现的结果, 数组就会扩容, 尽量避免hash冲突
  • JDK 1.8 数据结构: 数组 + 链表 + 红黑树 JDK 1.7基础上, 链表长度过长, 会导致查询效率常数阶O(1)变成线程阶O(n) , 为了提高查询效率 , 引入红黑树O(logn)

2. HashMap存值过程

  1. 根据key计算一个hash值
  2. 在put的时候判断数组是否存在, 如果不存在创建一个默认长度为16的数组
  3. 根据key的hash值与数组中的最大索引值进行与(&)运算得到索引位置 , 确定node在数组中的位置
  4. 判断该位置是否有元素, 如果没有元素直接放进去, 如果有元素, 判断key是否相同, 如果相同 , 把原来的node赋值给一个变量
  5. 再去判断该位置是红黑树还是链表
  6. 如果是红黑树, 以红黑树的形式将node放在树上, 如果是链表放在链表的最后位置
  7. 放完之后判断链表长度 , 如果链表长度大于8则要判断是否变成红黑树
  8. 返回被覆盖的值
  9. 判断整个数组是否需要扩容

3. HashMap和HashTable、ConcurrentHashMap的区别

  • HashMap线程不安全,
  • HashTable, ConcurrentHashMap线程安全
  • HashTable方法都是用synchronized修饰的同步代码块 , 并发情况下效率过低
  • ConcurrentHashMap解决HashTable效率低问题
    • 在JDK1.7的时候使用的分段锁
    • 1.8使用CAS解决线程安全问题和高并发响应速度

4. 为什么要在put的时候创建数组

  • 数组在内存中占用的一段连续的内存空间, 占用内存较多, 所以创建的map不使用也创建数组的话, 就会浪费内存.

5. HashMap什么时候会变成红黑树 什么时候由红黑树变成链表

  • 在数组长度到达64 , 并且链表长度到达8个时候由链表变成红黑树 , 当元素数量小于等于6的时候由红黑树转成链表.

扩展: 解决hash冲突的常用方法:

  • 开防寻址法
    • 如果出现了散列冲突,就重新探测一个空闲位置
    • 当往散列表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用了,就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止
  • 链表法
    • 就是hashMap解决hash冲突的方法

10. 为什么要设计迭代器

  • 迭代器适用性更强, 可以在不了解集合内部数据结构的情况下直接遍历
  • for循环需要知道遍历的数据类型

11.有哪些元注解

  1. @Target 描述注解的使用范围(类, 接口, 枚举, 方法, 参数, 构造器)
  2. @Inherited 允许继承父类注解
  3. @Retention 描述注解保留时间范围(保留到class文件中, 运行时保留 , 源文件保留)
  4. @Document 描述在写使用javadoc工具的时候是否要保留的注释信息

12. 构造器是否可以被重写?

  • 不能, 构造器不能被继承, 所以不能被重写
  • 构造器是构造对象唯一标识的方法

13. Object中的方法

  1. native方法
    1. getClass()
    2. hashCode()
    3. clone()
    4. notify()
    5. notifyAll()
    6. wait()
  2. equals()
  3. toString()
  4. finalize()

14. 加密

  • AES, DES, 3DES 对称加密

  • MD5 非对称加密 - 哈希加密 - 不可解密

  • BASE64

  • SHA - 哈密加密 - 不可解密

  • HMAC 不可解密

  • RSA, DSA - 数字签名

15. CAP理论

  • Consistency : 一致性是指写操作后的读操作读取到最新的数据 , 当数据分布在多节点上的时候 , 从任意节点读取都是最新的数据(主从) 要么返回正确数据 , 要么报错.
  • Availability : 可用性是指事务任何操作都可以得到响应结果 , 且不会出现响应超时或者响应错误 . 要么返回正确数据 , 要么返回旧数据.
  • Patition tolerance 分区容错性: 通常分布式系统的各个节点都部署在不同的子网 , 这就是网络分区 , 不可避免的可能会出现网络问题而导致节点之间通信失败 , 此时仍可对外提供服务

二、Spring

1. IOC

1.1 控制反转

  • 将创建管理对象的工作交给容器来做。在容器初始化(或在某个时间节点)通过反射机制创建好对象,在使用时直接从容器中获取。

1.2 依赖注入(DI)

  • IOC来管理对象关系,在创建有依赖关系的对象时,由IOC容器来注入依赖的兑现。构造器注入、setter方法注入。

2. AOP面相切面编程

2.1 AOP作用

  • 将一些系统性相关(日志、事务、安全)的编程工作,独立提取出来,独立实现,作为一个公共切面切入到相关的业务逻辑中。

  • 将业务逻辑代码与公共功能代码分离开,使开发人员能更专注地编写业务逻辑代码。

  • 在不改变业务逻辑代码的前提下,在业务逻辑之前、之后、或者周围添加横切关注点(切面),对目标功能进行扩展。

2.2 AOP术语解读

  • 通知:定义AOP切面执行的工作,以及切面执行的时间。(Before、After、After-returning、After-throwing、Around)
  • 切点:定义切面执行的地点,满足配置条件的目标方法(在什么地方执行切面)。
  • 连接点:切点的全集。一个程序可能会有多个切面,不同切面映射需要不同切点。
  • 切面:切点+通知。在什么时候、什么地点、执行什么工作。

2.3 AOP原理: 动态代理

实现动态代理的两种常用的方式:

  • JDK动态代理 - 基于接口 - 被代理的类 至少实现一个接口
    • jdk 官方的Proxy类
    • 要求:被代理的类至少实现一个接口
    • 实现:基于接口的动态代理
  • CGlib动态代理 - 基于子类 - 被代理的类, 不能是final修饰
    • 第三方的CGlib (如果想要使用的话 需要导入依赖 asm.jar)
    • 要求:被代理类不能用 final 修饰的类(最终类)

3.Spring注入bean的三种方式

  1. 构造方法注入
  2. setter方法注册
  3. 注解注入(属性注入, @Resource, @Autowired, @Inject)

3. Spring Bean生命周期

  1. 容器先实例化一个bean
  2. 再根据配置设置属性
  3. 如果Bean实现了BeanNameAware则调用Bean的setBeanName()方法传递Bean的ID
  4. 如果Bean实现了BeanFactoryAware则调用Bean的setBeanFactory()方法传入自身工厂
  5. 如果Bean实现了BeanPostProcessor 则会调用postProcessBeforeInitialization()返回一个Bean
  6. 如果Bean实现了InitializingBean, 则调用afterPropertiesSet()
  7. 执行initBean()方法 ( Bean定义的文件中使用"init-method"属性设定方法名称)
  8. 如果Bean实现了BanPostProcessor则会调用postProcessAfterInitialization()
  9. 如果Bean实现了DisposableBean接口, 则会调用该Bean的destroy()方法, 否则调用创建Bean是定义的destroy-method方法

4. 动态代理实现方式

  • JDK动态代理是利用反射机制生成一个实现代理接口的匿名类 , 调用具体方法前调用invokeHandle来处理
  • cglib是利用字节码技术, 执行方法时生成一个实现接口的.class文件

5. JDK动态代理和cglib动态代理区别

  1. JDK动态代理只能代理实现了接口的类 , 实现方式是实现InvocationHandler
  2. cglib动态代理是针对类实现代理 , 对指定类生成一个子类 , 覆盖其中的方法, 因为是继承, 所以被代理类属性不要用final修饰, 实现方式是实现MethodInterceptor

6. Spring如何解决循环依赖的

  • Spring利用三级缓存来解决循环依赖
    • 比如是A-B-A循环依赖问题, 首先调用A的实例化, 会在缓存中去找A对象, 发现缓存中没有,此时还没有设置属性, 就会调用实例化流程, 会将未实现完全的对象A存入三级缓存, 设置属性时会触发B的实例化, 也是先在缓存中获取B对象, 缓存中没有就开始实例化B对象, 因为B对象依赖A, 此时会触发获取A对象(是第二次获取A对象了), 这时缓存中就能获取到A对象了, 那么B对象就能顺理成章实例化出来, 那么A也能正常实例化出来了

7. Spring的@Transactional如何实现

  • 通过动态代理来实现的
    • 有事务调用没有事务的, 可以保证事务正常, 因为代理类有事务, 并且数据事务具有原子性, 调用代理前的方法,所以事务会生效
    • 没有事务调用有事务的, 事务会失效, 因为生成的代理类没有开启事务, 再去调用原有方法, 也是没有事务的

8. Spring事务的传播属性

所谓Spring事务的传播属性, 就是定义在存在多个事务同事存在的时候, Spring应该如何处理这些行为

  • Propagation.REQUIRED: 支持当前事务, 如果当前有事务, 加入当前事务, 如果当前没有事务, 则新建一个事务(默认)
  • Propagation.NOT_SUPPORTED: 不支持事务, 如果当前有事务, 就挂起当前事务, 执行完之后再恢复事务
  • Propagation.SUPPORTS: 如果当前有事务则加入, 没有就不用事务
  • Propagation.MANDATORY: 支持当前事务, 当前没有事务就抛出异常
  • PROPAGATION_NEVER: 非事务的方式执行, 如果当前有事务, 则抛出异常
  • Propagation.REQUIRES_NEW: 支持当前事务, 如果当前有事务, 则挂起当前事务, 然后创建一个新事务, 如果当前没有事务, 则创建一个新事务
  • Propagation.NESTED: 嵌套事务, 如果当前存在事务, 则嵌套在当前事务中, 如果当前没有事务, 则新建一个事务自己执行, 当内部事务回滚时不会影响外部事务提交, 外部事务回滚时会把内部事务也回滚

三. 消息队列的选择

1. RabbitMQ:

支持AMQP协议

优点:

  • 轻量级, 部署容易

  • 灵活自定义路由规则

  • 跨平台, 多种语言

缺点:

  • 出现消息堆积, 性能大大降低
  • 相比RocketMQ, Kafka性能低, 官方测试是每秒处理几万到几十万的消息

2. RocketMQ

优点

  • 提供了多种场景功能,比如:有序、事务、流计算等等

  • 性能比RabbitMQ要好,每秒处理几十万的消息量

缺点

  • 没有多租户
  • 不支持多语言

3. Kafka

优点

  • 默认是异步发送和接收消息的,所以性能很强,比RocketMQ和RabbitMQ都要好,如果是异步并且开启了消息压缩,那Kafka可以达到每秒两千万的消息处理量

  • 性能佳,可伸缩,支持消息持久化、分区、副本和容错

缺点

  • 扩容成本高:集群中新增的broker只会处理新topic,如果要分担老topic-partition的压力,需要手动迁移partition, 难度较大
  • partition过多会使得性能显著下降:ZK压力大,broker上partition过多让磁盘顺序写几乎退化成随机写。

四、 Elasticsearch

是一种存储和管理基于文档和半结构化的数据库, 也可以说是搜索引擎, 提供实时搜索, 分析结构化和非结构化的数据

1. 倒排索引

  • 就是“以内容的关键词”建立索引
  • 计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式

2. 概念

  1. 索引就是MySQL的数据库
  2. 字段表示列
  3. 文档就是一行数据

3. 用途

  1. 存储少修改, 不修改的数据
  2. 存日志
  3. 存流水信息

五、 MySQL

1. 四大特性

  • 原子性:事务是最小的执行单位, 执行要么都成功, 要么都失败
  • 持久性:事务对数据库的操作是不可逆的, 对数据库的修改会持久化到磁盘
  • 隔离性:事务并发执行的时候, 事务与事务之间互不干扰
  • 一致性:数据库在事务操作前和操作后状态是一致的, 从一个一致性到另一个一致性
    • 比如转账, A有100, B有0元, A向B转了100, 事务提交之后, 他们的钱加起来还是100

2. MySQL事务隔离级别

MySQL默认的隔离级别是可重复度, ORACLE默认是读已提交

  • 读未提交: 最低的隔离级别, 事务能读到其他事务未提交的数据, 不能避免脏读, 幻读, 不可重复读
  • 读已提交: 允许读取其他事务提交的数据, 可避免脏读, 不可重读读, 避免不了幻读
  • 可重复度: 同一条sql的查询语句查询出的结果一致, 可避免脏读, 不可重读读, 避免不了幻读
  • 串行化: 最高的隔离级别, 所有事务串行执行, 完全杜绝事务之间的影响

3. 脏读, 幻读, 可重复度, 不可重读度

  • 脏读: 一个事务读到另一个事务未提交的数据

  • 幻读: 一个事务两次读取的结果不一致, 第二次读的过程中另一个事务添加了数据

  • 不可重复度: 一个事务两次读取的结果不一致, 第二次读的过程中另一个事务修改了数据

  • 可重复度: 同一条sql的查询语句查询出的结果一致, 可避免脏读, 不可重读读, 避免不了幻读

4. 什么时候选择MyISAM, 和innodb区别

  1. 大量select和insert操作是选择MyISAM

  2. 数据量大, 没有事务操作的时候选择MyISAM

  3. 大量update和insert时选择InnoDB

    区别

    • MyISAM不支持事务, InnoDB支持事务
    • MyISAM是表级锁, InnoDB行锁
    • MyISAM不支持外键, InnoDB支持外键
    • MyISAM支持全文索引, InnoDB不支持
    • MyISAM表的查询,更新,插入效率比InnoDB高

5. 为什么选择B+Tree做索引数据结构

  1. BTREE: 有序数组 + 平衡多叉树
  2. B+TREE: 有序数组 + 链表 + 平衡多叉树
  3. BTREE数据结构是平衡二叉树, 数据一直增多, 树的深度一直增加, 影响查询效率
  4. B+TREE的数据全部存放在叶子节点中,非叶子节点用来做索引, 叶子节点中有一个指针指向下一个叶子节点, 提高了区间访问的性能
  5. B+TREE是一个扁平的数据结构, 避免了很高的深度
  6. 数据库引用B+TREE是因为BTREE提高了磁盘I/O性能, 但没有解决元素遍历效率低下的问题

6. MySQL索引四种类型

  • B+TREE, HASH, FULLTEXT(全文索引)和R-TREE

7. 建索引注意事项

  1. 越小的数据类型在磁盘, 内存,cpu缓存中需要更少的空间, 处理起来更快
  2. 整形比起字符处理开销更小
  3. 排序字段加上索引

8. B+Tree的叶子能存哪些数据

  1. 真实数据库数据
  2. 主键索引指针

9. order by原理

  1. 数据过滤完之后, 把符合条件的数据放到sort buffer中进行排序, 当sort buffer中空间不足的时候会用临时文件排序
  2. 为了尽量避免临时文件排序, MySQL会避免非必要的字段放到sort buffer中, 等排序完之后, 再将这个字段取出来

10. 建表注意事项

  1. 建表时候尽量不要让字段默认值为null, 因为复合索引只有一个字段为null, 那么这一列索引是无效的
  2. 设置好字段长度, 以免浪费空间, 并且后期做索引的话, 字段长度越短, 处理越快
  3. 合理添加冗余字段
  4. 主键建议int类型, 分布式用雪花算法
  5. 对于字段太多的表, 考虑拆表, (比如用户有很多附加属性, 后期附加属性, 可以拆表)
  6. 对于表中经常不被用的字段或者存储数据比较多的字段, 考虑拆表

11. SQL优化

  1. 尽量不要使用select *,只需查询所需字段, 增加了不必要的消耗, 可能会导致不走覆盖索引

  2. 尽量不要使用!=, <>, is null, or, not in

  3. 适当的调大一些sort_buffer_size做排序时候用的

  4. 模糊查询用右模糊

  5. 避免在where条件中做函数运算

  6. 建合适的索引

  7. in包含的值不应过多

  8. 尽量用union代替union all

    union all前提是两个结果集没有重复数据, 因为不去重

    • union
      • 对两个结果集进行并集操作
      • 不包括重复行,相当于distinct
      • 同时进行默认规则的排序;
    • union all
      • 对两个结果集进行并集操作
      • 包括重复行, 即所有的结果全部显示
      • 不排序
  9. 查询数据过多, 用程序来分段查询, 避免扫描行书过多

  10. 避免使用隐式转换, 查询int类型字段, 条件中用引号括起来

  11. 联合所以遵循最左匹配原则

  12. 使用join代替子查询

12. MySQL索引失效

  1. 索引列存null值
  2. 查询条件使用is null
  3. 前模糊, 或者全模糊
  4. or条件不是索引列
  5. 联合索引没遵循最左匹配原则

13 select * 和select全字段, select count(*) 和select count(1), select count(列)

  • select * 可能会导致用不了覆盖索引

    • 什么是覆盖索引: 表中有5个字段(1,2,3,4,5), 1,3,5联合索引, 查询结果就这三个字段, 即覆盖索引
  • 同样的查询条件, 没有索引的表select * 和select全字段效率基本相等, 有索引读到索引数据后还是再读取其他数据

  • count(*) 和count(1)会统计为null的列, count(列)不会

14. MySQL 5 和MySQL 8区别

  1. 性能: MySQL 8相对于MySQL 5.7有更高的性能和更好的资源管理能力。

  2. 安全性: MySQL 8对安全性的增强比MySQL 5.7更好,包括更强大的密码验证和更好的访问控制。

  3. 新特性: MySQL 8带来了许多新特性和功能,多了很多编码格式的数据, 包括JSON数据类型、多语言支持等,

  4. 数据字典: MySQL 8使用数据字典方式存储数据,而MVSQL5.7使用的是表格方式,这是一个重要的区别

  5. 兼容性: MySQL 8相对于MySQL 5.7有更好的兼容性,可以兼容更多的操作系统和平台。

15. MVCC

一多版本种并发控制方法

为什么需要MVCC

  • 数据库原生的锁
    最原生的锁,锁住一个资源后会禁止其他任何线程访问同一个资源。但是很多应用的一个特点都是读多写少的场景,很多数据的读取次数远大于修改的次数,而读取数据间互相排斥显得不是很必要。

  • 读写锁的出现
    读锁和读锁之间不互斥,而写锁和写锁、读锁都互斥。这样就很大提升了系统的并发能力。之后人们发现并发读还是不够

  • mvcc概念出现(mysql快照读)
    读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务只看到自己特定版本的数据

16. MySQL集群

一般采用双主双从单写模式
主从架构

17. Explain关注字段

  • type: 依次排列 , 最优到最差 , system > const > eq_ref > ref > range > index > ALL
    • 最少达到range
    • index: 全表索引扫描
    • all: 全表数据扫描
  • key 用到的索引
  • rows : 该列是MySQL估计要读取并检测的行数 , 注意这个不是结果集里的行数
  • Extra列 这一列是额外信息
    • Using index : 使用覆盖索引
    • Using where : 使用where语句来处理结果 , 查询的列未被索引覆盖
    • Using index condition : 查询的列 不完全被索引覆盖 , where条件是一个前导列的范围
    • Using temporary : mysql需要创建一张临时表来处理查询 , 出现这种情况一般要进行优化 , 首先要考虑进行索引优化
    • Using filesort : 将用外部排序而不是索引排序 , 数据较小时从内存中排序 , 否则需要在磁盘完成排序 , 这种情况一般也要考虑建索引来优化
    • impossible where 说明数据库这个查询是不可能有结果输出的, 会走全表扫描

六、Redis

1. key淘汰策略,过期策略

  1. 淘汰策略:
    1. 默认淘汰策略: 当redis内存满之后, 新进来的新增请求直接抛异常.
    2. 设置了超时时间的key:
      1. 淘汰最近最少使用的
      2. 所有key中随机淘汰
      3. 淘汰将要失效的
    3. 未设置过期时间的
      1. 淘汰最近最少使用的
      2. 所有key中随机淘汰

2. 过期策略

  1. 定时删除: 设置了过期时间的key到了超时时间就删除
    • 优点是节省了内存空间
    • 缺点是同一时间大量key到期, 会占用很多的cpu资源去删除到期的key
  2. 惰性删除: 到了过期的时间不删除, 下次请求时再删除
    • 优点: 节省了cpu资源占用
    • 缺点: 大量不使用key存在内存会导致内存泄露
  3. 定期删除: 制定删除时间和删除频率, 减少内存占用和同一时间cpu占用
    • 内存占用友好度不如定时删除, cpu占用友好度不如惰性删除

3. 持久化机制

  1. RDB: 到了规定的时候后, Redis会拉起一个新线程将内存中的数据存一个二进制快照
    • 优点:
      • RDB文件小, 非常适合做备份
      • Redis加载RDB速度要比AOF文件速度快得多, RDB是备份时的内存数据, AOF是存储的是操作指令
    • 缺点:
      • RDB不能做到完全的实时化, 在备份的时候如果Redis宕机, 则会导致丢失开始备份时候的数据, 不适用于实时性要求较高的场景.
  2. AOF: 存储Redis的新增操作命令写的AOF可读日志文件
    • 优点:
      • AOF机制是追加日志文件, 对服务器性能占用较低, 比RDB快
    • 缺点:
      • AOF生成的日志文件太大, 需要一直重写瘦身(重写: 读取Redis中的全量数据写到AOF中, 重写这期间的数据会写到原有的AOF文件中, 当重写结束后, 期间加操作也写入新的AOF文件中, 再将新AOF文件重命名, 替换原有AOF文件)
      • 比RDB文件大得多, 重启Redis恢复数据的时候也比RDB慢得多

4. 如何解决缓存击穿, 缓存雪崩, 缓存穿透

  1. 缓存穿透: 一直请求缓存中没有的数据
    • 解决方法: 将请求的key的值设置为null, 设置个超时时间, 不要太长, 布隆过滤器
  2. 缓存击穿: 缓存中没有数据, 数据库中有, 并发大的情况下, 所有请求全部打到数据库, 会给数据库造成很大的压力
    • 解决方法: 缓存预热 , 热点数据用不过期 , 加互斥锁, 只让一个用户请求数据库, 其他请求查缓存
  3. 缓存雪崩: 同一时间大批量key过期, 大量请求进来打到数据库, 数据库压力巨大, 还可能会导致宕机, 缓存击穿是同一条数据, 雪崩是不同数据同一时间过期
    • 解决方法: 缓存设置随机的过期时间, 热点数据用不过期, (如果数据库是分布式部署, 将热点数据均匀分布)

5. Redis分布式锁逻辑未执行完锁到期怎么办

  1. Redission开启一个锁的同时后台开启一个看门狗锁, 每隔10s判断一次, 如果最后一次检查业务逻辑还未结束, 就给原来的锁续期

七、JUC

1. 进程和线程,并发和并行

  • 进程:进程是计算机程序在一个数据集上的一次运行过程,也是资源分配和调度的基本单位
  • 线程:线城是进程中的一个执行单元。一个进程中至少有一个线程
  • 并行:指的是两个或多个事件在同一时刻发生,
  • 并发:指的是两个或多个事件在同一时间间隔内发生

2.死锁以及产生死锁的四个条件

  • 死锁是指多个进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
  • 产生死锁的必要条件:1. 互斥条件 2. 请求与保持条件 3. 不剥夺条件 4. 循环等待条件

3. Java中线程的状态

  • 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  • 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  • 阻塞(BLOCKED):表示线程阻塞于锁。
  • 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  • 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  • 终止(TERMINATED):表示该线程已经执行完毕

4. sleep() 和wait(),sleep()和yield()有什么区别

  • sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。
  • sleep() 不释放锁;wait() 释放锁。
  • sleep() 方法执行完成后,线程会自动苏醒;wait() 方法被调用后,线程不会自动苏醒,与notify()搭配使用。
  • 首先yield () 也是 Thread线程类的静态方法,也是不释放锁。
  • 线程执行 sleep()方法后转入阻塞(blocked)状态;而执行 yield()方法后转入就绪(ready)状态,因此接下来的获得执行权的线程可能是当前线程,也可能是其他线程。
  • sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;

5. notify()和notityAll()有什么区别

  • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
  • notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

6. CAS, synchronized

6.1 CAS

  • CAS,compare and swap的缩写,中文翻译成比较并交换
  • CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作

6.2 CAS的缺点以及怎样解决ABA问题

  • 缺点
    1) 如果CAS失败就会一直进行尝试,即一直在自旋,导致CPU开销。
    2) 只能一个共享变量的原子操作
    3) 会产生ABA问题。
  • ABA问题
      如果一个线程在初次读取时的值为A,并且在准备赋值的时候检查该值仍然是A,但是可能在这两次操作之间,有另外一个线程现将变量的值改成了B,然后又将该值改回为A,那么CAS会误认为该变量没有变化过。
  • 解决:添加版本号来解决

6.3 为什么使用CAS代替synchronized

  • synchronized加锁,是一种悲观锁, 同一时间段只允许一个线程访问,能够保证一致性但是并发性下降。而是用CAS算法使用do-while不断判断而没有加锁(实际是一个自旋锁),保证一致性和并发性。

6.4 java多线程锁

  • 公平锁:多个线程按照申请锁的顺序来获取锁,先来后到在等待队列中FIFO
  • 非公平锁:多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的比先申请的有先获取,在高并发的情况下,有可能造成优先级翻转或饥饿现象
  • 乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。基本通过CAS实现
  • 悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁
  • 可重入锁(递归锁):同一线程在外层函数获取锁以后,进入内层函数时自动获取锁.其作用是避免死锁。
  • 自旋锁:获取不到锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,好处是减少上下文切换的消耗,坏处是消耗CPU
  • 独占锁:每次只允许一个线程获取锁,如 synchronized和ReentrantLock
  • 共享锁:允许多个线程同时获取锁,如读写锁(ReadWriteLock)
  • 读写锁:读读共享 读写-写读-写写是独占

6.5 哪些场景需要额外注意线程安全问题?

  • 访问公共资源
  • 依赖时序操作(map的remove)
  • 要使用没有声明线程是安全的对象

6.6 使用线程池比手动创建线程好在哪里?

  • 灵活控制线程数量, 杜绝浪费资源
  • 减少频繁新建线程, 销毁线程所需资源

6.7 线程池的有哪些参数各个参数的含义?

  • corePoolSize 核心线程数
  • maxPoolSize 最大线程数
  • workQueue 工作队列
  • keepAliveTime 闲置线程存活时长
  • ThreadFactory 创建线程的工厂
  • Handle 处理被拒绝的任务

6.8 阻塞队列定义以及种类

  • 当队列是空的,从队列中获取元素的操作将会被阻塞当队列是满的,从队列中添加元素的操作将会被阻塞
  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。
  • PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
  • DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
  • SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
  • LinkedTransferQueue:由链表组成的无界阻塞队列。
  • LinkedBlockingDeque:由链表组成的双向阻塞队列。

6.9 常见线程池:

  • fixedThreadPool: 传统高效的线程池

  • scheduleThreadPool: 可以执行周期性任务的线程池

  • singleThreadPool: 单线程线程池

  • cacheThreadPool: 可缓存线程池

6.10 线程池具体工作流程

添加一个请求任务时,线程池会做出以下判断:

  • 如果正在运行的线程数量小于corePoolSize,会立刻创建线程运行该任务
  • 如果正在运行的线程数量大于等于corePoolSize,会将该任务放入阻塞队列中
  • 如果队列也满但是正在运行的线程数量小于maximumPoolSize,线程池会进行拓展,将线程池中的线程数拓展到最大线程数
  • 如果队列满并且运行的线程数量大于等于maximumPoolSize,那么线程池会启动相应的拒绝策略来拒绝相应的任务请求
  • 当一个线程完成任务时,它会从队列中取下一个任务来执行
  • 当一个线程空闲时间超过给定的keepAliveTime时,线程会做出判断:如果当前运行线程大于corePoolSize,那么该线程将会被停止。最终的线程数目会收缩到corePoolSize的大小

6.11 线程池的拒绝策略

  • AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
  • CallerRunsPolicy:将某些任务交给提交者处理
  • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中尝试再次提交当前任务
  • DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果任务允许丢失,那么该策略是最好的方案

6.12 synchronized和ReentrantLock的区别

  • synchronized是关键字,ReentrantLock是类
  • synchronized不需要用户手动释放锁,执行完会系统会自动释放对锁的占用
  • ReentrantLock需要用户手动释放锁。否则容易出现死锁现象
  • synchronized不可以中断,除非抛出异常或者正常完成;ReentrantLock可以被中断
  • synchronized是非公平锁
  • ReentrantLock默认是非公平锁,但是构造方法可以传入boolean值,true为公平,false为非公平

6.13 ThreadLocal

  • ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,线程之间互不干涉,保证了数据安全,是一种以空间换时间的做法。在每个线程中都创建了一个 ThreadLocalMap 对象,ThreadLocalMap 中使用的 key 为 ThreadLocal,而 value 则是变量的值。在不同线程中,访问同一个ThreadLocal的set和get方法,它们对ThreadLocal的读、写操作仅限于各自线程的ThreadLocalMap,从而使ThreadLocal可以在多个线程中互不干扰地存储和修改数据。

6.14 ThreadLocal造成内存泄漏的原因与解决方案

  • ThreadLocal中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后最好手动调用remove()方法

6.15 并发编程三个必要因素

  • 原子性:原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
  • 可见性:一个线程对共享变量的修改,另一个线程能够立刻看到,如synchronized,volatile
  • 有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)

八、Tomcat, Jetty, Undertow

1. 区别

  1. Jetty和Undertow都是基于NIO实现的高并发轻量级服务
  2. Tomcat是一款重量级服务器( Tomcat6以后支持NIO), Jetty和Undertow相比Tomcat轻量化
  3. 负载不高的情况下, 三款容器的负载能力差不多
  4. 负载较高的情况下, Jetty负载能力最差, Undertow强于Tomcat

2. 什么是NIO (非阻塞式输入输出)

  1. NIO是采用内存映射的方式处理输入输出, NIO是将文件映射到内存中, 这样就可以像访问内存一样访问文件, 传统的BIO(Blocking IO)会阻塞
  2. NIO和BIO的目的是一样, 但是使用方式不同, NIO是面向缓冲区, 基于通道的IO, 效率要高得多

九、JVM

1. 内存结构

JVM 分为堆区和栈区,还有方法区,初始化的对象放在堆里面,引用放在栈里面, class 类信息常量池(static 常量和 static 变量)等放在方法区

  • 方法区: 线程共享 主要存储类信息、常量、静态变量、即时编译器编译后的代码
  • 堆: 线程共享 初始化的对象,成员变量 (那种非 static 的变量),所有的对象实例和数组都要 在堆上分配
  • 虚拟机栈: 线程私有 存储局部变量表、对象指针、操作栈、动态链接、方法出口
  • 本地方法栈: 线程私有 主要为 Native 方法服务
  • 程序计数器: 线程私有 记录当前线程执行的行号, 指向下一条要执行的指令

2. 堆里面的分区:Eden,survival (from+ to),老年代,各自的特点

  • 堆里面分为新生代和老生代(java8 取消了永久代,叫元空间)
  • 新生代包 含 Eden+Survivor 区
    • survivor 区里面分为 from 和 to 区
    • 触发youngGC时, 存活下来的对象会从from区复制到to区,然后from区和to区角色互换, 当经过一次或者多次 GC 之后,hotspot虚拟机是15次(由JVM参赛MaxTenuringThreshold决定的,默认值就是15), 存活下来的对象会被移动 到老年代
  • 当 JVM 内存不够用的时候,会触发 Full GC,清理 JVM 老年区
  • 如果有特别大的对象,新生代放不下, 就会使用老年代的担保,直接放到老年代里面。因为 JVM 认为,一般大对象的存 活时间一般比较久远。

3. 新生代和老年代占用堆区比例

1: 2

4. 新生代中的伊甸园区和幸存者0区、幸存者1区的占比

8:1:1

5. GC的三种方法

复制算法 - 新生代

  • youngGC

标记清除 - 老年代

  • 先标记,标记完毕之后再清除,效率不高,会产生碎片

标记整理 - 老年代

  • 标记完毕之后,让所有存活的对象向一端移动

现在一般都采用分代收集算法

  • 新生代回回收大批量对象, 少量存活, 用复制算法, 复制少量对象就能完成新生代收集
  • 老年代存活率高, 就用标记整理, 标记清除算法来收集

6. Java类加载过程

类的加载过程又分为三个步骤

  1. 类的装载(Loading)
    将类的class文件读入内存,并为之创建一个java.lang.Class的实例对象,此过程由类加载器(负责类的加载,对应一个Class实例)完成。

  2. 链接(Linking)
    验证(Verify):确保加载的类的信息符合JVM规范,例如:每一个class文件都以cafebabe开头,没有安全方面的问题。
    准备(Prepare):正式为类中的(static)静态变量分配内存,并设置默认初始化值的阶段。这些内存都在方法区中进行分配。
    解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

  3. 初始化(initialization)
    执行类构造器方法的过程。执行所有类中(static)静态变量和(static)静态代码块中的语句的赋值动作,这些操作都在方法中进行。

因为类的加载过程中还没有对象的存在,因而赋值操作也只能是对静态变量进行

7. 类加载机制

  • 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的 java 类型

8. 类加载器双亲委派模型机制

  • 当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类
    去加载,如果此时父类不能加载,再交给子类去完成类的加载

9. 类加载器有哪些

  1. 启动类加载器(Bootstrap ClassLoader)用来加载 java 核心类库,JRE中的rt.jar
  2. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。JRE中的ext目录下的JAR包
  3. 系统类加载器(system class loader): 也成为应用程序加载器, 它根据 Java 应用的类路径(CLASSPATH) 来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
  4. 用户自定义类加载器,通过继承 java.lang.ClassLoader 类的方式实现。

十、分布式事务

1. seata

  1. 工作流程

    • Business(业务入口)会通过注解说明这是一个全局事务, TM(事务管理器)
    • Business会告诉TC(seata服务), 要开启一个全局事务, TC会生成一个XID返回给Business
    • Business拿到XID后开始调用微服务
    • 被调用的微服务称为资源管理者(RM), RM会在undo_log中记录自己的操作情况, 也会告诉TM执行结果
    • Business收到各个微服务的结果后 , 请求TC这个XID要提交, 否则回滚
    • TC收到请求后, 向XID下的所有分支事务发起相应的请求
    • 各个微服务收到TC的请求后,执行相应指令,并把执行结果上报给TC
  2. 全局事务回滚时怎样实现的

    • undo_log会记录修改前的数据, 如果需要回滚会根据undo_log中的数据组装sql执行, 提交则删除undo_log
  3. RM是怎样和TC通信的

    • 通过拦截JDBC, 监控到开启了本地事务, 就会自动向TC注册, 生成回滚日志, 想TC汇报执行结果
  4. 二阶段回滚失败会怎样

    • 如果TC向该XID下的各个分支事务发送回滚的指令时有一个微服务挂了, 那么所有正常的微服务也不会回滚, 当这个微服务再次启动后, TC会再发送回滚指令
  5. 核心组件

    • 事务协调器 TC
    • 事务管理者 TM
    • 资源管理者 RM

十一、Dubbo

配置优先级:

  • 方法级优先 ,接口级次之, 全局配置再次之
  • 如果级别一样, 则消费方优先, 提供方次之
  1. 注册中心宕机了, 提供者和调用方之前有掉用过可通过本地缓存调用
  2. 提供者全部宕机, 消费者也不能使用了
  3. 注册中心集群, 任意宕机一台, 将自动切换到另一台

DubboReference可以直接配置服务提供者地址, 绕过注册中心

Dubbo负载均衡策略

  • 基于权重随机
  • 基于权重轮询
  • 最少活跃数 (看上一次哪个节点处理得快)
  • 一致性hash(根据参数hash选择节点, 参数一样, 一直调用同一个服务)

RPC工作原理

  • client stub接收到请求后, 将方法, 请求封装成网络传输消息体
  • 将请求通过sockets发送给服务端
  • server stub收到消息后解码, 根据解码信息调用本地服务
  • server stub将结果封装成网络传输消息体通过sockets发送给调用方
  • client stub接收到消息, 并进行解码得到最终结果
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值