Java 阿里云 - 面试总结

一、Java 多线程

1、线程池的原理,为什么要创建线程池?创建线程池的方式;

原理:

JAVA 线程池原理详解一

JAVA 线程池原理详解二

创建线程池的几种方式:

ThreadPoolExecutor、ThreadScheduledExecutor、ForkJoinPool

2、线程的生命周期,什么时候会出现僵死进程;

 僵死进程是指子进程退出时,父进程并未对其发出的SIGCHLD信号进行适当处理,导致子进程停留在僵死状态等待其父进程为其收尸,这个状态下的子进程就是僵死进程。

3、说说线程安全问题,什么是线程安全,如何实现线程安全;

自动检测线程安全 - 如果线程执行过程中不会产生共享资源的冲突,则线程安全。

线程不安全 - 如果有多个线程同时在操作主内存中的变量,则线程不安全

实现线程安全的三种方式

  1. 互斥同步

  临界区:syncronized、ReentrantLock

  信号量 semaphore

  互斥量 mutex

  1. 非阻塞同步

  CAS(Compare And Swap)

  1. 无同步方案

    可重入代码

  使用 Threadlocal 类来包装共享变量,做到每个线程有自己的 copy

线程本地存储

参考:https://blog.csdn.net/jackieeecheng/article/details/69779824

4、创建线程池有哪几个核心参数? 如何合理配置线程池的大小?

自动检测 1)核心参数

public ThreadPoolExecutor(int corePoolSize,  // 核心线程数量大小                    
int maximumPoolSize, // 线程池最大容纳线程数
long keepAliveTime, // 线程空闲后的存活时长
TimeUnit unit, //缓存异步任务的队列 //用来构造线程池里的worker线程  
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,//线程池任务满载后采取的任务拒绝策略
 RejectedExecutionHandler handler)
  1. 核心说明
  1. 当线程池中线程数量小于 corePoolSize 则创建线程,并处理请求。

  2. 当线程池中线程数量大于等于 corePoolSize 时,则把请求放入 workQueue 中, 随着线程池中的核心线程们不断执行任务,只要线程池中有空闲的核心线程,线程池就从 workQueue 中取任务并处理。

3 、当 workQueue 已存满,放不下新任务时则新建非核心线程入池,并处理请求直到线程数目达到 maximumPoolSize(最大线程数量设置值)。

  1. 如果线程池中线程数大于 maximumPoolSize 则使用 RejectedExecutionHandler 来进行任务拒绝处理。

参考:http://gudong.name/2017/05/03/thread-pool-intro.html

3) 线程池大小分配

线程池究竟设置多大要看你的线程池执行的什么任务了,CPU 密集型、IO 密集型、混合型,任务类型不同,设置的方式也不一样。

任务一般分为:CPU 密集型、IO 密集型、混合型,对于不同类型的任务需要分配不同大小的线程池。

3.1)CPU 密集型

尽量使用较小的线程池,一般 Cpu 核心数 +1

3.2)IO 密集型

方法一:可以使用较大的线程池,一般 CPU 核心数 * 2

方法二:(线程等待时间与线程 CPU 时间之比 + 1)* CPU 数目

3.3)混合型

可以将任务分为 CPU 密集型和 IO 密集型,然后分别使用不同的线程池去处理,按情况而定

参考:https://www.cnblogs.com/cherish010/p/8334952.html

5、volatile、ThreadLocal 的使用场景和原理;

自动检测 volatile 原理

volatile变量进行写操作时,JVM 会向处理器发送一条 Lock 前缀的指令,将这个变量所在缓存行的数据写会到系统内存。

Lock 前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。

自动检测 volatile 的适用场景

  1. 状态标志, 如:初始化或请求停机
  2. 一次性安全发布,如:单列模式
  3. 独立观察,如:定期更新某个值
  4. “volatile bean” 模式
  1. 开销较低的“读-写锁”策略,如:计数器

参考:https://blog.csdn.net/hgc0907/article/details/79664102

参考:https://www.ibm.com/developerworks/cn/java/j-jtp06197.html

ThreadLocal 原理

ThreadLocal是用来维护本线程的变量的,并不能解决共享变量的并发问题。ThreadLocal是各线程将值存入该线程的map中,
以ThreadLocal自身作为key,需要用时获得的是该线程之前存入的值。
如果存入的是共享变量,那取出的也是共享变量,并发问题还是存在的。

参考:https://www.jianshu.com/p/ee8c9dccc953

参考:https://blog.csdn.net/xlgen157387/article/details/78297568

自动检测 ThreadLocal 的适用场景

场景:数据库连接、Session 管理、

参考:https://www.jianshu.com/p/cadd53f063b9

6、ThreadLocal 什么时候会出现 OOM 的情况?为什么?

自动检测 ThreadLocal 变量是维护在 Thread 内部的,这样的话只要我们的线程不退出,对象的引用就会一直存在。当线程退出时,Thread 类会进行一些清理工作,其中就包含 ThreadLocalMap,Thread 调用 exit 方法如下:

