写点什么

java 整理,springboot2 精髓百度云

  • 2021 年 11 月 10 日
  • 本文字数:7883 字

    阅读完需:约 26 分钟

并发问题




  • Java 中 volatile 与 synchronized 有什么区别,分别作用于哪些场景


【答题要点】


1、volatile 本质是在告诉 jvm 当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.


2、volatile 仅能使用在变量级别,synchronized 则可以使用在变量,方法中.


3、volatile 仅能实现变量的修改可见性,但不具备原子特性,而 synchronized 则可以保证变量的修改可见性和原子性.


volatile 不会造成线程的阻塞,而 synchronized 可能会造成线程的阻塞.


4、volatile 标记的变量不会被编译器优化,而 synchronized 标记的变量可以被编译器优化.


  • synchonrize 和 juc 中的锁比较分别适用于哪些场景


【答题要点】


1、ReentrantLock 在内存上的语义与 synchronize 相同, 但是它提供了额外的功能, 可以作为一种高级工具. 当需要一些可定时, 可轮询, 可中断的锁获取操作, 或者希望使用公平锁, 或者使用非块结构的编码时才应该考虑 ReetrantLock.


2、在业务并发简单清晰的情况下推荐 synchronized, 在业务逻辑并发复杂, 或对使用锁的扩展性要求较高时, 推荐使用 ReentrantLock 这类锁.


  • 既要保证线程安全又要尽可能提升性能,怎么取得平衡?


【答题要点】


1、分析业务场景,尽可能的实现无锁编程;


2、对于并发场景,尽可能的缩小锁的范围;


  • 分布式场景中,如何实现一个全局锁


【答题要点】


1、使用数据库实现;


2、使用缓存实现;


总之候选人能够回答出来,用一个全局唯一的资源来满足资源竞争的顺序执行和原子性就行。


  • 对于一个 8 核的的高性能 CPU 来说在多线程场景下是不是线程池设置的越大越好?如何确定线程池的大小,设置不当会带来什么问题?


【答题要点】


1、并非越大越好,线程池大小的设置要根据 CPU 处理的任务特征来区别对待。


2、如果线程执行的是 CPU 密集型任务服务器的物理内核数就应该被视为是有限的资源,这样创建的线程数就不应该超过系统的内核数。


3、如果线程执行的是 IO 密集型任务就要根据 IO 的占比和速度进行性能测试来确认线程池的大小。


4、线程池大小设置的过小或者过大都会导致系统产生问题无法利用系统资源,如果线程池的线程数量过少这使得用户需要花费很长时间来等待请求的响应。但是,如果允许创建过多的线程,会产生线程不断切换,系统的资源又会被耗尽,这会对系统造成更大的破坏,甚至宕机。


JVM




  • 题一:jvm 的类加载机制是什么样的?有几类加载器?


参考答案:


jvm 通过双亲委派模型进行类的加载,即当某个类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。


JVM 提供了 3 种类加载器:


1》启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类。


2》扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库。


3》应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。


  • 题二:JDK8 中 MetaSpace 代表什么?


参考答案:


MetaqSpace 是 JDK8 才诞生的名词,它是一种新的内存空间,中文译为:元空间;JDK8 HotSpot 中移除了永久代(PermGen Space),使用 MetaSpace 来代替,MetaSpace 是使用本地内存来存储类元数据信息。内存容量取决于操作系统虚拟内存大小,通过参数 MaxMetaspaceSize 来限制 MetaSpace 的大小。


  • 题三:JVM 内存结构是什么样的?


参考答案:


JVM 内存主要分为五个区:方法区,虚拟机栈,本地方法栈,堆,程序计数器;每个区特点如下:


1》方法区


1.1、有时候也称为永久代,在该区内很少发生垃圾回收,但是并不代表不发生 GC,在这里进行的 GC 主要是对方法区里的常量池和对类型的卸载


1.2、方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。


