文章目录
- 一、java 基础
- 1、JDK 和 JRE的区别
- 2、= = 和equals的区别
- 3、String、StringBuffer、StringBuilder
- 4、String str = “a”、 new String(“a”)一样吗?
- 5、ArrayList 和 LinkedList的区别?
- 6、HashMap的原理与实现
- 7、HashMap中的put方法执行过程
- 8、ConcurrentHashMap工作原理
- 9、SimpleDateFormat是线程安全的吗?
- 10、java.util.Date 与 java.sql.Date 有什么区别?
- 11、对象被置为null,垃圾回收器会立即释放占用的内存吗?
- 12、java数据结构有哪些
- 13、java中异常分为几种
- 14、Error与Exception的区别
- 15、switch 是否能作用在 byte 上,是否能作用在 long 上
- 16、哪些集合是线程安全的?
- 17、synchronized原理
- 18、volatile有什么用
- 19、synchronized关键字可以实现什么类型的锁?
- 20、怎么创建不可修改的集合
- 21、sleep和wait的区别
- 22、jvm内存结构
- 23、运行时数据区
- 24、堆结构(jdk1.8)
- 25、java垃圾回收算法
- 26、垃圾回收器
- 27、GC Roots 可以是哪些?
- 28、引用类型有哪些
- 29、java的IO流分为几种?
- 30、BIO、NIO、AIO
- 31、NIO直接缓冲区与非直接缓冲区
- 32、NIO内存映射到缓存
- 33、序列化和反序列化
- 二、网络基础
- 三、spring
- 四、微服务
- 五、mysql
- 六、redis
- 七、多线程
- 八、Activiti流程引擎
- 九、设计模式
- 十、ES
- 十一、VUE
- 十二、综合性问题
一、java 基础
1、JDK 和 JRE的区别
2、= = 和equals的区别
= = 比基本类型:比较值,
= = 比引用类型:比较引用。
equals本质上就是= =,String、integer等重写了equals方法,把它变成了值比较。
3、String、StringBuffer、StringBuilder
String 每次操作都会生成新的String对象
StringBuffer 线程安全的,所有公开方法都是 synchronized 修饰的
StringBuilder 非线程安全的,性能更高一些
4、String str = “a”、 new String(“a”)一样吗?
前者会放到常量池,后者放到堆内存
5、ArrayList 和 LinkedList的区别?
ArraList 是基于数组实现的线性表,尾部插入和数据访问效率高
LinkedList是双向链表,中间插入和头部插入效率高,查询效率低
6、HashMap的原理与实现
- 数组:HashMap底层由一个数组组成,数组的每个元素称为一个桶(存一个或多个Entry)。
- 链表:多个键值对映射到同一个桶,会发生哈希冲突。哈希冲突后使用链表将冲突的键值对连接起来。这就是所谓的“数组 + 链表”的散列数据结构。
- 红黑树:Java 1.8 开始,当链表的长度超过一定阈值(默认为 8)时,链表会转换为红黑树。红黑树是一种自平衡的二叉查找树,能够提高 HashMap 的查询效率。
6.1、容量与扩容
HashMap 的默认大小是16(16是为了确保算出来的值足够随机),
当容量到达阈值的时候会触发扩容,阈值=容量加载因子,160.75=12。
每次扩容是之前容量的2倍。
6.2、扩容机制
JDK 1.7:空参构造函数,内部是空数组,第一次put初始化数组。
JDK1.8 : 空参构造函数,内部是null,第一次put初始化数组。
7、HashMap中的put方法执行过程
1、判断键值对数组是否为空(null)或者length=0,是的话就执行resize()方法进行扩容。
2、不是就根据键值key计算hash值得到插入的数组索引i。
3、判断索引i这个位置是否是null,如果是,就新建节点。如果不是,判断首个元素是否和key一样,一样就直接覆盖。
4、如果位置I的首个元素和key不一样,判断是否是红黑树,如果是红黑树,直接在树中插入键值对。
5、如果不是红黑树,开始遍历链表,判断链表长度是否大于8,如果大于8就转成红黑树,在树中执行插入操作,如果不是大于8,就在链表中执行插入;在遍历过程中判断key是否存在,存在就直接覆盖对应的value值。
6、插入成功后,就需要判断实际存在的键值对数量size是否超过了最大容量threshold,如果超过了,执行resize方法进行扩容。
8、ConcurrentHashMap工作原理
Jdk1.7
JDK1.8
JDK1.7,结构为segment+数组+链表,通过继承 ReentrantLock加分段锁。每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的。
JDK1.8,放弃了锁分段的概念,使用Node+CAS+Synchronized的方式实现并发,的结构为数组+链表+红黑树
9、SimpleDateFormat是线程安全的吗?
DateFormat的所有实现都是线程不安全的。
10、java.util.Date 与 java.sql.Date 有什么区别?
java.util.Date表示一个日期和时间,通常用于表示一个时间戳。
java.sql.Date是java.util.Date的子类,它只表示日期,不包含时间。
11、对象被置为null,垃圾回收器会立即释放占用的内存吗?
不会,这个对象可能被回收
12、java数据结构有哪些
线性表、链表、栈、队列、map、树
13、java中异常分为几种
编译时异常、运行时异常
编译时异常:空指针异常、数值转换异常、数组越界异常等
14、Error与Exception的区别
- Error和Exception都继承了Throwable类 。
异常可以通过程序捕捉。 - Error是系统错误,通常比较严重。
15、switch 是否能作用在 byte 上,是否能作用在 long 上
switch可以作用于char byte short int及它们对应的包装类型、String上、枚举上,
switch不可作用于long double float boolean及他们的包装类型。
15.1、switch 为什么不能作用于long上
1、类型安全考虑:long 类型的值范围较大,大量的 case 标签,使得代码难以阅读和维护。
2、编译器优化:编译器在编译阶段会对 switch 语句进行优化。当发现 switch 语句中的变量类型为 long 时,编译器会将其转换为 int 类型,然后在 switch 语句中使用 int 类型进行比较。这样的优化虽然提高了代码的可读性,但同时也限制了 long 类型的使用场景。
3、性能考虑:允许 switch 语句作用于 long 类型可能会带来性能问题。因为 long 类型的值较大,比较操作需要消耗更多的计算资源。而在很多情况下,使用 int 类型进行比较会更加高效。因此,禁止 switch 语句作用于 long 类型有助于提高程序的性能。
16、哪些集合是线程安全的?
1、Vector (vector比arraylist多个synchronized同步,因此系统开销比arraylist大)
2、hashTable
3、ConcurrentHashMap
17、synchronized原理
synchronized主要有三种使用方式:修饰普通同步方法、修饰静态同步方法、修饰同步方法块。
锁的升级过程:
1、无锁,不锁住资源,多个线程只有一个能修改资源成功,其他线程会重试;
2、偏向锁,同一个线程获取同步资源时,没有别人竞争时,去掉所有同步操作,相当于没锁;
3、轻量级锁,多个线程抢夺同步资源时,没有获得锁的线程使用CAS自旋等待锁的释放;
4、重量级锁,多个线程抢夺同步资源时,使用操作系统的互斥量进行同步,没有获得锁的线程阻塞等待唤醒
synchronized关键字三大特性:
原子性(指令不可分割)、
可见性(其他线程可看见修改)、
有序性(代码是顺序执行)
volatile关键字只能保证可见性和有序性,不能保证原子性,也称为是轻量级的synchronized。
18、volatile有什么用
- 可见性:当一个线程修改了 volatile 变量的值,其他线程能够立即看到修改后的值。
(这是因为 volatile 变量的读写操作不会被缓存在寄存器或其他处理器内部的缓存中,而是直接从内存中读取或写入。因此,使用 volatile 变量可以确保多线程之间的数据同步。) - 禁止指令重排序:编译器或处理器可能会对代码进行优化,对指令进行重新排序,以提高执行效率。volatile 变量的读写操作不会被重排序,这样可以确保指令按照代码中的顺序执行,避免了因指令重排序导致的数据不一致问题。
- 原子性:volatile 变量在读写操作时,具有原子性。这意味着,多个线程同时访问 volatile 变量时,不会发生冲突。虽然 volatile 变量不保证复合操作的原子性(如自增或复合赋值操作),但简单的读写操作具有原子性。
19、synchronized关键字可以实现什么类型的锁?
悲观锁:每次访问共享资源时都会上锁。
非公平锁:线程获取锁的顺序并不一定是按照线程阻塞的顺序。
可重入锁:已经获取锁的线程可以再次获取锁。
独占锁或者排他锁:该锁只能被一个线程所持有,其他线程均被阻塞
20、怎么创建不可修改的集合
List<String> list = new ArrayList<>();
list.add("1");
Collection<String> listC = Collections.unmodifiableCollection(list); // listC不可修改
listC.add("2"); // listC 报错
21、sleep和wait的区别
- Sleep是线程内停顿,不释放锁,自动唤醒,sleep 是 Thread 类的静态本地方法,
- Wait要释放锁,必须放在 synchronized 块里面,需要 notify/ notifyAll 进行唤醒,wait 是 Object 类的本地方法。Wait可能还是有机会重新竞争到锁继续执行的。
22、jvm内存结构
1、程序计数器:指向当前线程正在执行的字节码指令的行号。
2、本地方法栈:Native方法。
3、栈(虚拟机栈):每个Java方法在被调用的时候都会创建一个栈帧,用来存储局部变量表(八大原始类型、封装类型)、作数栈、动态链接、方法出口。
4、堆:对象实例、数组
5、方法区:各个线程共享的内存区域,存储类的结构信息,例如运行时常量池,字段(通过引用常量池中的常量来描述)和方法等数据,以及方法和构造函数的代码,包括用于类和实例初始化以及接口初始化的特殊方法。
23、运行时数据区
24、堆结构(jdk1.8)
25、java垃圾回收算法
-
标记-清除算法:
1、标记,从根节点出发遍历对象,对访问过的对象打上标记
2、清除,对没有标记的对象进行回收 -
标记-整理算法:
1、标记,从根节点出发遍历对象,对访问过的对象打上标记
2、让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存 -
复制算法:用在新生代,把可用的对象从一个地方拷贝到另一个地方
-
三色标记法:CMS、G1垃圾回收器都使用了三色标记法。
白色:没有标记的对象(垃圾对象)
灰色:该对象已标记,对象下的属性还没全被标记
黑色:该对象已标记,对象下的属性也全被标记 -
分代回收算法:堆空间分为新生代和老年代
Minor GC : 新生代回收
Major GC : 老年代回收,出现了 Major GC,经常会伴随至少一次的 Minor GC。
Full GC : 新生代 + 老年代回收 -
可达性分析算法:以GC Roots为起始点,从上至下的方式搜索被根对象集合所连接的目标对象是否可达。内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链。
26、垃圾回收器
JDK1到JDK13的发展历程中,一共出现了10种垃圾回收器
- Serial(串行收集器):新生代,只使用一条GC线程进行垃圾回收,过程中暂停其他工作线程。
- Parallel Scavenge(并行收集器):新生代,复制算法实现,并行多线程回收器,常用于新生代。
- CMS :老年代,标记-清除算法实现(最后会产生许多内存碎片,到达一定量时,交由串行收集器处理)。
- G1 :主要作用于老年代,标记-整理算法实现。
各个收集器的Full GC:
27、GC Roots 可以是哪些?
栈中引用的对象、本地方法栈内引用的对象、静态属性引用的对象、常量引用的对象、同步锁 synchronized 持有的对象、虚拟机内部的引用、反映 java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等
28、引用类型有哪些
强引用:new出来的对象就是强引用,垃圾回收器不会回收
软引用:new SoftReference(),内存不足时回收
弱引用:第一次GC回收时,扫到了回收。ThreadLocal的map的key是弱引用
虚引用:虚引用主要用来跟踪对象的回收,清理被销毁对象的相关资源。
29、java的IO流分为几种?
按功能分:输入流、输出流
按类型分:字节流(8个字节为单位)、字符流(16)
30、BIO、NIO、AIO
BIO:同步阻塞IO,使用简单,并发处理能力低
NIO:同步非阻塞IO,通过channel实现多路复用
AIO:异步非阻塞IO,基于事件和回调机制
31、NIO直接缓冲区与非直接缓冲区
- 缓冲区分配方式:
非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在 JVM 的内存中。
直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。 - 数据传输效率:
非直接缓冲区:在操作系统和 JVM 之间进行数据传输,可能涉及到内存复制操作,因此相对较慢。
直接缓冲区:由于直接建立在物理内存中,可以减少内存复制操作,提高数据传输效率。
32、NIO内存映射到缓存
将文件或文件的一部分映射到内存中的技术。通过内存映射文件,可以以字节数组的方式访问文件内容,而无需进行昂贵的系统调用。
33、序列化和反序列化
把当前jvm进程中的对象,传输到另一个进程里面进行恢复。序列化后进行传输,恢复就是反序列化。
常用的序列化工具json、xml、kyro、hessian等
二、网络基础
1、TCP 三次握手
1、客户端请求建立连接
2、服务端应答,并请求建立连接
3、客户端确认客户端应答
2、TCP 四次挥手关闭连接
1、客户端请求断开连接
2、服务端确认应答
3、服务端请求断开连接
4、客户端确认应答
3、TCP与UDP的区别
- TCP:面向连接的协议,一个TCP连接必须要经过三次“对话”才能建立起来。TCP保证数据正确性、数据顺序,
- UDP:一个非连接的协议,传输数据之前源端和终端不建立连接。UDP可能丢包、不保证顺序;
4、请求出现503是什么问题
503 错误是 HTTP 协议中的一种服务器错误,表示服务器暂时无法处理请求。这个错误通常发生在服务器维护、过载或者遇到了意外的问题时。
- 1、确认问题:首先,确认问题是否存在。可以通过监控系统、日志或者与前端开发人员沟通来了解是否存在 503 错误。
- 2、分析错误原因:一旦确认存在 503 错误,需要分析错误的原因。这可能包括服务器过载、硬件故障、软件错误、配置问题等。
- 3、制定解决方案:根据错误原因,制定相应的解决方案。以下是一些建议:
如果是服务器过载,可以考虑增加服务器数量、优化数据库查询、限制并发连接数等。
如果是硬件故障,及时更换硬件或升级服务器。
如果是软件错误或配置问题,修复代码或配置错误。
考虑使用负载均衡器分发请求,以避免单个服务器过载。
优化代码,减少不必要的资源消耗。 - 4、实施解决方案:根据解决方案,修改代码、配置服务器或其他相关组件。
测试验证:在实施解决方案后,需要对系统进行测试,确保 503 错误已得到解决,并且不会影响其他请求的正常处理。 - 5、预防措施:为防止 503 错误再次发生,可以采取以下措施:定期检查、监控系统、制定应急预案,以应对突发情况。
三、spring
1、spring boot启动流程
1、首先从main找到run方法,在执行run之前new一个springApplication对象
2、进入run方法,创建应用监听器
3、加载配置文件
4、加载应用上下文,当作run方法的返回对象
5、创建spring容器,实现自动配置和bean的实例化
2、spring bean的实例化
两个阶段:容器启动阶段、bean实例化阶段
容器启动阶段:加载元数据、对各种处理器进行注册、上下文初始化、事件广播初始化
Bean实例化:
1、加载Bean定义:spring从配置文件或注解中加载bean定义
2、实例化Bean: 根据bean的定义,创建bean实例。如果bean定义中指定了初始化方法,spring会在实例化后调用这些方法。
3、属性赋值:如果bean中定义了属性,spring会使用属性值或构造器参数对属性进行赋值。
4、Bean注册:将Bean实例注册到Spring容器中,以便在其他地方使用。
3、Spring的ioc
ioc中文名控制反转(另一名称DI依赖注入)。IOC是指,利用反射的原理将创建对象的权利交给Spring容器,spring在运行的时候根据配置文件来动态的创建对象和维护对象之间的关系,实现了松耦合的思想。
4、Spring的aop
面向切行期间,不修改源码对已有方法进行增强。
配置方式:基于注解配置aop、基于XML的Aop、编程式创建代理
Spring AOP 可以使用两种代理方式:JDK动态代理和 CGLIB 代理。如果目标对象实现了至少一个接口,则使用JDK动态代理;否则,使用 CGLIB 代理。
- AOP有哪些实现方式?
AOP有两种实现方式:静态代理(编译时增强)和动态代理(运行时创建)。 - 动态代理的两种实现方式?
JDK动态代理:通过接口实现,代理类根据目标类实现的接口动态生成,不需要自己编写
CGLIB动态代理:通过继承实现。
5、Spring的类加载机制
Springboot中的类SPI扩展机制
在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。
6、Java类加载机制
jvm的类加载器默认使用的是双亲委派模式。三种默认的类加载器 启动类加载器、扩展类加载器、应用程序类加载器。每一个中类加载器都确定了从哪一些位置加载文件。也可以通过继承java.lang.classloader实现自己的类加载器。
7、双亲委派模型
当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的启动类加载器,只有当父加载器无法完成加载任务时,才会尝试自己来加载。
8、Spring怎么解决循环依赖
A依赖于B,B依赖于C,C又依赖于A
解决办法:
1、Spring首先从一级缓存“SingletonObjects”中获取。
2、获取不到并且对象正在创建中,就再从二级缓存“earlySingletonObjects”中获取。
3、如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取。
4、如果从三级缓存中获取到就从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
无法解决的循环依赖:A依赖于B,B依赖于A
9、Spring 用到了哪些设计模式?
- 工厂模式:spring在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法。每个 Bean 都会对应一个 FactoryBean,如 SqlSessionFactory 对应 SqlSessionFactoryBean。
- 单例模式:一个类仅有一个实例,提供一个访问它的全局访问点。
- 适配器模式:Spring提供了一个适配器接口,每一种 Controller 对应一种 HandlerAdapter 实现类,当请求过来,SpringMVC会调用getHandler()获取相应的Controller,然后获取该Controller对应的 HandlerAdapter,最后调用HandlerAdapter的handle()方法处理请求,实际上调用的是Controller的handleRequest()。每次添加新的 Controller 时,只需要增加一个适配器类就可以,无需修改原有的逻辑。
- 代理模式:spring 的 aop 使用了动态代理,有两种方式Jdk动态代理和Cglib动态代理。
- 模板模式:Spring 中 jdbcTemplate、hibernateTemplate 等,就使用到了模板模式。
10、spring的事务和分布式事务
Spring里面的事务本质上是数据库层面的事务,主要是针对单个库中的对个表的操作。
分布式事务主要解决多个库直接数据一致性问题。
11、分布式事务的实现方式
1、使用数据库事务: MySQL 的 XA 事务、 Oracle 的 RAC 事务
2、分布式事务中间件:例如,可以使用 Spring 框架提供的分布式事务支持,通过配置事务管理器、事务传播行为等来实现分布式事务。
3、使用消息队列:将事务操作转化为消息,通过消息队列进行异步处理,保证事务的最终一致性。
4、采用分布式锁:通过对分布式环境中的共享资源进行加锁,保证事务的原子性和一致性。可以使用如 Redis、ZooKeeper 等分布式锁实现。
5、采用分布式缓存:通过将数据缓存在分布式缓存中,减少对数据库的访问,提高并发性能,同时保证数据的一致性。如使用 Redis、Memcached 等分布式缓存技术。
6、使用 TCC机制:分为三个阶段,分别在事务主动方、事务被动方和协调器之间进行。通过 try、commit 和 confirm 操作来保证分布式事务的一致性。
12、分布式锁?
1、什么是分布式锁?
系统内,有多个消费者,需要对同一共享数据并发访问和消费时,会有线程安全问题。例如在秒杀、抢优惠券等场景下,商品库存的数量是有限的,在高并发下,会有"超买"或"超卖"的问题。因此我们需要使用锁,解决多线程对共享数据并发访问的线程安全问题。
2、为什么要用分布式锁
当系统使用分布式架构时,服务会有多个实例存在。需要使用分布式锁,保证一个资源在同一时间内只能被一个服务的同一个线程执行。
3、redis分布式锁
通过代码调用setnx命令,只在键不存在的情况下, 将键的值设置为某个值。若键已经存在, 则setnx命令不做任何动作。为了能处理"获取该锁的请求所在的服务实例宕机,会导致该资源被长期锁住,其他请求无法获取该锁"这种情况,我们还需要设置超时时间。
4、redis分布式锁缺陷
1、强依赖redis的可用性,一旦redis宕机,会导致业务系统不可用,因此最好搭建redis集群。
2、因为对锁设置了超时时间,如果某次请求不能在该次限制时间内完成操作,也会导致在某些时刻,多 个请求获取到锁。
解决方案也很简单,我们在调用setnx时,将值设置为该次请求线程的id,并且在服务实例内,设置一个守护线程,当锁快要超时时,判断请求是否完成,如果未完成,延长超时时间。
四、微服务
1、Gateway
1.1、Gateway的职能
请求接入:作为所有API接口服务请求的接入点
业务聚合:作为所有后端业务服务的聚合点
中介策略:实现安全、验证、路由、过滤、流控等策略
统一管理:对所有API服务和策略进行统一管理
1.2、Gateway和Zuul 对比
Gateway相当于Zuul 1的升级版和代替品,使用 Netty异步 IO、可以通过 SpringBoot 配置或者手工编码链式调用来使用。实现响应式非阻塞式的Api,支持长连接。
zuul:是Netflix的,是基于servlet实现的,阻塞式的api,不支持长连接。
1.3、Gateway几个重要概念
Route(路由):是网关的基本构建块.它由一个id,一个目标uri,一组断言和一组过滤器定义.如果断言为真,则路由匹配
Predicate(断言):事先定义了一组匹配规则,方便让请求过来找到对应的 Route 进行处理。
filter(过滤器):对请求和响应进行修改、处理、拦截。
2、nocos
2.1、服务如何注册到nacos
1、在需要注册到nacos的服务中添加nacos配置。
2、服务在启动的时候,根据配置中的内容向nacos集群发起轮询注册请求。
3、nacos通过心跳检测机制检测服务的存活。
五、mysql
1、事务四大特性
原子性、
一致性(事务执行之前数据库是一致的,那么执行后也是)、
隔离性(事务间隔离)、
持久性(数据的改变是持久的)
2、事务的隔离级别
读未提交:能读取到其他事务没提交的,脏读、不可重复读、幻读
读已提交:读取到其他事务提交后的,不可重复读、幻读
可重复读:给事务读取的数据加上版本号,一个事务内两次读取一致,幻读
串行化:一个事务完成才能执行下一个事务
3、脏读、重复读、幻读
脏读:读到其他事务还没有提交的,可能后面又发生了回滚或修改,就是脏数据
不可重复读:同一条数据,同一个事务中两次读取结果不一样
幻读:同一个事务两次读取的数据条数不一样
4、B树和B+树的区别
- B树特点:
1、节点是排序的
2、一个节点可以存多个元素,这多个元素也是排序了 - B+树的特点:
1、有B树的特点
2、叶子节点之间有指针
3、非叶子节点的元素,都会冗余在叶子节点上(也就是叶子节点中储存了所有元素,并排了序的)
5、mysql为什么用B+树
1、B+树通过对数据进行排序可以提高查询效率
2、一个节点储存多个元素,(B树节点索引+数据,B+树节点仅索引,叶子节点索引+数据)使得B+树不会太 高(一个innodb页默认16k,一般两层B+树存2千万左右数据)
3、叶子节点存了所有数据,可以很好的支持全表扫描、范围查询
4、由于B+树不会太高,一般三层,B+树的IO次数比较稳定
6、mysql锁有哪些
行锁:锁住一行数据,并发度高
表锁:锁住整个表,并发读低(MyISAM 存储引擎只支持表锁)
间隙锁:锁住某个区间
7、mysql的redolog、binlog、undolog
1、binlog用于记录数据库执行的写入性操作,只有在事务提交时才会记录biglog,binlog日志有三种格式,分别为STATMENT、ROW和MIXED。(使用场景:主从复制、数据恢复)。
2、Redolog(InnoDB特有的)记录数据页的变更,而这种变更记录是没必要全部保存。mysql每执行一条DML语句,先将记录写入redo log buffer,后续再一次性将记录写到redo log file。(因为Innodb是以页为单位进行磁盘交互的,1、一个事务很可能只修改一个数据页里面的几个字节,这个时候将完整的数据页刷到磁盘,太浪费资源。2、一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机IO写入性能太差)
注意:binlog和redo log二者同时记录,才能保证当数据库发生宕机重启时,数据不会丢失。
redolog 事务日志、binlog数据库变更的逻辑日志
3、undolog保证事务原子性,记录了数据的逻辑变化。比如一条INSERT语句,对应一条DELETE的undo log,对于每个UPDATE语句,对应一条相反的UPDATE的undo log,这样在发生错误时,就能回滚到事务之前的数据状态。
8、mysql二阶段提交
Mysql开启了binlog日志时,那么提交事务时就要同时完成redolog和binlog的事务写入。二段提交发生在redolog和binlog的日志写入阶段,为了满足数据的一致性设计。
第一阶段:prepare阶段,事务操作记录到redolog中 ,记录为prepare状态。
第二阶段:commit阶段,事务操作记录到binlog中,把redolog的状态改为commit。
如果在写入redolog之前崩溃,redolog和binlog中为空,满足一致性
如果在写入redolog后,写入binlog前崩溃,redolog中为prepare状态状态,可根据redolog恢复。
如果的在binlog写入后崩溃,那么redolog可以拿着事务id去执行binlog的日志直接提交数据。
9、mysql什么时候会索引失效
1、没有使用索引列作为where查询条件
2、对索引列进行函数操作(字符串操作、日志操作)
3、对索引列进行类型转换
4、Like查询时以“%”开头
5、查询条件包括Or,or条件中的每个条件都不涉及索引列,mysql无法使用索引
6、当使用大范围查询的时候
六、redis
1、缓存雪崩、缓存击穿、缓存穿透
缓存雪崩:大量key同时失效
击穿:多个请求获取一个key,key失效时发生击穿到数据库
穿透:缓存中没有key,数据库中也没有,造成资源一直访问数据库
2、会员中有大量要过期怎么处理
1、系统不主动轮询,等用户登陆系统时触发检查(缺点:无法主动发送提醒)
2、用搜索引擎(如es),存储一份会员ID和过期时间到搜索引擎中,搜索引擎能够实现快速检索
3、使用redis实现,把id和过期时间存到redis中,使用redis过期提醒功能 ,当key过期后触发过期事件。
4、使用MQ里面的延迟队列,用户开通会员后,计算会员过期时间,发送延迟消息到MQ里面,达到时间进行消费。
七、多线程
1、线程同步的方法
- Wait(): 让线程等待,将线程存储到一个线程池中。
- Notify(): 唤醒被等待的线程,通常都唤醒线程池中的第一个,让被唤醒的线程处于临时阻塞状态。
- NotifyAll(): 唤醒(同一个锁的)所有的等待线程。必须在同步代码块中调用此方法,否则抛出异常。wait()和notify()系列方法这样设计的目的是防止死锁或永久等待发生。notifyAll()执行后,只有一个线程能得到锁,其他没有得到锁的线程会继续保持在等待状态。
注意:
1、调用完notifyAll()方法后,同步代码块中的其他代码,必须执行完后才能将对象锁释放,而不是调用了notifyAll()方法后立即释放。
2、在java中,Thread类线程执行完run()方法后,一定会自动执行notifyAll()方法。
2、什么是ThreadLocal,以及其实现原理
ThreadLocal是一种线程隔离机制,提供了多线程环境下对于共享变量访问的一个安全性。
在每个线程里面都有一个容器,来存储共享变量的一个副本,然后每个线程只对自己的变量副本进行更新操作。
实现原理:
在Thread类里面有一个ThreadLocalMap,用来存储共享变量的副本,线程仅对这个副本进行操作,不影响全局共享变量的值,实现数据隔离。
3、为什么要使用线程池
1、降低线程开启关闭消耗的系统资源
2、提高系统响应速度(线程开启响应速度慢)
3、线程管理(线程开启、关闭、线程数量等)
4、线程池执行过程
简述:任务进线程池,先判断核心线程是否满了,满了就进队列,队列满了就看最大线程数是否已达到,达到了就按饱和策略处理。未达到饱和就创建线程执行任务。
核心线程 - 队列 - 最大线程数 - 饱和策略
5、线程池的创建方式
1、固定大小线程池
2、缓存线程池
3、单线程线程池
4、延迟任务线程池
5、单线程延迟任务线程池
6、抢占式线程池
7、自定义线程池(ThreadPoolExecutor 7个参数)
6、线程池的7个参数
1、核心线程数
2、最大线程数
3、线程存活时间
4、线程存活时间单位
5、工作队列
6、线程工厂
7、拒绝策略
7、线程池队列有哪些
1、直接提交队列(没有容量,插入一个就会阻塞,执行删除才会唤醒)
2、有界任务队列(达到队列最大值,开启新的线程,否则一直是核心线程,该队列现进先出,有数组和链表两种实现)
3、无界任务队列(这种队列下最大线程数是无效的,只有核心线程)
4、优先任务队列(特殊的无界队列,使用平衡二叉树堆实现,排队时带有优先级)
8、线程池拒绝策略有哪些
1、丢弃任务抛弃异常
2、直接丢弃任务
3、丢弃队列最前面的任务
4、调用线程(提交任务的线程)直接执行此任务
5、new RejectedExecutionHandler() 自定义拒绝策略
9、线程池的关闭
shutdown 当任务队列为空的时候,才关闭线程池
shutdownnow 直接停止
10、使用无界队列的线程池会导致内存飙升吗?
会的,newFixedThreadPool使用了无界的阻塞队列LinkedBlockingQueue,如果线程获取一个任务后,任务的执行时间比较长,会导致队列的任务越积越多,导致机器内存使用不停飙升, 最终导致OOM。
11、线程创建的几种方式
1、继承Thread,重写run方法
2、实现Runnable接口创建线程(无返回值)
3、实现Callable接口+Future创建线程(有返回值,可抛出经过检查的异常)
12、Future有什么用
可以对正在执行的任务进行维护操作(取消任务、获取任务执行结果、判断任务是否已完成等)。
13、FutureTask有什么用
1、可取消的异步计算;
2、利用开始和取消计算的方法、查询计算是否完成的方法和获取计算结果的方法,此类提供了对Future的基本实现;
3、可使用FutureTask包装Callable或Runnable对象因为FutureTask实现了Runnable,所以可将FutureTask提交给Executor执行;
4、仅在计算完成时才能获取结果;如果计算尚未完成,则阻塞get方法一旦计算完成,就不能再重新开始或取消计算;
14、线程的生命周期(状态)
- 新建
- 可运行
- 阻塞(处于阻塞状态的线程不会占用CPU资源,阻塞IO操作执行完,线程可转换为可运行状态)
- 等待(执行object.wait()或thread.join()变成等待,notify()或加入的线程执行完毕会转为可运行)
- 固定时间等待(不会无限期地等待,如果线程没有在指定时间内完成操作,自动转换为可运行)
- 终止状态
15、 i++是线程安全的吗
i++不是原子性操作,执行它包括三个操作:读i的值、i+1、将新值保存到内存。
所以它不是线程安全的。
16、可重入锁ReentrantLock
可重入:当一个线程获得一个对象锁后,再次请求该对象锁时,可以再次获得该对象的锁。
16.1、Synchronized和ReentrantLock的相同
1、都是独占锁
2、都是可重入的
16.2、Synchronized和ReentrantLock的不同
1、ReentrantLock是Java层面的实现,手动加锁手动解锁,synchronized是JVM层面的实现。
2、ReentrantLock可以实现公平和非公平锁。
3、ReentantLock获取锁时,限时等待,配合重试机制更好的解决死锁
4、ReentrantLock可响应中断
5.使用synchronized结合Object上的wait和notify方法可以实现线程间的等待通知机制。ReentrantLock结合Condition接口同样可以实现这个功能。
17、synchronized可重入锁的实现原理
1、每一个可重入锁都会关联一个线程ID和一个锁状态status。
2、当一个线程请求方法时,会去检查锁状态,
- 如果锁状态是0,代表该锁没有被占用,直接进行CAS操作获取锁,将线程ID替换成自己的线程ID。
- 如果锁状态不是0,代表有线程在访问该方法。此时,如果线程ID是自己的线程ID,
– 可重入锁,将status自增1,然后获取到该锁,进而执行相应的方法。
– 非重入锁,进入阻塞队列等待。
3、释放锁时,
- 可重入锁,每一次退出方法,就会将status减1,直至status的值为0,最后释放该锁。
- 非可重入锁,直接就会释放该锁。
八、Activiti流程引擎
1、Activiti工作流的主要组件
Activity(活动)、workflow(流程)、workItem(工作项)、rule(规则)、状态(state)
2、Activiti工作流有什么优势
1、提供了可视化的方式进行描述和执行业务流程
2、支持业务逻辑的可重用性和模块化开发
3、支持监控和追踪业务流程
4、扩展性比较好
3、Activiti工作流service有哪些
1、RepositoryService 流程定义和部署对象
2、RuntimeService 执行管理,包括流程实例和执行对象
3、TaskService 执行任务(正在流程中的)
4、HistoryService 历史管理
5、IdentityService 用户角色
4、流程实例和执行对象的区别
流程实例:一个流程中,流程实例只有一个
流程对象:按照流程定义的规则执行一次操作,一个流程中执行对象可以多个
5、工作流中一个任务完成后,存在多条连线,如何处理
1、使用流程变量
2、当一个任务完成之后,根据这几条连线的条件和设置流程变量,例如${流程变量名==’值’},{}符号是布尔类型,判断走哪条线。
6、Activiti工作流中排他网关和并行网关都能执行什么功能
排他网关:分支,通过连线的流程变量,判断执行哪条连线,如果条件不符合,默认离开连线。只执行其中一个流程
并行网关:可以同时执行多个流程,直到总流程结束。可以对流程进行分支和聚合。
7、分配个人任务的三种方式
1、直接给值,在Xxxx.bpmn中指定
2、流程变量${流程变量名}或#{}
3、用监听类指定任务办理人(setAssgnee)
8、个人任务和组任务的查询一样吗?
1、不一样
2、都是用TaskService完成
3、个人任务(taskAssgnee),组任务(taskCandidateUser)
4、数据库存放,个人任务:参与,组任务:类型、参与、候选
九、设计模式
1、设计模式的六大原则
- 开闭原则: 一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化
- 依赖倒转原则: 面向接口编程
- 里氏代换原则: 只要父类能出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或异常
- 单一职责: 一个类/方法尽可能只做一件事
- 迪米特法则: 对象与对象间因该尽可能独立,少调用其他对象的方法
- 接口隔离原则: 每个接口的功能应该是独立的
2、设计模式分类
创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
3、单例模式
饿汉式:默认创建对象
懒汉式:第一次使用时创建对象
饿汉式:1、静态变量,2、静态代码块
懒汉式:3、静态变量(初始化方法加载),4、同步方法,5、同步代码块
6、双重检查
7、静态内部类(利用jvm对静态内部类在使用时加载的机制)
8、枚举
4、工厂模式
工厂模式分为三种:简单工厂模式、工厂方法模式、抽象工厂模式
- 简单工厂模式:建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
- 工厂方法模式:简单工厂模式的改进,使用一个工厂接口,创建多个工厂类,每个工厂创建对应的对象。spring ioc使用的是这个模式。
- 抽象工厂模式:围绕一个超级工厂创建其他工厂,每个工厂可以生产不同类型的产品
5、装饰器模式
原理:在不改变原有结构的情况下,可以动态地扩展其功能。
装饰器模式所包含 4 个组成:
java的io流使用的是装饰器模式:
InputStream 是具体构件
FilterInputStream 是抽象装饰,FilterInputStream构造器可以接受任意一种形式的 InputStream。
BufferedInputSream 是具体装饰
6、代理模式
- 解释:代理模式就是将被代理类包装起来然后重新实现相同的方法,并在调原方法的同时,在其前后添加一个新的处理。
- 使用场景:代理模式还可以用于实现事务管理、日志记录、缓存等功能。例如,在电子商务网站中,代理模式可以用于处理用户订单,包括订单的生成、处理和交付等过程。
静态代理:继承实现(继承后添加代理代码)或组合实现(接口作为代理类,该接口作为属性)
动态代理:JDK动态代理(反射实现,必须提供接口)、CGLIB动态代理(操作字节码实现,操作类型必须可继承)
十、ES
1、为什么要使用Elasticsearch
1、全文搜索引擎:快速地搜索、分析和返回匹配的结果。相比传统数据库,更适合处理大规模的文本数据,可以在毫秒级内返回搜索结果。
2、分布式实时分析:可在多个节点上扩展和处理大数据。
3、灵活性和可扩展性:可以自定义搜索的准确性和范围。支持多种数据类型、分析器和插件,可以定制和扩展。
4、可靠性和高可用性:它提供了复制和故障恢复功能,可以在节点故障或系统崩溃时自动恢复数据。es还提供了跨集群复制和同步功能,可以实现数据的多重备份和高可用性。
5、易用性和可维护性:它有友好的API和用户界面,易于使用和配置。它提供了丰富的监控和管理工具,可以方便地监控集群状态、诊断问题、优化性能和调整配置。
2、es是如何实现Master选举的
投票机制。所有可以发现彼此的节点内部,都会根据nodeId字典排序,排序的第一个就是当前节点的投票master,当某个节点被投票数大于节点数/2+1,该节点就被选举为master。
2.1、假设节点总共20个,其中的10个选了一个master,另外10个选了另一个master,怎么办?(es脑裂问题)
1、增加物理机:可以增加一台物理机,可以缓解脑裂问题。
2、减少误判:可以适当调大节点状态的响应时间,默认为3s,调大参数(如6s),可适当减少误判。
3、选举触发:控制选举行为发生的最小集群主节点数量的值设置为1。当备选主节点的个数大于等于该参数的值,且备选主节点中有该参数个节点认为主节点挂了,进行选举。
4、角色分离:即master节点与data节点分离,限制角色。
3、es索引文档的过程
1、接收请求:用户发起请求,请求先到达master节点,然后计算出id对应的分片,发由对应的节点来处理。
2、写入内存缓冲:分片所在节点收到请求后,先写入内存,此时文档搜不到。
3、定时刷新文件缓存:每隔1秒写入数据到文件缓存,此时文档能搜到。
4、数据同步:通过先写入到TransLog日志保证数据不丢失,然后写入到磁盘。
5、写入磁盘后,清空TransLog日志
4、es的删除和更新文档的过程
es的文档是不可变的,当删除请求发送后,文档并没有真的被删除,而是被标记删除。更新也类似,将旧的文档标记删除,新的文档写入。标记删除的也会被查到,只是在结果中被过滤掉。
5、es搜索的过程
分两个阶段:查询、读取
6、es的倒排索引
普通的索引是根据key去找value,es中倒排索引是将文档中的单词、词语构建成索引,匹配到单词后,得到整个文档。
十一、VUE
1、vue的生命周期
四个阶段,8个钩子函数
创建 :beforeCreate,created
挂载 :beforeMount(render),mounted
更新 :beforeUpdate,updated
销毁 :beforeDestroy,destroyed
1.1、创建前
数据是获取不到,并且真实dom元素没有渲染出来
1.2、创建
数据能获取,真实dom元素没有渲染出来,可以进行相关初始化事件的绑定、发送请求操作
2.1、挂载前
dom马上就要被渲染出来了,但是却还没有真正的渲染出来,这个钩子函数与created钩子函数用法基本一致,可以进行相关初始化事件的绑定、发送ajax操作
2.2、挂载
数据挂载完毕,真实dom元素也已经渲染完成了,这个钩子函数内部可以做一些实例化相关的操作
3.1、更新前
当数据改变的时候会立马执行,这个钩子函数获取dom的内容是更新之前的内容
3.2、更新
更新新的dom
4.1、销毁前
组件销毁的时候,就会触发这个钩子函数代表销毁之前,可以做一些善后操作,可以清除一些初始化事件、定时器相关的东西。
4.2、销毁
实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
2、第一次页面加载会触发哪几个钩子
beforeCreate,created,beforeMount,mounted
3、vue-router 是什么?它有哪些组件
vue-router路由,通俗来讲主要是来实现页面的跳转,通过设置不同的path,向服务器发送的不同的请求,获取不同的资源。
主要组件:router-view(渲染视图的组件)、router-link(设置路由跳转的组件)
4、vue当中的指令和它的用法
v-model:双向绑定,将表单元素的值与Vue实例的数据进行同步。
v-on:监听DOM事件,可以将事件处理函数绑定到指定的元素上。
v-show:元素显示和隐藏,根据表达式的值来切换元素的CSS样式。
v-if v-else:是否渲染某个元素。
v-html:用于解析HTML字符串并渲染到元素中,可以防止XSS攻击。
v-text:用于将表达式的值作为纯文本插入到元素中。
v-pre:用于保留原始的HTML标签,不会对标签进行转义。
5、vue中:和@有什么区别
":“主要用于属性绑定,也被简写为"v-bind”
"@“主要用于事件绑定,也被简写为"v-on”
十二、综合性问题
1、生产环境发生内存泄露怎么排查问题?
- 1、什么是内存泄露
程序在运行过程中,因为某些原因导致不需要的对象,仍然占用jvm内存空间并且无法回收。导致占用内 存越来越大出现OOM。 - 2、内存泄露出现的现象
频繁FULL GC、内存暂用量过大无法释放 - 3、怎么定位问题?
是否是内存泄露?老年代逐步增长?Full gc卡顿?年轻代内存一直在高位无法释放?频繁full GC?
使用jstat命令查看虚拟机中各个内存的使用情况、GC情况
使用dump工具把内存dump下来,使用MAT工具进行分析。Mat会分析dump文件的内容,给出分析结果,定位到有问题的类,然后找到有问题代码进行优化。
一般情况可是循环引用、内存对象泄露没有销毁、动态分配内存后没有释放、长期持有对象引用、资源未关闭等情况造成的内存泄露