自动检测 ThreadLocal 在没有线程池使用的情况下,正常情况下不会存在内存泄露,但是如果使用了线程池的话,就依赖于线程池的实现,如果线程池不销毁线程的话,那么就会存在内存泄露。

参考:https://blog.csdn.net/xlgen157387/article/details/78297568

7、synchronized、volatile 区别

自动检测 1) volatile 主要应用在多个线程对实例变量更改的场合,刷新主内存共享变量的值从而使得各个线程可以获得最新的值,线程读取变量的值需要从主存中读取;synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。另外,synchronized 还会创建一个内存屏障,内存屏障指令保证了所有 CPU 操作结果都会直接刷到主存中(即释放锁前),从而保证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作

  1. volatile 仅能使用在变量级别;synchronized 则可以使用在变量、方法、和类级别的。

volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞,比如多个线程争抢 synchronized 锁对象时,会出现阻塞。

  1. volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性,因为线程获得锁才能进入临界区,从而保证临界区中的所有语句全部得到执行。
  2. volatile 标记的变量不会被编译器优化,可以禁止进行指令重排;synchronized 标记的变量可以被编译器优化。

参考:https://blog.csdn.net/xiaoming100001/article/details/79781680

8、synchronized 锁粒度、模拟死锁场景;

自动检测 synchronized:具有原子性,有序性和可见性

参考:https://www.jianshu.com/p/cf57726e77f2

粒度:对象锁、类锁

死锁场景,参考:https://blog.csdn.net/u013925989/article/details/50208839

二、JVM 相关

1、JVM 内存模型,GC 机制和原理;

内存模型

自动检测 Jdk1.6 及之前:有永久代, 常量池在方法区

Jdk1.7:有永久代,但已经逐步“去永久代”,常量池在堆

Jdk1.8 及之后: 无永久代,常量池在元空间

2、GC 分哪两种,Minor GC 和 Full GC 有什么区别?什么时候会触发 Full GC?分别采用什么算法?

自动检测对象从新生代区域消失的过程,我们称之为 "minor GC"

对象从老年代区域消失的过程,我们称之为 "major GC"

Minor GC

清理整个 YouGen 的过程,eden 的清理,S0\S1 的清理都会由于 MinorGC Allocation Failure(YoungGen 区内存不足),而触发 minorGC

Major GC

OldGen 区内存不足,触发 Major GC

Full GC

Full GC 是清理整个堆空间—包括年轻代和永久代

Full GC 触发的场景

  1. System.gc
  2. promotion failed (年代晋升失败, 比如 eden 区的存活对象晋升到 S 区放不下,又尝试直接晋升到 Old 区又放不下,那么 Promotion Failed, 会触发 FullGC)
  3. CMS 的 Concurrent-Mode-Failure

由于 CMS 回收过程中主要分为四步: 1.CMS initial mark 2.CMS Concurrent mark 3.CMS remark 4.CMS Concurrent sweep。在 2 中 gc 线程与用户线程同时执行,那么用户线程依旧可能同时产生垃圾, 如果这个垃圾较多无法放入预留的空间就会产生 CMS-Mode-Failure, 切换为 SerialOld 单线程做 mark-sweep-compact。

  1. 新生代晋升的平均大小大于老年代的剩余空间 (为了避免新生代晋升到老年代失败)

当使用 G1,CMS 时,FullGC 发生的时候 是 Serial+SerialOld。

当使用 ParalOld 时,FullGC 发生的时候是 ParallNew +ParallOld.

3、JVM 里的有几种 classloader,为什么会有多种?

自动检测启动类加载器:负责加载 JRE 的核心类库,如 jre 目标下的 rt.jar,charsets.jar 等

扩展类加载器:负责加载 JRE 扩展目录 ext 中 JAR 类包

系统类加载器:负责加载 ClassPath 路径下的类包

用户自定义加载器:负责加载用户自定义路径下的类包

为什么会有多种:

  1. 分工,各自负责各自的区块
  2. 为了实现委托模型

4、什么是双亲委派机制?介绍一些运作过程,双亲委派模型的好处;

自动检测 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都不愿意干活,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的双亲委派模式。

动作过程

好处

自动检测沙箱安全机制:自己写的 String.class 类不会被加载,这样便可以防止核心 API 库被随意篡改

避免类的重复加载:当父亲已经加载了该类时,就没有必要子 ClassLoader 再加载一次

5、什么情况下我们需要破坏双亲委派模型;

自动检测 < 待补充 >

6、常见的 JVM 调优方法有哪些?可以具体到调整哪个参数,调成什么值?

自动检测调优工具

console,jProfile,VisualVM

Dump 线程详细信息:查看线程内部运行情况

死锁检查

查看堆内类、对象信息查看:数量、类型等

线程监控

线程信息监控:系统线程数量。

线程状态监控:各个线程都处在什么样的状态下

热点分析

CPU 热点:检查系统哪些方法占用的大量 CPU 时间