1.3、该区域是被线程共享的。


1.4、方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。


2》虚拟机栈


2.1、虚拟机栈也就是我们平常所称的栈内存,它为 java 方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。


2.2、虚拟机栈是线程私有的,它的生命周期与线程相同。


3》本地方法栈


本地方法栈和虚拟机栈类似,只不过本地方法栈为 Native 方法服务。


4》堆


java 堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。


5》程序计数器


内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个 java 虚拟机规范没有规定任何 OOM 情况的区域。


  • 题四:Java 中垃圾收集的方法有哪些?


参考答案:


1》标记-清除:


这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次 GC 动作。


2》复制算法:


为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。


于是将该算法进行了改进,内存区域不再是按照 1:1 去划分,而是将内存划分为 8:1:1 三部分,较大那份内存交 Eden 区,其余是两块较小的内存区叫 Survior 区。每次都会优先使用 Eden 区,若 Eden 区满,就将对象复制到第二块内存区上,然后清除 Eden 区,如果此时存活的对象太多,以至于 Survivor 不够时,会将这些对象通过分配担保机制复制到老年代中。(java 堆又分为新生代和老年代)


3》标记-整理


该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。


4》分代收集


现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。


  • 题五:MinorGC 和 FullGC 的区别?


参考答案:


1》Minor GC 通常发生在新生代的 Eden 区,在这个区的对象生存期短,往往发生 GC 的频率较高,回收速度比较快,一般采用复制-回收算法


2》Full GC/Major GC 发生在老年代,一般情况下,触发老年代 GC 的时候不会触发 Minor GC,所采用的是标记-清除算法


  • 题六:产生 concurrent-mode-failure 的原因


参考答案:在 CMSGC 过程中,由于老年代剩余空间无法存放需要分配的对象,导致产生上述原因。


  • 题七:请写出常用的几种垃圾回收器及启用参数


参考答案:


1》串行收集器:暂停所有的线程,属于单线程工作,启用命令:-XX:+UseSerialGC


2》并行收集器(默认):暂停所有线程,多线程工作,启用命令:-XX:+UseParNewGC


3》G1 收集器:这个主要是对堆内存进行分区,并发性回收,启用:-XX:+UseG1GC


4》CMS 收集器:多线程扫描,使用的算法是标记清除算法,标记需要回收的对象,进行回收,启动命令:-XX:+UseConcMarkSweepGC


设计模式




  • 题一:平时工作中用到过哪些设计模式?分别有什么特点?


参考答案:


1》适配器模式:


2》工厂模式


3》单例模式


  • 题二:在 Java 中,什么时候用重载,什么时候用重写?


参考答案:


对有经验的 Java 设计师来说,这是一个相当简单的问题。如果你看到一个类的不同实现有着不同的方式来做同一件事,那么就应该用重写(overriding),而重载(overloading)是用不同的输入做同一件事。在 Java 中,重载的方法签名不同,而重写并不是。


  • 题三:请列举出在 JDK 中几个常用的设计模式?


参考答案:


1》单例模式用于 Runtime, Calendar 和其他的一些类中。


2》工厂模式被用于各种不可变的类如 Boolean,像 Boolean.valueOf 方法。


3》观察者模式被用于 swing 和很多的时间监听中。


4》装饰器模式被用于多个 java IO 类。


  • 题四:举一个用 Java 实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类层次?


参考答案:


装饰模式增加强了单个对象的能力。Java IO 到处都使用了装饰模式,典型例子就是 Buffered 系列类如 BufferedReader 和 BufferedWriter,它们增强了 Reader 和 Writer 对象,以实现提升性能的 Buffer 层次的读取和写入。


笔试题一:写出至少两种单例模式的实现


参考答案:


1》懒汉式


public class TestSingleton{


private static TestSingleton instance;


private TestSingleton(){


}


public static synchronized TestSingleton getInstance(){


if(instance == null){


instance = new TestSingleton();


}


return instance;


}


}


2》饿汉式


public class TestSingleton {


private static TestSingleton instance = new TestSingleton();


private TestSingleton() {


}


public static TestSingleton getInstance() {


return instance;


}


}


  • 笔试题二:什么是适配器模式?用 Java 实现一个适配器模式


参考答案:


适配器模式在现代的 Java 框架中十分常用。这种模式适用于以下场景:想使用一个已存在的类,但是该类不符合接口需求;或者需要创建一个可重用的类,适配没有提供合适接口的其它类。


java.io.InputStreamReader(InputStream) (返回一个 Reader)


java.io.OutputStreamWriter(OutputStream) (返回一个 Writer)


Spring




  • 【spring 的 Annotation 使用】Spring @Resource、@Autowired 的区别


【参考答案】


@Resource 默认是按照名称来装配注入的,只有当找不到与名称匹配的 bean 才会按照类型来装配注入;@Autowired 默认是按照类型装配注入的;


  • 【spring 的 scope 理解】在两个不同的线程中通过 Spring 获取同样 id 的 bean,获取到的是同一个对象还是不同对象?


【参考答案】


  1. “同一个对象”,如果仅这样回答,可以得到 1 分。

  2. 考虑了 scope,并且答出不同的 scope 下的结论是同一个对象还是不同对象,可以加分


  • 【Spring Boot Starter 原理】总结 Spring Boot Starter 的工作原理


【参考答案】


  1. Spring Boot 在启动时扫描项目所依赖的 JAR 包,寻找包含 spring.factories 文件的 JAR

  2. 根据 spring.factories 配置加载 AutoConfigure 类

  3. 根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring Context


  • 【spring 实例初始化】我们希望一个 bean 实例被初始化后执行一些逻辑,怎么做?


【参考答案】


如果存在类实现 BeanPostProcessor(后处理 Bean) ,执行 postProcessBeforeInitialization


如果 Bean 实现 InitializingBean 执行 afterPropertiesSet


调用<bean init-method="init"> 指定初始化方法 init


如果存在类实现 BeanPostProcessor(处理 Bean),执行 postProcessAfterInitialization


  • 【spring 设计模式】Spring 框架中都用到了哪些设计模式,并举例说明


【参考答案】


代理模式—在 AOP 和 remoting 中被用的比较多。


单例模式—在 spring 配置文件中定义的 bean 默认为单例模式。


模板方法—用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。


前端控制器—Spring 提供了 DispatcherServlet 来对请求进行分发。


视图帮助(View Helper )—Spring 提供了一系列的 JSP 标签,高效宏来辅助将分散的代码整合在视图里。


依赖注入—贯穿于 BeanFactory / ApplicationContext 接口的核心理念。


工厂模式—BeanFactory 用来创建对象的实例。


  • 【spring 循环依赖(spring circular dependency)】如何解决 Spring 循环依赖


【参考答案】


首先什么叫循环依赖,有几种情况:


A 的构造方法中依赖了 B 的实例对象,同时 B 的构造方法中依赖了 A 的实例对象


A 的构造方法中依赖了 B 的实例对象,同时 B 的某个 field 或者 setter 需要 A 的实例对象,以及反之


A 的某个 field 或者 setter 依赖了 B 的实例对象,同时 B 的某个 field 或者 setter 依赖了 A 的实例对象,以及反之


Spring 通过三级缓存加上“提前曝光”机制,配合 Java 的对象引用原理


  1. A 首先完成了初始化的第一步,并且将自己提前曝光到 singletonFactories 中此时进行初始化的第二步,发现自己依赖对象 B,此时就尝试去 get(B),发现 B 还没有被 create,所以走 create 流程。

  2. B 在初始化第一步的时候发现自己依赖了对象 A,于是尝试 get(A),由于 A 通过 ObjectFactory 将自己提前曝光了,所以 B 能够通过 ObjectFactory.getObject 拿到 A 对象

  3. B 拿到 A 对象后顺利完成了初始化阶段 1、2、3,完全初始化之后将自己放入到一级缓存 singletonObjects 中。此时返回 A 中,A 此时能拿到 B 的对象顺利完成自己的初始化阶段 2、3,最终 A 也完成了初始化。