内存热点:检查哪些对象在系统中数量最大(一定时间内存活对象和销毁对象一起统计)

内存泄漏检查

< 待补充 >

7、JVM 虚拟机内存划分、类加载器、垃圾收集算法、垃圾收集器、class 文件结构是如何解析的;

自动检测 JVM 虚拟机内存划分(重复)

类加载器(重复)

垃圾收集算法:标记 - 清除算法、复制算法、标记 - 整理算法、分代收集算法

垃圾收集器: Serial 收集器、ParNew 收集器、Parallel Scavenge 收集器、Serial Old 收集器、Parallel Old 收集器、CMS 收集器、G1 收集器、Z 垃圾收集器

自动检测 class 文件结构是如何解析的

解悉过程:https://blog.csdn.net/sinat_38259539/article/details/78248454

三、Java 扩展篇

1、红黑树的实现原理和应用场景;

自动检测红黑树 (一棵自平衡的排序二叉树) 五大特性:

  1. 每个结点要么是红的,要么是黑的。
  2. 根结点是黑的。
  3. 每个叶结点,即空结点是黑的。
  4. 如果一个结点是红的,那么它的俩个儿子都是黑的。
  5. 对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。

场景

  1. 广泛用于 C++ 的 STL 中,map 和 set 都是用红黑树实现的.
  2. 著名的 linux 进程调度 Completely Fair Scheduler, 用红黑树管理进程控制块, 进程的虚拟内存区域都存储在一颗红黑树上, 每个虚拟地址区域都对应红黑树的一个节点, 左指针指向相邻的地址虚拟存储区域, 右指针指向相邻的高地址虚拟地址空间.
  3. IO 多路复用 epoll 的实现采用红黑树组织管理 sockfd,以支持快速的增删改查.
  4. ngnix 中, 用红黑树管理 timer, 因为红黑树是有序的, 可以很快的得到距离当前最小的定时器.
  5. java 中的 TreeSet,TreeMap

2、NIO 是什么?适用于何种场景?

自动检测(New IO)为所有的原始类型(boolean 类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。

特性:I/O 多路复用 + 非阻塞式 I/O

NIO 适用场景

服务器需要支持超大量的长时间连接。比如 10000 个连接以上,并且每个客户端并不会频繁地发送太多数据。例如总公司的一个中心服务器需要收集全国便利店各个收银机的交易信息,只需要少量线程按需处理维护的大量长期连接。

Jetty、Mina、Netty、ZooKeeper 等都是基于 NIO 方式实现。

NIO 技术概览

3、Java9 比 Java8 改进了什么;

自动检测 1)引入了模块系统,采用模块化系统的应用程序只需要这些应用程序所需的那部分 JDK 模块,而非是整个 JDK 框架了,减少了内存的开销。

  1. 引入了一个新的 package:java.net.http,里面提供了对 Http 访问很好的支持,不仅支持 Http1.1 而且还支持 HTTP2。
  2. 引入了 jshell 这个交互性工具,让 Java 也可以像脚本语言一样来运行,可以从控制台启动 jshell ,在 jshell 中直接输入表达式并查看其执行结果。
  3. 增加了 List.of()、Set.of()、Map.of()和 Map.ofEntries() 等工厂方法来创建不可变集合
  4. HTML5 风格的 Java 帮助文档
  5. 多版本兼容 JAR 功能,能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。
  6. 统一 JVM 日志

可以使用新的命令行选项 -Xlog 来控制 JVM 上 所有组件的日志记录。该日志记录系统可以设置输出的日志消息的标签、级别、修饰符和输出目标等。

  1. 垃圾收集机制

Java 9 移除了在 Java 8 中 被废弃的垃圾回收器配置组合,同时把 G1 设为默认的垃圾回收器实现. 因为相对于 Parallel 来说,G1 会在应用线程上做更多的事情,而 Parallel 几乎没有在应用线程上做任何事情,它基本上完全依赖 GC 线程完成所有的内存管理。这意味着切换到 G1 将会为应用线程带来额外的工作,从而直接影响到应用的性能

  1. I/O 流新特性

java.io.InputStream 中增加了新的方法来读取和复制 InputStream 中包含的数据。

readAllBytes:读取 InputStream 中的所有剩余字节。

readNBytes: 从 InputStream 中读取指定数量的字节到数组中。

transferTo:读取 InputStream 中的全部字节并写入到指定的 OutputStream 中 。

参考:

java8 新特性

java9 新特性

4、HashMap 内部的数据结构是什么?底层是怎么实现的?

自动检测 HashMap 内部结构

jdk8 以前:数组 + 链表

jdk8 以后:数组 + 链表 (当链表长度到 8 时,转化为红黑树)

在并发的情况,发生扩容时,可能会产生循环链表,在执行 get 的时候,会触发死循环,引起 CPU 的 100% 问题,所以一定要避免在并发环境下使用 HashMap。

5、延伸考察 ConcurrentHashMap 与 HashMap、HashTable 等,考察对技术细节的深入了解程度;

HashMap、HashTable、ConcurrentHashMap 的原理与区别

老生常谈,HashMap 的死循环

ConcurrentHashMap 在 jdk1.8 中的改进

谈谈 ConcurrentHashMap1.7 和 1.8 的不同实现

深入分析 ConcurrentHashMap1.8 的扩容实现

深入浅出 ConcurrentHashMap1.8

ConcurrentHashMap 的红黑树实现分析

6、说说反射的用途及实现,反射是不是很慢,我们在项目中是否要避免使用反射;

自动检测一、用途

反射被广泛地用于那些需要在运行时检测或修改程序行为的程序中。

二、实现方式

Foo foo = new Foo();

第一种:通过 Object 类的 getClass 方法

Class cla = foo.getClass();

第二种:通过对象实例方法获取对象

Class cla = foo.class;

第三种:通过 Class.forName 方式

Class cla = Class.forName("xx.xx.Foo");

三、缺点

  1. 影响性能

反射包括了一些动态类型,所以 JVM 无法对这些代码进行优化。因此,反射操作的效

率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程

序中使用反射。

  1. 安全限制

使用反射技术要求程序必须在一个没有安全限制的环境中运行。

  1. 内部暴露

由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方 法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。 反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

7、说说自定义注解的场景及实现;

自动检测利用自定义注解, 结合 SpringAOP 可以完成权限控制、日志记录、统一异常处理、数字签名、数据加解密等功能。

实现场景(API 接口数据加解密)

  1. 自定义一个注解,在需要加解密的方法上添加该注解
  2. 配置 SringAOP 环绕通知
  3. 截获方法入参并进行解密
  4. 截获方法返回值并进行加密

8、List 和 Map 区别

自动检测一、概述

List 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合,

List 中存储的数据是有顺序,并且允许重复,值允许有多个 null;

Map 中存储的数据是没有顺序的,键不能重复,值是可以有重复的,key 最多有一个 null。

二、明细

List

  1. 可以允许重复的对象。
  2. 可以插入多个 null 元素。
  3. 是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
  4. 常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。

Map

  1. Map 不是 collection 的子接口或者实现类。Map 是一个接口。
  2. Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
  3. TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
  4. Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
  5. Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap 最常用)

Set(问题扩展)

  1. 不允许重复对象
  2. 无序容器,你无法保证每个元素的存储顺序,TreeSet 通过 Comparator 或 Comparable 维护了一个排序顺序。
  3. 只允许一个 null 元素
  4. Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare()和 compareTo() 的定义进行排序的有序容器。

三、场景(问题扩展)

  1. 如果你经常会使用索引来对容器中的元素进行访问,那么 List 是你的正确的选择。如果你已经知道索引了的话,那么 List 的实现类比如 ArrayList 可以提供更快速的访问, 如果经常添加删除元素的,那么肯定要选择 LinkedList。
  2. 如果你想容器中的元素能够按照它们插入的次序进行有序存储,那么还是 List,因为 List 是一个有序容器,它按照插入顺序进行存储。
  3. 如果你想保证插入元素的唯一性,也就是你不想有重复值的出现,那么可以选择一个 Set 的实现类,比如 HashSet、LinkedHashSet 或者 TreeSet。所有 Set 的实现类都遵循了统一约束比如唯一性,而且还提供了额外的特性比如 TreeSet 还是一个 SortedSet,所有存储于 TreeSet 中的元素可以使用 Java 里的 Comparator 或者 Comparable 进行排序。LinkedHashSet 也按照元素的插入顺序对它们进行存储。
  4. 如果你以键和值的形式进行数据存储那么 Map 是你正确的选择。你可以根据你的后续需要从 Hashtable、HashMap、TreeMap 中进行选择。

参考:List、Set、Map 的区别

9、Arraylist 与 LinkedList 区别,ArrayList 与 Vector 区别;

自动检测 1)数据结构

Vector、ArrayList 内部使用数组,而 LinkedList 内部使用双向链表,由数组和链表的特性知:

LinkedList 适合指定位置插入、删除操作,不适合查找;

ArrayList、Vector 适合查找,不适合指定位置的插入删除操作。

但是 ArrayList 越靠近尾部的元素进行增删时,其实效率比 LinkedList 要高

2) 线程安全

Vector 线程安全,ArrayList、LinkedList 线程不安全。

  1. 空间

ArrayList 在元素填满容器时会自动扩充容器大小的 50%,而 Vector 则是 100%,因此 ArrayList 更节省空间。

参考:ArrayList 和 LinkedList 内部实现、区别、使用场景

四、Spring 相关

1、Spring AOP 的实现原理和场景;

自动检测 AOP(Aspect Orient Programming),作为面向对象编程的一种补充,广泛应用于处理一些具有横切性质的系统级服务。

一、场景

事务管理、安全检查、权限控制、数据校验、缓存、对象池管理等

二、实现技术