前面讲到的三种情况,第一种是无法解决的。


Redis




  • 【Redis 数据结构】Redis 支持哪几种数据结构


【参考答案】


字符串 String、字典 Hash、列表 List、集合 Set、有序集合 SortedSet。


  • 【Redis 查询 key 列表】假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如何将它们全部找出来?


【参考答案】


? 使用 keys 指令可以扫出指定模式的 key 列表。


? redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。为了避免这种问题,可以换用 scan 指令。scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。


  • 【redis 集群搭建】使用 redis 进行缓存数据时,当三台机器扩容到四台时,如何能做到迁移数据量最小


【参考答案】


组建集群时使用分布式一致算法,扩容后能尽可能命中原来的机器


  • 【redis 分布式锁】使用 redis 做一个分布式锁


【参考答案】


? 先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。但这种做法可能带来的一个问题:在 setnx 之后执行 expire 之前进程意外 crash 或者要重启维护了,导致永远不释放该锁。


? 为了避免上面提到的问题。setnx 时加上一个时间戳,表示失效时间。


SETNX lock.foo <current Unix time + lock timeout + 1>


该锁包含一个 Unix 时间戳,如果这样一个时间戳等于当前的 Unix 时间,该锁将不再有效。


另一个客户端 C4 检测到一个过期的锁并且都尝试去释放它的算法:


? C4 发送 SETNX lock.foo 为了获得该锁


? 已经崩溃的客户端 C3 仍然持有该锁,所以 Redis 将会返回 0 给 C4


? C4 发送 GET lock.foo 检查该锁是否已经过期。如果没有过期,C4 客户端将会睡眠一会,并且从一开始进行重试操作


? 另一种情况,如果因为 lock.foo 键的 Unix 时间小于当前的 Unix 时间而导致该锁已经过期,C4 会尝试执行以下的操作:


GETSET lock.foo <current Unix timestamp + lock timeout + 1>


? 由于 GETSET 的语意,C4 会检查已经过期的旧值是否仍然存储在 lock.foo 中。如果是的话,C4 会获得锁


? 如果另一个客户端,假如为 C5 ,比 C4 更快的通过 GETSET 操作获取


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


到锁,那么 C4 执行 GETSET 操作会被返回一个不过期的时间戳。C4 将会从第一个步骤重新开始。请注意:即使 C4 在将来几秒设置该键,这也不是问题。


为了使这种加锁算法更加的健壮,持有锁的客户端应该总是要检查是否超时,保证使用 DEL 释放锁之前不会过期,因为客户端故障的情况可能是复杂的,不止是崩溃,还会阻塞一段时间,阻止一些操作的执行,并且在阻塞恢复后尝试执行 DEL(此时,该 LOCK 已经被其他客户端所持有)


  • 【redis 限流】使用 Redis 实现一个恶意登录保护功能,限制 1 小时内每用户 Id 最多只能登录 5 次


【参考答案】


  1. 使用信号量进行控制并发,限制一项资源最多能够同时被多少个进程访问。

  2. 使用 List 数据结构,每条数据记录登陆的时间,登陆时,首先将 list 中的超过一小时的数据清理,然后将此次登陆时间加入到 list 中,如果 list 中的 size>=5 时,说明 list 中已经记录了该用户最近一小时登陆超过 5 次,如果是 1 小时外,否则该用户最近一小时未超过 5 次。


Mybatis




  • Mybatis 中 #和 $的区别是什么?


【参考答案】


${} 变量的替换阶段是在动态 SQL 解析阶段,而 #{ }变量的替换是在 DBMS 中。