AOP(这里的 AOP 指的是面向切面编程思想,而不是 Spring AOP)主要的的实现技术主要有 Spring AOP 和 AspectJ。

  1. AspectJ 的底层技术。

    AspectJ 的底层技术是静态代理,即用一种 AspectJ 支持的特定语言编写切面,通过一个命令来编译,生成一个新的代理类,该代理类增强了业务类,这是在编译时增强,相对于下面说的运行时增强,编译时增强的性能更好。

  2. Spring AOP

    Spring AOP 采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,Spring AOP 提供了对 JDK 动态代理的支持以及 CGLib 的支持。

    JDK 动态代理只能为接口创建动态代理实例,而不能对类创建动态代理。需要获得被目标类的接口信息(应用 Java 的反射技术),生成一个实现了代理接口的动态代理类(字节码),再通过反射机制获得动态代理类的构造函数,利用构造函数生成动态代理类的实例对象,在调用具体方法前调用 invokeHandler 方法来处理。

    CGLib 动态代理需要依赖 asm 包,把被代理对象类的 class 文件加载进来,修改其字节码生成子类。

    但是 Spring AOP 基于注解配置的情况下,需要依赖于 AspectJ 包的标准注解。

2、Spring bean 的作用域和生命周期;

作用域

生命周期

3、Spring Boot 比 Spring 做了哪些改进?

自动检测 1)Spring Boot 可以建立独立的 Spring 应用程序;

  1. 内嵌了如 Tomcat,Jetty 和 Undertow 这样的容器,也就是说可以直接跑起来,用不着再做部署工作了;
  2. 无需再像 Spring 那样搞一堆繁琐的 xml 文件的配置;
  3. 可以自动配置 Spring。SpringBoot 将原有的 XML 配置改为 Java 配置,将 bean 注入改为使用注解注入的方式 (@Autowire),并将多个 xml、properties 配置浓缩在一个 appliaction.yml 配置文件中。
  4. 提供了一些现有的功能,如量度工具,表单数据验证以及一些外部配置这样的一些第三方功能;
  5. 整合常用依赖(开发库,例如 spring-webmvc、jackson-json、validation-api 和 tomcat 等),提供的 POM 可以简化 Maven 的配置。当我们引入核心依赖时,SpringBoot 会自引入其他依赖。

Spring 5 比 Spring4 做了哪些改进;

【官网说明 】

自动检测 Spring 4.x 新特性

  1. 泛型限定式依赖注入
  2. 核心容器的改进
  3. web 开发增强
  4. 集成 Bean Validation 1.1(JSR-349)到 SpringMVC
  5. Groovy Bean 定义 DSL
  6. 更好的 Java 泛型操作 API
  7. JSR310 日期 API 的支持
  8. 注解、脚本、任务、MVC 等其他特性改进

Spring 5.x 新特性

  1. JDK8 的增强
  2. 核心容器的改进
  3. 新的 SpringWebFlux 模块
  4. 测试方面的改进

参考:

《Spring5 官方文档》新功能

Spring4 新特性——泛型限定式依赖注入

Spring4 新特性——核心容器的其他改进

Spring4 新特性——Web 开发的增强

Spring4 新特性——集成 Bean Validation 1.1(JSR-349) 到 SpringMVC

Spring4 新特性——Groovy Bean 定义 DSL

Spring4 新特性——更好的 Java 泛型操作 API

Spring4 新特性——JSR310 日期 API 的支持

Spring4 新特性——注解、脚本、任务、MVC 等其他特性改进

如何自定义一个 Spring Boot Starter?

《自定义 spring boot starter 三部曲之一:准备工作》

《自定义 spring boot starter 三部曲之二:实战开发》

《自定义 spring boot starter 三部曲之三:源码分析 spring.factories 加载过程》

Spring IOC 是什么?优点是什么?

自动检测 IoC 文英全称 Inversion of Control,即控制反转,我么可以这么理解 IoC 容器:“把某些业务对象的的控制权交给一个平台或者框架来同一管理,这个同一管理的平台可以称为 IoC 容器。”

ioc 的思想最核心的地方在于,资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处:

  1. 资源集中管理,实现资源的可配置和易管理
  2. 降低了使用资源双方的依赖程度,也就是我们说的耦合度

Spring IOC 原理解读 面试必读

SpringMVC

动态代理

反射

AOP 原理

Spring 事务;

自动检测一、spring 事务

什么是事务: 事务逻辑上的一组操作, 组成这组操作的各个逻辑单元, 要么一起成功, 要么一起失败.

二、事务特性(4 种):

原子性 (atomicity): 强调事务的不可分割.

一致性 (consistency): 事务的执行的前后数据的完整性保持一致.

隔离性 (isolation): 一个事务执行的过程中, 不应该受到其他事务的干扰

持久性(durability) : 事务一旦结束, 数据就持久到数据库

如果不考虑隔离性引发安全性问题:

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

不可重复读 : 一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致.

虚幻读 : 一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致.

三、解决读问题: 设置事务隔离级别(5 种)

DEFAULT 这是一个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别.

未提交读(read uncommited) : 脏读,不可重复读,虚读都有可能发生

已提交读 (read commited): 避免脏读。但是不可重复读和虚读有可能发生

可重复读 (repeatable read) : 避免脏读和不可重复读. 但是虚读有可能发生.

串行化的 (serializable) : 避免以上所有读问题.

Mysql 默认: 可重复读

Oracle 默认: 读已提交

四、事务的传播行为

PROPAGION_XXX : 事务的传播行为

  • 保证同一个事务中

PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个 (默认)

PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务

PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常

  • 保证没有在同一个事务中

PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务

PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务

PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常

PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行

五、中间件篇

Dubbo 完整的一次调用链路介绍;

参考:http://dubbo.apache.org/zh-cn/docs/dev/design.html

Dubbo 支持几种负载均衡策略?

自动检测 1)Random LoadBalance

随机,按权重设置随机概率。

在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

  1. RoundRobin LoadBalance

轮询,按公约后的权重设置轮询比率。

存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

  1. LeastActive LoadBalance

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。

使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

  1. ConsistentHash LoadBalance

一致性 Hash,相同参数的请求总是发到同一提供者。

当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

算法参见:http://en.wikipedia.org/wiki/Consistent_hashing

缺省只对第一个参数 Hash,如果要修改,请配置

<dubbo:parameter key="hash.arguments" value="0,1" />

缺省用 160 份虚拟节点,如果要修改,请配置

<dubbo:parameter key="hash.nodes" value="320" />

参考:http://dubbo.apache.org/zh-cn/docs/user/demos/loadbalance.html

原码分析:http://www.cnblogs.com/wyq178/p/9822731.html

Dubbo Provider 服务提供者要控制执行并发请求上限,具体怎么做?

服务端并发限流:executes

客户端并发限流:actives

自动检测样例 1

限制 com.foo.BarService 的每个方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个:

<dubbo:service interface="com.foo.BarService" executes="10" />

样例 2

限制 com.foo.BarService 的 sayHello 方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个:

<dubbo:service interface="com.foo.BarService">

">

<dubbo:method name="sayHello" executes="10" />

/dubbo:service

样例 3

限制 com.foo.BarService 的每个方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个:

<dubbo:service interface="com.foo.BarService" actives="10" />

<dubbo:reference interface="com.foo.BarService" actives="10" />

样例 4

限制 com.foo.BarService 的 sayHello 方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个:

<dubbo:service interface="com.foo.BarService">

">

<dubbo:method name="sayHello" actives="10" />

/dubbo:service

<dubbo:reference interface="com.foo.BarService">

">

<dubbo:method name="sayHello" actives="10" />

/dubbo:service

参考:http://dubbo.apache.org/zh-cn/docs/user/demos/concurrency-control.html

Dubbo 启动的时候支持几种配置方式?

自动检测 XML 配置

http://dubbo.apache.org/zh-cn/docs/user/configuration/xml.html

属性配置

http://dubbo.apache.org/zh-cn/docs/user/configuration/properties.html

API 配置

http://dubbo.apache.org/zh-cn/docs/user/configuration/api.html

注解配置

http://dubbo.apache.org/zh-cn/docs/user/configuration/annotation.html

了解几种消息中间件产品?各产品的优缺点介绍;

各种消息队列对比.pdf1MB

消息中间件如何保证消息的一致性

自动检测 (1) 主动方应用先把消息发送给消息中间件,消息状态标记为待确认;

(2) 消息中间件收到消息之后,把消息持久化到消息存储中,但并不向被动方应用投递消息;

(3) 消息中间件返回消息持久化结果(成功,或者失效),主动方应用根据返回结果进行判断如何处理业务操作处理;

①失败:放弃业务操作处理,结束(必须向上层返回失败结果)

②成功:执行业务操作处理

(4) 业务操作完成后,把业务操作结果(成功 / 失败)发送给消息中间件;

(5) 消息中间件收到业务操作结果后,根据结果进行处理;

①失败:删除消息存储中的消息,结束;

②成功:更新消息存储中的消息状态为·待发送(可发送)·,紧接着执行消息投递;

(6) 前面的正向流程都成功后,向被动方应用投递消息;

如何进行消息的重试机制?

参考:Rocket 重试机制,消息模式,刷盘方式

Spring Cloud 熔断机制介绍;

自动检测在 Spring Cloud 框架里,熔断机制通过 Hystrix 实现。Hystrix 会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是 5 秒内 20 次调用失败,就会启动熔断机制。熔断机制的注解是 @HystrixCommand,Hystrix 会找有这个注解的方法,并将这类方法关联到和熔断器连在一起的代理上。当前,@HystrixCommand 仅当类的注解为 @Service 或 @Component 时才会发挥作用。

参考:http://www.cnblogs.com/lvgg/p/7843809.html

Spring Cloud 对比下 Dubbo,什么场景下该使用 Spring Cloud?

自动检测两者所解决的问题域不一样:Dubbo 的定位始终是一款 RPC 框架,而 Spring Cloud 的目的是微服务架构下的一站式解决方案。