这是 #{} 和 ${} 我们能看到的主要的区别,除此之外,还有以下区别:


? #方式能够很大程度防止 sql 注入。


? $方式无法防止 Sql 注入。


? $方式一般用于传入数据库对象,例如传入表名.


? 一般能用 #的就别用 $.


  • Mybatis 中 $可能带来的风险是什么?


【参考答案】


sql 注入


  • Mybatis DAO 接口为什么不需要实现类?


【参考答案】


mybatis 通过 JDK 的动态代理方式,在启动加载配置文件时,根据配置 mapper 的 xml 去生成 Dao 的实现。


session.getMapper()使用了代理,当调用一次此方法,都会产生一个代理 class 的 instance,看看这个代理 class 的实现.


  • mybatis 是用来做什么的?为什么不直接用 jdbc


【参考答案】


MyBatis 是一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。


相对于 JDBC,MyBatis 有以下优点:


? SQL 统一管理,便于维护和管理


? 能够对结果集进行映射


? 提供了缓存功能


  • mybatis 的缓存分几种?各有什么作用?


【参考答案】


MyBatis 中的缓存分为两种:一级缓存和二级缓存。使用过 MyBatis 的可能听到过这样一句话“一级缓存是 sqlSession 级别的,二级缓存是 mapper 级别的”


这也说明了,当使用同一个 sqlSession 时,查询到的数据可能是一级缓存;而当使用同一个 mapper 是,查询到的数据可能是二级缓存。


一级缓存默认开启的。二级缓存需要配置才开启,当我们的配置文件配置了 cacheEnabled=true 时,就会开启二级缓存


MQ




问题:


  • 1. 请列举出可能发生消息重复消费的场景

  • 2. 怎么解决消息的重复消费问题

  • 3. 业务端如何保证幂等性(提问要点:以上三个问题是连环炮,当候选人回答出一个问题后,引出下一个问题)


答案要点:


  1. 请列举出可能发生消息重复消费的场景


要点:重复消费在不可避免,1)网络出现抖动;2)消费端任务执行超时;3)消费端出现异常;


  1. 怎么解决消息的重复消费问题


要点:MQ 的服务端通常不保证消息不重复,由业务端来去重;主要是消费端业务逻辑保持幂等性;也可以由消息的服务端通 messageId 保证,但是通常不这么做


  1. 业务端如何保证幂等性


要点:1)使用唯一字段,建立唯一性索引;2)多版本控制;3)状态机幂等;4)保证数据唯一性的其它方式,比如通过 redis、zk 等方式


  • 消息队列消息推拉模式是什么意思,分别在什么场景下适用;


答案要点:


1)推:服务端主动,拉:消费端主动;


2)看消费端的消费能力,处理复杂时间长的适合拉模式,推模式实时性响应更好;其中推对于消费端要求更高,拉对于 MQ 服务端的堆积能力要求较高


  • 消息的可靠性怎么是保证的;


答案要点:


1)生产者端:通过同步发送消息,收到服务端的确认写入并存储,来保证一致性;rabbitmq 还支持先写入本地磁盘再发送给服务端


2)服务器端:通过把消息写入磁盘、数据库、分布式存储系统中,保证服务端的可靠性


3)消费端:通过消费端确认消费成功,才进行下一条消费;通过消息重试保证最终会被消费;


  • 顺序消息的实现原理


答案要点:


1)同一队列中保持有序;


2)相同业务 ID 的分配到相同的队列


  • 假设发送一条消息需要 2ms,如何使用最短的时间完成 300 条消息的发送


答案要点:


1)创建多个 producter,然后使用一个线程池进行分组发送;


ZooKeeper




  • Zookeeper 的据结构模型是什么?数据节点的类型有哪些


答案要点:


1)数据结构类似文件系统的树形结构;


2)节点类型有持久节点和临时节点

评论

发布
暂无评论
java整理,springboot2精髓百度云