Spring Cloud 抛弃了 Dubbo 的 RPC 通信,采用的是基于 HTTP 的 REST 方式。

严格来说,这两种方式各有优劣。虽然在一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生 RPC 带来的问题。而且 REST 相比 RPC 更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适。

更多对比:http://blog.51cto.com/13954634/2296010

六、数据库篇

锁机制介绍:行锁、表锁、排他锁、共享锁;

乐观锁的业务场景及实现方式;

事务介绍,分布式事物的理解,常见的解决方案有哪些,什么是两阶段提交、三阶段提交;

MySQL 记录 binlog 的方式主要包括三种模式?每种模式的优缺点是什么?

自动检测 mysql 复制主要有三种方式:基于 SQL 语句的复制 (statement-based replication, SBR),基于行的复制 (row-based replication, RBR),混合模式复制 (mixed-based replication, MBR)。对应的,binlog 的格式也有三种:STATEMENT,ROW,MIXED。

① STATEMENT 模式(SBR)

每一条会修改数据的 sql 语句会记录到 binlog 中。优点是并不需要记录每一条 sql 语句和每一行的数据变化,减少了 binlog 日志量,节约 IO,提高性能。缺点是在某些情况下会导致 master-slave 中的数据不一致 (如 sleep() 函数, last_insert_id(),以及 user-defined functions(udf)等会出现问题)

② ROW 模式(RBR)

不记录每条 sql 语句的上下文信息,仅需记录哪条数据被修改了,修改成什么样了。而且不会出现某些特定情况下的存储过程、或 function、或 trigger 的调用和触发无法被正确复制的问题。缺点是会产生大量的日志,尤其是 alter table 的时候会让日志暴涨。

③ MIXED 模式(MBR)

以上两种模式的混合使用,一般的复制使用 STATEMENT 模式保存 binlog,对于 STATEMENT 模式无法复制的操作使用 ROW 模式保存 binlog,MySQL 会根据执行的 SQL 语句选择日志保存方式。

MySQL 锁,悲观锁、乐观锁、排它锁、共享锁、表级锁、行级锁;

自动检测乐观锁

用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将 version 字段的值一同读出,数据每更新一次,对此 version 值加 1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的 version 值进行比对,如果数据库表当前版本号与第一次取出来的 version 值相等,则予以更新,否则认为是过期数据。

悲观锁

在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟 java 中 synchronized 很相似,共享锁(读锁)和排它锁(写锁)是悲观锁的不同的实现

共享锁(读锁)

共享锁又叫做读锁,所有的事务只能对其进行读操作不能写操作,加上共享锁后在事务结束之前其他事务只能再加共享锁,除此之外其他任何类型的锁都不能再加了。

排它锁(写锁)

若某个事物对某一行加上了排他锁,只能这个事务对其进行读写,在此事务结束之前,其他事务不能对其进行加任何锁,其他进程可以读取, 不能进行写操作,需等待其释放。

表级锁

innodb 的行锁是在有索引的情况下, 没有索引的表是锁定全表的

行级锁

行锁又分共享锁和排他锁, 由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。

注意:行级锁都是基于索引的,如果一条 SQL 语句用不到索引是不会使用行级锁的,会使用表级锁。

更多参考:https://blog.csdn.net/yzj5208/article/details/81288633

分布式事务的原理 2 阶段提交,同步异步阻塞非阻塞;

自动检测

数据库事务隔离级别,MySQL 默认的隔离级别

自动检测 Mysql 默认隔离级别:Repeatable Read

Spring 如何实现事务

参考:spring 事务管理 (详解和实例)

自动检测 Spring 事物四种实现方式:

基于编程式事务管理实现

基于 TransactionProxyFactoryBean 的声明式事务管理

基于 AspectJ 的 XML 声明式事务管理

基于注解的声明式事务管理

参考:https://blog.csdn.net/zhuxinquan61/article/details/71075051

JDBC 如何实现事务

在 JDBC 中处理事务,都是通过 Connection 完成的。

同一事务中所有的操作,都在使用同一个 Connection 对象。

①JDBC 中的事务

Connection 的三个方法与事务有关:

  • setAutoCommit(boolean): 设置是否为自动提交事务,如果 true(默认值为 true)表示自动提交,也就是每条执行的 SQL 语句都是一个单独的事务,如果设置为 false,那么相当于开启了事务了;con.setAutoCommit(false) 表示开启事务。
  • commit():提交结束事务。
  • rollback():回滚结束事务。

示例代码

自动检测 try{

 con.setAutoCommit(false);//开启事务

 ......

 con.commit();//try的最后提交事务  

} catch() {

con.rollback();//回滚事务

}

嵌套事务实现

自动检测 spring 事务嵌套: 外层事务 TraB, 内层事务 TraA、TraC

场景 1:

TraA、TraC @Transactional(默认 REQUIRED)

TraB:

traA.update(order1); (traA.update throw new RuntimeException();)

traC.update(order2);

结果: 内外层事务全部回滚;

场景 2:

TraA、TraC @Transactional(默认 REQUIRED)

TraB:

traA.update(order1); (traA.update throw new RuntimeException();try catch traC.update)

traC.update(order2);

结果: 内外层事务全部不回滚,traA 中 try catch 后的事务提交;

场景 3:

TraA、TraC @Transactional(默认 REQUIRED)

TraB: try{(traA.update throw new RuntimeException();

         在外层TraB try catch TraA)

		  traA.update(order1);

		  }catch(){}

	   traC.update(order2);

结果: 内外层事务全部回滚, 内层的异常抛出到外层捕获也会回滚;

场景 4:

TraA @Transactional(propagation=Propagation.REQUIRES_NEW)、TraC @Transactional(默认 REQUIRED)

TraB:

	   traA.update(order1);	(traA.update throw new RuntimeException();)

	   traC.update(order2);

结果: 内层事务回滚, 外层事务继续提交;

场景 5:

TraA @Transactional(propagation=Propagation.REQUIRES_NEW)、TraC @Transactional(默认 REQUIRED)

TraB:  

	   traA.update(order1); (traA.update throw new RuntimeException();try catch traC.update)			 

	   traC.update(order2);

结果: 内外层事务全部不回滚,traA 中 try catch 后的事务提交, 达到与场景 2 的同样效果;

场景 6:

TraA @Transactional(propagation=Propagation.REQUIRES_NEW)、TraC @Transactional(默认 REQUIRED)

TraB:  

	   try{					(traA.update throw new RuntimeException();在外层TraB try catch TraA)

		 traA.update(order1);

	   } catch

	   traC.update(order2);

结果: 内层事务回滚, 外层事务不回滚;

更多参考:https://blog.csdn.net/yangchangyong0/article/details/51960143

分布式事务实现;

自动检测 1)基于 XA 协议的两阶段提交(2PC)

XA 规范主要 定义了 (全局) 事务管理器 (Transaction Manager) 和 (局部) 资源管理器 (Resource Manager) 之间的接口。

  1. 两阶段提交

事务的提交分为两个阶段:

预提交阶段 (Pre-Commit Phase)

决策后阶段(Post-Decision Phase)

  1. 补偿事务(TCC)

针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段

Try 阶段主要是对业务系统做检测及资源预留

Confirm 阶段主要是对业务系统做确认提交,Try 阶段执行成功并开始执行 Confirm 阶段时,默认 Confirm 阶段是不会出错的。即:只要 Try 成功,Confirm 一定成功。

Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放

  1. 本地消息表(MQ 异步确保)

其基本的设计思想是将远程分布式事务拆分成一系列的本地事务。

  1. MQ 事务消息

有一些第三方的 MQ 是支持事务消息的,比如 RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的 MQ 都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。

  1. Sagas 事务模型

该模型其核心思想就是拆分分布式系统中的长事务为多个短事务,或者叫多个本地事务,然后由 Sagas 工作流引擎负责协调,如果整个流程正常结束,那么就算是业务成功完成,如果在这过程中实现失败,那么 Sagas 工作流引擎就会以相反的顺序调用补偿操作,重新进行业务回滚。

  1. 其他补偿方式

加入详细日志记录的,一旦系统内部引发类似致命异常,会有邮件通知。同时,后台会有定时任务扫描和分析此类日志,检查出这种特殊的情况,会尝试通过程序来补偿并邮件通知相关人员。

在某些特殊的情况下,还会有 "人工补偿" 的,这也是最后一道屏障。

参考:https://www.javazhiyin.com/573.html

SQL 的整个解析、执行过程原理、SQL 行转列;

整体架构

SQL 解析

行转列、列转行

自动检测参考:https://blog.csdn.net/jx_870915876/article/details/52403472

七、Redis

Redis 为什么这么快?

自动检测 (1) 绝大部分请求是纯粹的内存操作(非常快速)

(2) 采用单线程, 避免了不必要的上下文切换和竞争条件

(3) 非阻塞 IO - IO 多路复用

redis 采用多线程会有哪些问题?

自动检测 1)单线程的问题

无法发挥多核 CPU 性能,单进程单线程只能跑满一个 CPU 核

可以通过在单机开多个 Redis 实例来完善

可以通过数据分片来增加吞吐量,问题(不支持批量操作、扩缩容复杂等)

  1. 多线程的问题

多线程处理可能涉及到锁

多线程处理会涉及到线程切换而消耗 CPU

参考:阿里云 Redis 多线程性能提升思路解析

Redis 支持哪几种数据结构;

自动检测 String 、List 、Set 、Hash 、ZSet

Redis 跳跃表的问题;

自动检测 Redis 只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群结点中用作内部数据结构

参考:https://blog.csdn.net/idwtwt/article/details/80233859

Redis 是单进程单线程的,如何能够高并发?

自动检测采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗)

Redis 如何使用 Redis 实现分布式锁?

参考:Redis 分布式锁的正确实现方式

Redis 分布式锁操作的原子性,Redis 内部是如何实现的?

自动检测 setnx

Incrby\Decrby