【超全总结八股】Java面试题常考总大全 - 1

一、Java

- 基础

Java语言特点?

面向对象(封装、继承、多态)、平台无关性(Java虚拟机)、支持多线程、可靠安全、网络编程

  • 封装:将对象的属性和行为封闭,尽可能隐蔽对象的内部细节。提高代码的安全性。
  • 继承:类和类之间具有一定的关系,子类共享父类变量和方法。提高代码的复用性。
  • 多态:同一个实体同时具有多种形式,不同的对象,收到同一消息可以产生 不同的结果,这种现象称为多态性。提高代码灵活性和重用性。
JVM、JDK、JRE区别?
  • jvm:运行 Java 字节码的虚拟机
  • jdk:创建和编译程序
  • jre:Java 运行时环境
什么是字节码

我们编写好的.java文件,通过编译器编译成**.class**文件,JVM负责加载解释字节码文件,并生成系统可识别的代码执行

Object类常见方法有哪些(11个方法)

1.getClass(返回运行时对象)、2.hashCode(对象的哈希码)、3.equals(比较对象内存地址)、4.clone(返回对象的拷贝)、5.toString(16进制字符串)、6.notify(唤醒线程)、7.notifyAll(唤醒所有线程)、8.wait(等待暂停线程,3个重载方法)、11.finalized(垃圾回收)

🚩 “==”和“equals”的区别
  1. ”==“对于基本类型和引用类型的作用效果是不同的

    对于基本数据类型来说,== 比较的是

    对于引用数据类型来说,== 比较的是对象内存地址

  2. “equals()”不能用于判断基本数据类型的变量,只能用来判断两个对象内存地址是否相等。

因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

public、protected、default、private区别

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

int 和 Integer 有什么区别?
  1. 数据类型不同:int 是基础数据类型,而 Integer 是包装数据类型;
  2. 默认值不同:int 的默认值是 0,而 Integer 的默认值是 null;
  3. 内存中存储的方式不同:int 在内存中直接存储的是数据值,而 Integer 实际存储的是对象引用,当 new 一个 Integer 时实际上是生成一个指针指向此对象;
  4. 实例化方式不同:Integer 必须实例化才可以使用,而 int 不需要;
  5. 变量的比较方式不同:int 可以使用 == 来对比两个变量是否相等,而 Integer 一定要使用 equals 来比较两个变量是否相等。
🚩 String str = “i”;和String str = new String(“i”);一样吗?
  • String str="i"会将其分配到常量池中,如果常量池中存在 i,就将i的地址赋给变量,如果不存在就创建一个再赋给变量。

  • String str=new String(“i”)会将对象分配到中,即使内存一样,还是会重新创建一个新的对象。

String 和 StringBuffer、StringBuilder 的区别是什么?
  • String:长度固定不可变的,对象创建后不可修改,任何字符串操作都会创建新的对象;线程安全
  • StringBuffer:长度可变,线程安全的
  • StringBuilder:长度可变,线程不安全,相比StringBuffer有更高的性能
接口和抽象类有什么区别
  • 接口:接口使用interface修饰、接口不能实例化、类可以实现多个接口;

    • java8之前,接口中的方法都是抽象方法,省略了public abstract。
    • java8之后;接口中可以定义静态方法,静态方法必须有方法体,普通方法没有方法体,需要被实现;
  • 抽象类:抽象类使用abstract修饰、抽象类不能被实例化、抽象类只能单继承;

    • 抽象类中可以包含抽象方法和非抽象方法,非抽象方法需要有方法体
    • 如果一个类继承了抽象类,①如果实现了所有的抽象方法,子类可以不是抽象类;②如果没有实现所有的抽象方法,子类仍然是抽象类。

    抽象类是抽象的根源,例如鸟、猫狗等都属于动物

    接口是功能的具体实现,如鸟会飞,猫、狗会爬

🚩重载和重写有什么区别?

​ 重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理;重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法

  • 重载:发生在同一个类中(或者父类和子类之间),方法名必须相同参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
  • 重写:重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。方法名、参数列表必须相同
浅拷贝和深拷贝?
  • 浅拷贝:仅仅克隆基本类型变量,不克隆引用类型变量;
  • 深拷贝:既克隆基本类型变量,又克隆引用类型变量;
什么是 Java 内部类? 内部类的分类有哪些 ?内部类有哪些优点和应用场景?
  • 成员内部类:定义在类内部,但在方法外部的类。它可以访问外部类的所有成员变量和方法;
  • 静态内部类:定义在类内部,但使用 static 修饰的类。它只能访问外部类的静态成员变量和方法;
  • 局部内部类:定义在方法内部的类。它只能访问方法内部的 final 变量和方法参数;
  • 匿名内部类:没有类名的内部类,通常用于创建只需要使用一次的类。
什么是序列化和反序列化?应用场景?
  • 序列化: 将数据结构或对象转换成二进制字节流的过程(对象->字节流)
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

序列化应用场景:

  • 对象进行网络传输存储到文件存储到数据库存储到内存

  • 序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。

JDK 自带的序列化,只需实现 java.io.Serializable接口即可。


- 异常

Exception和Error区别?

​ 所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:

  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。

    Exception 又可以分为 CheckedException (受检查异常,必须处理) 和 UncheckedException (不受检查异常,可以不处理)。

  • ErrorError 属于程序无法处理的错误。Java 虚拟机(JVM)一般会选择线程终止。

try-catch-finally如何使用?
  1. try块 : 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally

  2. catch块 : 用于处理 try 捕获到的异常。

  3. finally 块 : 无论是否捕获或处理异常,finally 块里的语句都会被执行。

当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。如果catch块和finally块都有return语句,那么直接返回finally块的return。

throw 和 throws 的区别?

throw和throws 是 Java中的关键字,它们在处理异常时有着不同的作用。

  • throw关键字用于在方法内部抛出异常。它可以抛出一个已经定义好的异常对象。
  • throws关键字用于在方法声明中声明可能抛出的异常。如果一个方法可能抛出异常,但是在方法内部没有使用throw关键字抛出,那么在方法声明中使用throws 关键字声明可能抛出的异常。

- 泛型

泛型的作用:

代码更加简洁、健壮。把一个集合中的内容限制为一个特定的数据类型


- 反射

什么是反射:
  • 加载完类之后,在堆内存的方法区中就产生了一个Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构
  • 这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
反射的优缺点,应用场景

优点:

  • 可以动态地获取类的信息,不需要在编译时就知道类的信息。
  • 可以动态地创建对象,不需要在编译时就知道对象的类型。
  • 可以动态地调用对象的属性和方法,可以在运行时动态地改变对象的行为。

缺点:

  • 运行效率较低,不如直接调用方法或属性。
  • 破坏 Java 的封装性,可能会使代码变得复杂和不稳定。

应用场景:①动态代理、②单元测试、③配置文件加载

获取Class对象的四种方式
  1. 知道具体类的情况下:

    Class alunbarClass = MyTest.class;
    
  2. 通过对象实例instance.getClass()获取:

    MyTest test = new MyTest();
    Class t2 = test.getClass();
    
  3. 通过 Class.forName()传入类的全路径获取:

    Class myTest = Class.forName("com.atguigu.ms2.MyTest");
    
  4. 通过类加载器xxxClassLoader.loadClass()传入类路径获取:

    ClassLoader.getSystemClassLoader().loadClass("com.atguigu.ms2.MyTest");
    

- 注解

什么是注解?

Annotation (注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用

注解的解析方法有哪几种?

注解只有被解析之后才会生效,常见的解析方法有两种:

  1. 编译期直接扫描 :编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。

  2. 运行期通过反射处理 :像框架中自带的注解(比如 Spring 框架的 @Value@Component)都是通过反射来进行处理的。


- I/O流

IO流
Java IO 流了解吗?

IO 即 Input/Output,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储的过程即输出

IO流按照单位划分可以分为几种?
  • 字节流:InputStream、OutputStream

  • 字符流:Reader、Writer

BIO、NIO、AIO有什么区别?
  • 同步阻塞BIO:一个连接一个线程。(JDK1.4之前)
  • 同步非阻塞NIO:一个请求一个线程。(JDK1.4之后)
  • 异步非阻塞AIO:一个有效请求一个线程。(JDK1.7)

- 集合类

Java集合继承关系

集合框架底层数据结构介绍:

1、Collection

1.List

  • ArrayList: Object[] 数组,线程不安全。
    • ArrayList 首先是空数组,有数据添加后默认容量为10。之后每次扩容之后容量都会变为原来的 1.5 倍左右
  • Vector:Object[] 数组,线程安全
  • LinkedList: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)

2.Set(线程不安全、无重复)

  • HashSet(无序,唯一): 底层采用 ’HashMap’来保存元素(放在HashMap的’key‘上)
  • LinkedHashSet: HashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的。
  • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)

3.Queue

  • PriorityQueue: Object[] 数组来实现二叉堆
  • ArrayQueue: Object[] 数组 + 双指针

Queue 是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循 先进先出(FIFO) 规则

Deque 是双端队列,在队列的两端均可以插入或删除元素。


ArrayList是否是线程安全的?如何保证线程安全?
  1. ArrayList是线程不安全的,add等方法未添加synchronized关键字,多线程同时读写时会发生java.util.ConcurrentModificationException异常。

  2. ① Vector()、② java.util包下的Collections.synchronizedList(new ArrayList<>());

    ③ JUC包下的CopyOnWriteArrayList(写时复制)。

注:HashSet、HashMap同理(synchronizedSetCopyOnWriteArraySetsynchronizedMapConcurrentHashMap

ArrayList和LinkedList有什么区别?
  1. 底层实现结构: ArrayList是基于动态数组,LinkedList则是基于双向链表的实现
  2. 插入和删除操作:在ArrayList中,插入和删除元素会导致后续元素的位置移动,需要保持索引的连续性;LinkedList中,插入和删除元素只需要修改链表的引用
  3. 内存占用: ArrayList需要预先分配一定的容量,如果元素数量超过容量,需要重新分配更大的内存空间。LinkedList则不需要预先分配固定大小的空间,每个元素都可以独立分配内存

2、Map🚩

HashMap: JDK1.8 之前 HashMap 由数组+链表组成的,初始大小为162倍扩容负载因子为0.75(线程不安全)

LinkedHashMap:继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 还增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。

HashTable: 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的(线程安全)

TreeMap: 红黑树(自平衡的排序二叉树)

HashSet底层是什么?

​ HashSet底层是HashMap,对于HashSet来说,只关心HashMap的key值,value是一个object()常量

HashMap和HashTable有什么区别?
  1. 线程安全性:HashMap不是线程安全的,即不保证线程安全。HashTable是线程安全的,每个方法都使用synchronized来进行同步。

  2. 性能:由于HashTable使用了synchronized关键字进行同步,所以它的效率比HashMap低,尤其在单线程环境下。HashMap的性能要高于HashTable。

  3. 允许null键和值:HashMap允许使用null键和值,而HashTable不允许使用null键和值。

  4. 容量和加载因子:“HashMap的默认容量为16,加载因子为0.75f。HashTable的默认容量为11,加载因子为null。

  5. 继承的父类不同:Hashtable继承Dictionary类,HashMap继承AbstractMap类

🚩HashMap底层原理

​ JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列

​ JDK1.8之后:数组+链表+红黑树;相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。HashMap的长度是2的幂次方(n - 1) & hash 相当于 %2

ConcurrentHashMap在JDK1.8发生了什么变化?
  • 线程安全实现方式 :JDK 1.7 采用 Segment 分段锁来保证安全, Segment 是继承自 ReentrantLock。JDK1.8 放弃了 Segment 分段锁的设计,采用 Node + CAS + synchronized 保证线程安全,锁粒度更细,synchronized 只锁定当前链表或红黑二叉树的首节点。

  • Hash 碰撞解决方法 : JDK 1.7 采用拉链法,JDK1.8 采用拉链法结合红黑树(链表长度超过一定阈值时,将链表转换为红黑树)。

  • 并发度 :JDK 1.7 最大并发度是 Segment 的个数,默认是 16。JDK 1.8 最大并发度是 Node 数组的大小,并发度更大

HashCode有什么作用?

作用是获取哈希码,也称为散列码,这个哈希码的作用是确定该对象在哈希表中的索引位置

关于HashCode值比较
  • 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。hashCode 值不相等,直接认为两个对象不相等
  • 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
🚩 规范为什么重写equals方法要重写hashcode方法?

​ 在Java的哈希表数据结构中,hashCode()方法用于确定对象在哈希表中的存储位置,而equals()方法用于比较两个对象是否相等。每个对象的哈希码将根据对象的内存地址生成,即使两个对象通过equals()方法相等,它们的哈希码也可能不同。

自定义的类重写了equals方法,没重写hashcode方法会有什么问题呢?
  1. 哈希表数据结构无法正常工作:可能会导致无法正确检索或重复存储对象。
  2. 违反了hashCode()方法与equals()方法的规范:
  3. 在使用基于哈希的数据结构时性能下降
迭代器Iterator是什么?

为了方便的处理集合中的元素,Java中出现了一个对象,该对象提供了一些方法专门处理集合中的元素.例如删除和获取集合中的元素.该对象就叫做迭代器(Iterator)。

Iterator 接口源码中的方法?
  1. java.lang.Iterable 接口被 java.util.Collection 接口继承,java.util.Collection 接口的 iterator() 方法返回一个 Iterator 对象
  2. next() 方法获得集合中的下一个元素
  3. hasNext() 检查集合中是否还有元素
  4. remove() 方法将迭代器新返回的元素删除

其他

Java8新特性
  1. 函数式接口(仅仅只包含一个抽象方法,但是可以有多个非抽象方法)

  2. Lambda表达式

    • 使用 Lambda 表达式可以使代码变的更加简洁紧凑。让 java 也能支持简单的函数式编程。

    • Lambda 表达式是一个匿名函数,java 8 允许把函数作为参数传递进方法中。

  3. Stream(在一组元素上一次执行的操作序列)

  • 检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等
  1. Optional(防止 NullPointerException工具)

  2. Date-Time API

什么是里氏替换原则?举例

​ 里氏替换原则(Liskov Substitution Principle)是面向对象设计中的一个原则,它指导子类应该能够替换其父类并且不会破坏程序的正确性。换句话说,任何使用父类对象的地方,都应该能够使用子类对象来替代,而不需要修改原有的代码。


二、多线程 JUC并发编程

多线程基础

什么是进程和线程?
  • 进程:资源分配的最小单位(一个进程启动时包含一个主线程和多个线程)
  • 线程:任务调度的最小单位(多个线程共享堆和方法区,每个线程都有自己的程序计数器、虚拟栈、本地方法栈)
  • 协程:一种用户态的轻量级线程,由程序员自己控制调度,可以在单个线程内实现多个协程的并发执行。
  • 管程:提供了一种用于协调多个线程之间访问共享资源的机制。
并发和并行的区别?
  • 并发:两个及两个以上的作业在同一 ’时间段‘ 内执行。
  • 并行:两个及两个以上的作业在同一 ‘时刻’ 执行。
同步和异步的区别?
  • 同步: 程序按照代码的顺序执行,一行一行地执行
  • 异步:不需等待该任务完成,可以直接执行下一个任务
线程的生命周期和状态?
  1. 新建(New):当创建一个线程对象时,线程处于新建状态。此时线程还没有开始运行。
  2. 就绪(Runnable):当调用**线程的start()**方法后,线程进入就绪状态。此时线程已经准备好运行,等待CPU的调度。
  3. 运行(Running):当线程获得CPU时间片后,开始执行线程的run()方法,线程进入运行状态。
  4. 阻塞(Blocked):线程在运行过程中,可能会因为某些原因而暂停执行,进入阻塞状态。比如等待某个资源、等待I/O操作完成等。当阻塞条件满足时,线程会重新进入就绪状态,等待CPU的调度。
  5. 死亡(Terminated):线程执行完run()方法后,或者因异常退出,线程进入死亡状态。死亡的线程不能再次启动。
🚩什么是上下文切换?

​ 当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。

什么是线程死锁?怎样预防、避免?
  • 线程死锁:多个线程同时被阻塞,它们在互相等待对方释放资源。由于线程被无限期地阻塞,因此程序不可能正常终止。
  • 预防死锁:
    1. 避免资源独占:尽量避免一个进程在获得了某些资源后再次请求其他资源,而应该将所有需要的资源一次性申请到位。

    2. 避免资源持有和等待:当一个进程占用了一些资源并等待另一些资源时,其他进程就无法使用这些资源,容易引发死锁。因此,尽可能减少资源持有和等待时间。如果申请不到,可以主动释放它占有的资源。

    3. 避免资源互斥:有些资源在同一时间只能被一个进程占用,比如打印机、磁带机等,需要采用一些技术手段来避免资源互斥的问题。

    4. 引入资源剥夺策略:当一个进程请求的资源被其他进程占用时,可以采取剥夺资源的策略,即暂停占用该资源的进程,直到该资源被释放后再恢复该进程的执行。

    5. 引入进程抢占策略:当一个进程等待时间过长时,可以采取抢占其资源的策略,即中断正在执行的进程,强制释放其占用的资源。

  • 避免死锁:
    • 在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。
sleep()和wait()方法对比
  • 共同:两者都可以暂停线程的执行
  • 区别:①sleep() 方法没有释放锁,而 wait() 方法释放了锁 。 wait() 通常被用于线程间交互/通信,sleep()通常被用于暂停执行。wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法。sleep()方法执行完成后,线程会自动苏醒。sleep()是 Thread 类的静态本地方法,wait() 则是 Object类的本地方法。(因为wait()要操作对象,而sleep()暂停当前线程与对象无关)
创建线程有四种方式?
  • 继承Thread类。
  • 实现Runnable接口。
  • 实现Callable接口
  • 线程池创建
🚩可以直接调用 Thread 类的 run 方法吗?

new 一个 Thread,线程进入了新建状态。 调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结: 调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。

乐观锁和悲观锁?
  • 乐观锁:乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源是否被其它线程修改了(版本号或 CAS 算法)
    • 乐观锁通常多于多读场景,避免频繁加锁影响性能,大大提升了系统的吞吐量。
  • 悲观锁:悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。
    • 悲观锁通常用于多写场景,避免频繁失败和重试影响性能。
乐观锁存在哪些问题?
  • ABA 问题是乐观锁最常见的问题。(变量前面追加上版本号或者时间戳)
  • 循环时间长开销大,CAS经常要自旋进行重试。(使用 pause 指令)
  • 只能保证一个共享变量的原子操作。CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。(封装成对象再进行CAS操作)

JUC

什么是CAS,工作原理是什么?
  • CAS(Compare-And-Swap 比较并交换),是一条CPU原语(属于硬件,原语的执行必须连续不可中断)。CAS底层调用sun.misc包下的Unsafe类,该类所有方法都是native修饰,直接调用操作系统底层资源执行相应任务。
  • CAS包括内存值、预期值、新值。只有当内存值等于预期值时,才会将内存值修改为新值,否则放弃更新
CAS有什么缺点?
  • 循环时间长开销很大
  • 只能保证一个共享变量的原子操作
  • 引出ABA问题(一个线程先读取共享内存数据值A,随后因某种原因,线程暂时挂起,同时另一个线程临时将共享内存数据值先改为B,随后又改回为A。随后挂起线程恢复,并通过CAS比较,最终比较结果将会无变化。这样会通过检查,这就是ABA问题。)
volatile

volatile底层实现原理是内存屏障;在每个写操作的前面插入一个StoreStore屏障,在每个写操作的后面插入一个StoreLoad屏障

  • volatile是Java虚拟机提供的轻量级的同步机制
    1. 保证可见性:线程在自己内存做完变量操作后要通知给主内存
    2. 不保证原子性:即没有同步机制锁。解决(1.synchronized、2.使用JUC包下的AtomicInteger等Atomic原子)
    3. 禁止指令重排:计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排。多线程环境线程交替执行,由于编译器优化指令重排的存在,两个线程中使用的变量能否保证一致性是无法预测的。
🔏Synchronized关键字概念?怎么用?
  • 概念:主要解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
  • 修饰实例方法 (锁当前对象实例)、修饰静态方法 (锁当前)、修饰代码块 (锁指定对象/类
Synchronized底层原理?

​ Synchronized是可重入锁,每部锁对象会有一个计数器记录线程获取几次锁,在执行完同步代码块时,计数器的数量会-1,直到计数器的数量为0,就释放这个锁

同步方法通过ACC_SYNCHRONIZED 关键字隐式的对方法进行加锁。当线程要执行的方法被标注上ACC_SYNCHRONIZED时,需要先获得锁才能执行该方法。

同步代码块通过 monitorentermonitorexit 分别指向同步代码块的开始和结束,用来执行加锁。当线程执行到 monitorenter 的时候要先获得锁,才能执行后面的方法。当线程执行到monitorexit 的时候则要释放锁。每个对象自身维护着一个被加锁次数的计数器,当计数器不为0时,只有获得锁的线程才能再次获得锁。

JDK1.6 之后的 synchronized 底层做了哪些优化?
  • JDK1.6 对锁的实现引入了大量的优化,如①偏向锁、②轻量级锁、③自旋锁、④适应性自旋锁、⑤锁消除、⑥锁粗化等技术来减少锁操作的开销。
  • 锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
synchronized 和 volatile 有什么区别?
  • volatile关键字是线程同步轻量级,所以 volatile性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量,而 synchronized 关键字可以修饰方法以及代码块
  • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性
🔒 ReentrantLock是什么?
  • ReentrantLock 实现了 Lock 接口,是一个可重入且独占式的锁,和 synchronized 关键字类似。不过,ReentrantLock 更灵活、更强大,增加了轮询、超时、中断、公平锁和非公平锁等高级功能。
🚩 synchronized 和 ReentrantLock 有什么区别?
  • 相同:两者都是可重入锁
  • 区别不同:
    1. 实现公平锁:ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁

    2. 手动释放锁:ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;

    3. 适用范围:ReentrantLock 只适用于代码块锁,而 synchronized 可以修饰类、方法、变量等。

    4. 响应中断:ReentrantLock可响应中断,可轮回,synchronized是不可以响应中断的

    5. 级别层面:ReentrantLock是API级别的,synchronized是JVM级别的

    6. 底层实现:ReentrantLock是同步非阻塞,采用的是乐观并发策略;synchronized是同步阻塞,使用的是悲观并发策略。

ReentrantReadWriteLock

​ 是一个可重入的读写锁,既可以保证多个线程同时读的效率,同时又可以保证有写入操作时的线程安全。

🚩ThreadLocal 是什么?有哪些使用场景?
  1. ThreadLocal类用来提供线程内部的局部变量,不同的线程之间不会相互干扰
  2. 线程安全、上下文信息传递、数据库连接管理
ThreadLocal导致内存泄露的问题?如何解决?

​ 内部类ThreadLocalMap中的Entry的设计。Entry继承了WeakReference<ThreadLocal<?>>,即Entry的key是弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。所以key会在垃圾回收的时候被回收掉, 而key对应的value则不会被回收, 这样会导致一种现象:key为null,value有值。 key为空的话value是无效数据,久而久之,value累加就会导致内存泄漏。

​ 解决:每次使用完ThreadLocal都调用它的remove()方法清除数据。(finally块中清理)

ConcurrentHashMap
  • JDK1.7底层采用分段的数组+链表实现
  • JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑树
    • 采用 CAS + Synchronized来保证并发安全进行实现
      • CAS控制数组节点的添加
      • synchronized只锁定当前链表红黑树首节点,只要hash不冲突,就不会产生并发的问题 , 效率得到提升
线程池是什么?为什么用线程池?
  • 线程池就是管理一系列线程的资源池。当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。
  • ①降低资源消耗、②提高响应速度、③提高线程的可管理性
线程池的创建和使用?🚩
  • 方式一,通过 Executor 框架的工具类 Executors 来创建:
    • Executors.newFixedThreadPool():—池N线程
    • Executors.newSingleThreadExecutor():一个任务一个任务执行,一池一线程
    • Executors.newCachedThreadPool():线程池根据需求创建线程,可扩容,遇强则强
  • 方式二,通过**ThreadPoolExecutor**构造函数来创建(推荐):
    • 七个参数:
public ThreadPoolExecutor(
 	int corePoolSize, //核心线程数、常驻
 	int maximumPoolSize, // 最大线程数
  	long keepAliveTime, // 线程最大生命周期,存货时间。
 	TimeUnit unit, //时间单位                                 
 	BlockingQueue<Runnable> workQueue, //任务队列。当线程池中的线程都处于运行状态,而此时任务数量继续增加,则需要一个容器来容纳这些任务,这就是任务队列。
  	ThreadFactory threadFactory, // 线程工厂。定义如何启动一个线程,可以设置线程名称,并且可以确认是否是后台线程等。
 	RejectedExecutionHandler handler // 拒绝策略。由于超出线程数量和队列容量而对继续增加的任务进行处理的程序。
)
线程池拒绝策略
  1. AbortPolicy:拒绝并抛出异常。

  2. DiscardPolicy:直接忽略并抛弃当前任务。

  3. CallerRunsPolicy:使用当前调用的线程来执行此任务。如果执行程序关闭,则丢弃该任务

  4. DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。

线程池中有哪些常见的阻塞队列
  1. ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。

  2. LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。

  3. DelayedWorkQueue :是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的

  4. SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

线程池执行流程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 如果当前运行的线程数小于核心线程数,那么就会新建一个线程来执行任务。

  2. 如果当前运行的线程数等于或大于核心线程数,但是小于最大线程数,那么就把该任务放入到任务队列里等待执行。

  3. 如果向任务队列投放任务失败(任务队列已经满了),但是当前运行的线程数是小于最大线程数的,就新建一个线程来执行任务。

  4. 如果当前运行的线程数已经等同于最大线程数了,新建线程将会使当前运行的线程超出最大线程数,那么当前任务会被拒绝,饱和策略会调用拒绝策略

  • CompletableFuture:提供了函数式编程、异步任务编排组合(可以将多个异步任务串联起来,组成一个完整的链式调用)等能力

    AQS是什么?原理是什么?

    ​ AQS(AbstractQueuedSynchronizer)抽象的队列同步器,通过维护一个共享资源状态和一个先进先出( FIFO )的线程等待队列来实现一个多线程访问共享资源的同步框架。可以看成是一个实现同步锁的核心组件

    ​ AQS 为每个共享资源都设置一个共享资源锁,线程在需要访问共享资源时首先需要获取共享资源锁,如果获取到了共享资源锁,便可以在当前线程中使用该共享资源,如果获取不到,则将该线程放入线程等待队列,等待下一次资源调度。

    什么是分布式的 CAP 理论?
    • C:Consistency,一致性。所有节点在同一时间的看到的数据是一致的,副本保持一致。
    • A:Availability,可用性。指系统在任何时候都能对外提供服务,即系统随时能够响应用户请求,不会因为节点故障或其他原因而导致服务中断。
    • P:Partition tolerance,分区容错性。指系统在出现网络分区(节点之间失去联系)时,仍能够继续工作。分区容错性是必须的。

    CAP理论:

    ​ 由于网络或外在因素导致分区容错性不可避免,所以一个分布式系统只能同时满足其中的两个指标,从剩下的A、P抉择。例如,当出现网络分区时,如果要保证一致性,就必须停止对外服务,从而失去可用性;如果要保证可用性,就必须放弃一致性,从而可能导致不同节点之间数据不一致。

    ​ 因此,在分布式系统中,我们需要考虑的是当出现分区问题时,选择的是一致性还是可用性,即 CP 还是 AP

什么是分布式系统的BASE理论?

BASE理论是分布式系统中用于描述数据一致性的一个概念。它是一个缩写,分别代表:

  1. 基本可用(Basic Availability):分布式系统在出现故障时,依然能够保证系统的可用性,但可能会出现部分功能或性能降低的情况。
  2. 软状态(Soft State):由于分布式系统中各个节点的状态可能会有一定的延迟,系统允许在一定时间内存在数据不一致的情况。
  3. 最终一致性(Eventual Consistency):在一段时间内,分布式系统中的数据可能不一致,但最终会达到一致状态。这个过程可能需要一定的时间。

BASE理论和CAP理论之间的关系是:BASE理论实际上是对CAP理论的一种实践和解释



三、MySQL

🚩什么是数据库事务?事务的ACID特性?
  1. 数据库事务是指数据库管理系统(DBMS)中的一个操作序列,这些操作必须作为一个不可分割的单元执行,即要么全部执行成功,要么全部失败回滚。

  2. 事务的 ACID 特性指四个关键特征:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

    • 原子性:事务是一个原子操作,要么全部提交,要么全部回滚
    • 一致性:事务执行结束后,数据必须保持一致性状态。
    • 隔离性:数据库系统必须保证事务之间相互隔离,不会互相干扰。隔离级别不同,会影响到事务的并发性和数据一致性,比如出现脏读、不可重复读、幻读等问题。
    • 持久性:一旦事务提交,其所做的修改必须永久保存到数据库中。即使系统发生故障或宕机,数据也能够保持不变。
并发事务带来了哪些问题?
  • 脏读:指的是读到了其他事务未提交、并一定最终存在的数据。
  • 不可重复读:在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。也就是说在读取的过程中被修改
  • 幻读:当事务不是独立执行时发生的一种现象。例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。(不可重复读与幻读区别在于前者读到修改数据,后者读到插入数据)
SQL四种隔离级别?
  • READ-UNCOMMITTED(读取未提交) :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交) : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL默认隔离级别
  • SERIALIZABLE(可串行化) : 最高的隔离级别,完全服从 ACID 的隔离级别。
隔离级别
MySQL表锁、行锁;共享锁(S)、排他锁(X)?
  • 表级锁: MySQL 中锁定粒度最大的一种锁(全局锁除外),是针对非索引字段加的锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。

  • 行级锁: MySQL 中锁定粒度最小的一种锁,是 针对索引字段加的锁 ,只针对当前操作的行记录进行加锁。

  • 共享锁(S):又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取

  • 排他锁(X):又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取

意向锁是什么?有什么作用?
  1. MySQL的意向锁是一种表级锁,属于辅助锁的一种,主要用于帮助事务在请求表级锁之前了解其他事务对同一个表的锁定情况,以便更好的协调锁定请求。

    • 意向共享锁(IS):对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁」

    • 意向排他锁(IX):对某些记录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」

  2. 作用:为了快速判断表里是否有记录被加锁,协调事务对表的锁定,它本身并不会对表产生实际的锁定

    ​ 由于在对记录加独占锁前,先会加上表级别的意向独占锁,那么在加「独占表锁」时,直接查该表是否有意向独占锁,如果有就意味着表里已经有记录被加了独占锁,这样就不用去遍历表里的记录

MySQL日志都有哪些?
  1. 二进制日志(bin log):用于记录服务器上增删改时的记录日志。
  2. 重做日志(redo log):主要用于保证数据库的事务原子性和持久性
  3. 回滚日志(undo log):当MySQL发生事务回滚时,undolog会记录这些操作并将其写入磁盘。
  4. 错误日志(Error Log):记录MySQL服务器在启动、运行和关闭过程中发生的错误信息和警告信息。
  5. 一般查询日志(Query Log):记录MySQL服务器接收到的所有客户端发送的SQL语句,包括查询、更新、删除等操作语句。
  6. 慢查询日志(Slow Query Log):记录执行时间超过指定时间阈值的SQL语句,通常默认为10秒。
  7. 中继日志(Relay Log):在主从复制中,从服务器上的二进制日志被传输到从服务器的中继日志中,然后再被应用到从服务器的数据中。
  8. 事务日志(Transaction Log):记录MySQL服务器中正在进行的事务信息,包括事务的开始、提交、回滚等操作。
  9. InnoDB重做日志(InnoDB Redo Log):记录InnoDB存储引擎中正在进行的事务操作,在事务提交前被写入磁盘,用于在MySQL重启后恢复未提交的事务。
数据库三范式是什么?
  • 第一范式(1NF):确保每个表中的每个列都是原子的,不可再分的。每个表中的每个列都应该具有唯一的名称,并且每个单元格应该包含一个单一的值。这样可以避免数据的重复和冗余。
  • 第二范式(2NF):在第一范式的基础上,确保每个非主键列完全依赖于主键,而不是依赖于主键的一部分。
  • 第三范式(3NF):在第二范式的基础上,确保每个非主键列之间没有传递依赖关系
MySQL数据库系统架构?

插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。存储引擎是基于表的,而不是数据库。

  • 连接层:客户端和连接服务
  • 服务层:主要完成大多少的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行
  • 引擎层:存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。
  • 存储层:将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
MyISAM 和 InnoDB 有什么区别?
  1. MyISAM不支持事务;InnoDB支持,实现四个隔离级别
  2. MyISAM只支持级别的锁粒度,InnoDB支持级别
  3. MyISAM不支持外键,InnoDB支持
  4. MyISAM不支持MVVC(多版本并发控制)、InnoDB支持
  5. MyISAM是非聚集索引,InnoDB是聚集索引。都是B+树
  6. MyISAM性能不如InnoDB
  7. MyISAM不支持数据库异常崩溃后的安全恢复,InnoDB支持
什么是MVCC?

多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突MVCC的具体实现,主要依赖于数据库记录中的隐式字段undo log日志、readView。

索引是什么?以及优缺点

​ 索引(Index)是帮助MySQL高效的排好序的数据结构

  • 优点:①索引可以加快检索速度 ②保证数据的唯一性 ③降低数据排序的成本,降低了CPU的消耗
  • 缺点:①索引需要使用物理文件存储,也会耗费一定空间 ②创建索引和维护索引需要耗费许多时间,降低 SQL 执行效率
索引类型有哪些?
  • 普通索引:允许被索引的数据列包含重复的值
  • 唯一索引:可以保证数据记录的唯一性
  • 主键索引:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字primary key来创建
  • 联合索引:索引可以覆盖多个数据列
  • 全文索引:通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术(关键词搜索)
覆盖索引和联合索引是什么?讲一下索引的最左前缀匹配原则?

索引方式

  • 覆盖索引查询包含了所有查询需要的列的索引,查询时可以直接从索引中取到需要的数据,而不需要再回到表中查找,从而可以提高查询效率。

  • 联合索引:使用多个列组合起来作为一个索引,可以同时查询多个列,以提高查询效率

    最左前缀匹配原则:一个联合索引包含了多个列,使用最左优先的方式进行索引的匹配,以最左边的为起点任何连续的索引都能匹配上。

    比如建立索引A_B_C(三个列),则查询时,只有A、AB、ABC才能匹配成功,就是从左到右只要中间缺失一个,匹配就会不成功。

MySQL聚簇和非聚簇索引的区别是什么?
  • 聚集索引:聚集索引是索引结构和数据一起存放的索引。类似于字典的正文,当我们根据拼音直接就能找到那个字。
  • 非聚集索引:非聚集索引是索引结构和数据分开存放的索引。类似于根据偏旁部首找字,首先找到该字所在的地址,再根据地址找到这个字的信息。
MySQL 中的索引是怎么实现的?B+ 树是什么?
  • MySQL索引通过B+树实现的。
  • B+树是一种多叉平衡树,将所有数据存储在叶子节点,通过区间查找,快速定位数据(特点:快速定位数据、插入删除高效、IO次数少查询效率高等)
索引失效的情况?
  1. 复合索引时,未使用左列字段,不符合最左匹配原则
  2. like以%开头
  3. 在索引列上进行计算、函数、类型转换等操作;
  4. 使用select * 查询
  5. 查询条件中使用 or,且 or 的前后条件中有一个列没有索引,涉及的索引都不会被使用到;
  6. 字符串不加单引号
MySQL回表的概念

​ 如果一次索引查询就能获得所有的select 记录就不需要回表,如果select 所需获得列中有其他的非索引列,就会发生回表动作。(看查询是否包含全部索引字段,否则就需要回表)

MySQL怎么调优?
  1. 选择合适的存储引擎(InnoDB)
  2. 保证从内存读取数据
  3. 定期优化重建数据库
  4. 减少磁盘写入操作
  5. 充分使用索引
  6. 分析查询日志和慢查询日志
  7. 检查是否在扫描额外的记录
如果分析SQL语句,即MySQL执行计划?explain - EXPLAIN
  • 使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是
    如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈
explain

四、Redis

什么是 Redis?

​ Redis 是基于内存非关系型 Key-Value键值对数据库,支持多种数据结构,包括字符串、哈希、列表、集合、有序集合等。Redis具有高性能、高可用性、丰富的数据结构和灵活的扩展性等特点,被广泛应用于缓存、消息队列、计数器、排行榜、实时消息推送等场景。

以下是Redis的主要特点

  1. 高性能:Redis的数据存储在内存中,读写速度非常快,并且支持多种数据结构的操作。

  2. 高可用性:Redis支持主从复制和哨兵机制,可以实现高可用性和自动故障转移。

  3. 丰富的数据结构:Redis支持多种数据结构,包括字符串、哈希、列表、集合、有序集合等。

  4. 灵活的扩展性:Redis支持多种扩展方式,包括集群、分片等,可以满足不同规模和性能要求的应用场景。

  5. 支持事务:Redis支持事务,可以保证多个操作的原子性,避免数据不一致的情况。

  6. 支持Lua脚本:Redis支持通过Lua脚本实现复杂的数据操作,可以提高应用的灵活性和性能。

  7. 支持持久化:Redis可以将数据持久化到磁盘中,保证数据的安全和可靠性。

Redis 常见的5种数据结构和使用场景
  1. String:字符串,最基础的数据类型。

    缓存数据,如页面缓存、对象缓存等;计数器,如网站访问量、用户在线时长等;分布式锁,如限流、秒杀等。

  2. List:列表。

    ​ 常用于消息队列任务队列日志记录、列表

  3. Hash:哈希对象。

    对象存储,如用户信息、商品信息等;缓存数据,如页面缓存、对象缓存等。配置信息

  4. Set:集合。

    去重、标签、好友列表、点赞等。

  5. Sorted Set:有序集合,Set 的基础上加了个分值。

    排行榜分数排序,计时器,分数排名。

Redis种4种高级数据结构:
  1. HyperLogLog:通常用于基数统计。使一种基于概率算法的数据结构,用于统计元素的数量。

    ​ 统计网站的 UV、PV 等指标。

  2. Geo:一种存储地理位置信息的数据结构,可以进行地理位置相关的操作。

    存储商家、用户的地理位置信息,实现附近商家推荐、地理位置搜索等。

  3. Bitmap:一种特殊的字符串,可以进行位运算,用于存储二进制数据

    ​ 存储用户签到信息、在线状态等。

  4. Stream:一种类似于消息队列的数据结构,可以进行发布和订阅操作。

    ​ 实现消息队列、事件流等。

Redis为什么这么快?
  1. Redis 基于内存,内存的访问速度是磁盘的上千倍;
  2. 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环IO 多路复用
  3. 内置了多种优化过后的数据结构实现,性能非常高。
Redis是单线程还是多线程?

​ 在 redis 6.0 之前,redis 的核心操作是单线程的。

​ 因为 redis 是完全基于内存操作的,通常情况下CPU不会是redis的瓶颈,redis 的瓶颈最有可能是机器内存的大小或者网络带宽。

Redis事务?

是一种将多个命令请求打包的功能。再按顺序执行打包的所有命令,并且不会被中途打断。

①开始事务(MULTI);②执行事务(EXEC);③ DISCARD取消事务;④WATCH监视事务

Redis内存淘汰机制?
  • 定期删除:Redis 可以设置一个定时器,定期扫描键空间中的键,并删除已经过期的键。

  • 惰性删除:当一个键过期时,Redis 不会立即删除该键,而是等到该键被访问时再删除。

  • 内存淘汰策略:当 Redis 内存占用达到上限时,会根据内存淘汰策略来选择一些键进行删除,以腾出更多的内存空间。

缓存淘汰策略?
  1. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。

  2. volatile-ttl:删除马上要过期的key

  3. allkeys-lru:LRU算法移除最近最少使用的 key(最常用的)

  4. volatile-lru:从已设置过期时间的key,使用LRU算法进行删除

  5. allkeys-random:对所有key随机删除

  6. volatile-random:从已设置过期时间的key随机删除

  7. volatile-lfu:对所有设置了过期时间的key使用LFU算法进行删除

  8. allkeys-lfu:对所有key使用LFU算法进行删除

least recently used(lru):最近最少使用

least frequently used(lfu):频率最少使用

Redis和 memecache 有什么区别

两者都是非关系型内存键值数据库

  • 数据类型:Memcached 仅支持字符串类型,而 Redis 支持五种不同的数据类型
  • 数据持久化:Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。
  • 分布式:Memcached 不支持分布式,Redis Cluster 实现了分布式的支持。
  • 内存管理机制:在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。
Redis持久化有几种方式?
  • RDB:快照。简而言之,就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘等介质上;
  • AOF追加文件,则是换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。
🚩 Redis存在的问题,怎么解决?
  • 缓存穿透:大量请求查询不存在的key,根本不存在于缓存中,也不存在于数据库中,导致数据库无法负载。
    • 解决:①对查询结果为空 null 的情况也进行缓存、②加入短暂过期时间
  • 缓存击穿:请求的 key 对应的是热点数据该数据存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期
    • 解决:①针对热点数据进行预热、②请求数据库写数据到缓存之前,先加锁
  • 缓存雪崩:缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力
    • 解决:①限流、②过期时间不固定,设置固定后再加随机值
分布式缓存一致性?
  • 双写模式:就是写完数据库之后再去写缓存,保证最终缓存一致性
  • 失效模式:简言之就是写完数据库,不用写缓存,而是删缓存,等有请求进来读数据的时候,缓存中没有,就会查数据库,然后主动放到缓存里面。也叫触发主动更新;
🚩 Redis集群有哪些方案?(高可用的手段)
  • 主从复制:通过配置主节点和多个从节点,将主节点上的数据实时同步到从节点上,当主节点发生故障时可以切换到从节点继续提供服务。
  • 哨兵模式:官方推荐的高可用解决方案,它通过监控Redis实例的运行状态,当主节点发生故障时自动将一个从节点升级为新的主节点,并通知其他从节点更新配置。
  • 分片集群:通过将数据分片存储在多个节点上,实现数据的水平扩展和高可用性。当某个节点发生故障时,Cluster会自动迁移数据并重新平衡集群。

五、Spring

Spring概述

什么是Spring容器?有什么优势?

Spring是一个轻量级的**控制反转(IoC)面向切面(AOP)**的容器(框架)。

  1. 依赖注入:将应用程序中的不同组件之间的依赖关系交给 Spring 来管理,从而降低组件之间的耦合度,并方便后续的组件替换和维护。
  2. 面向切面编程:可以将应用程序的不同功能抽象成切面,并将这些切面与应用程序中的不同组件关联起来,从而降低了应用程序中的重复代码量,并提高了代码的可重用性可维护性
  3. 提供了 IoC 容器:Spring 框架提供了一个 IoC 容器,可以对应用程序中的不同组件进行管理,并支持对组件进行 AOP 增强,从而实现了应用程序中的组件解耦和高度可配置性
  4. 提供了多种技术整合方案:Spring 框架可以与其他的 Java 企业应用程序框架和技术进行整合,从而降低了技术整合的复杂度。
  5. 支持声明式事务管理:开发者可以通过配置来管理应用程序中的事务,从而简化了事务管理的过程。
  6. 便于测试:Spring 框架可以方便地进行单元测试和集成测试,提高了代码的可测试性和可靠性。
Spring 的两大核心概念是什么?
  • Ioc控制反转将创建对象的控制权交给Spring管理,可以使开发者更专注于业务逻辑的开发,需要使用到对象时,可以直接在IOC容器中取,降低了代码的复杂度,提高了系统的可维护性

  • AOP面向切面编程。是一种编程思想,它的核心思想是,将横切关注点从应用程序代码中分离出来,以便更好地实现模块化和复用。动态代理技术,将某段代码动态的切入到指定方法的指定位置

    ​ AspectJ AOP:基于编译期织入和运行期织入实现的,可以对类的方法和类成员变量进行拦截,功能更加强大

什么是 Spring 的依赖注入,依赖注入的基本原则以及好处?
  • 依赖注入DI):

    ​ 是Spring框架中的一种设计模式,用于实现控制反转,将应用程序中的不同组件之间的依赖关系交给 Spring 来管理,从而降低组件之间的耦合度,并方便后续的组件替换和维护。

  • 基本原则

    1. 高层模块不应该依赖低层模块。它们都应该依赖抽象。
    2. 抽象不应该依赖具体实现。具体实现应该依赖抽象。
  • 依赖注入的好处

    ①解耦(组件独立开发)②去重,代码复用,代码简洁
    ③更容易配置管理 ④提高代码的可测试性(单独测试)

什么是 Spring Bean?

简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象

🚩 Spring Bean 的生命周期?
image-20230305132311514
  • 实例化:在这个阶段,Spring容器根据配置或者注解等方式创建Bean的实例。可以通过构造函数实例化或者工厂方法实例化。
  • 属性赋值:在实例化后,Spring容器会为Bean的属性注入值,可以通过setter方法注入或者字段注入。
  • 初始化:在属性赋值完成后,Spring容器会调用Bean的初始化方法进行一些额外的初始化操作。可以通过实现InitializingBean接口或者在配置文件中指定初始化方法。
  • 使用:初始化完成后,Bean可以被正常使用,可以调用其方法进行业务逻辑处理。
  • 销毁:当容器关闭或者手动销毁Bean时,Spring容器会调用Bean的销毁方法进行资源释放等清理操作。可以通过实现DisposableBean接口或者在配置文件中指定销毁方法。
🚩 Spring Bean的作用域
  • ① singleton

    ​ 默认方式,Spring的IoC容器创建的Bean对象是单例的,不管接受多少请求,每个容器中只有一个bean的实例

  • ② prototype

    ​ 如果想让Spring的Bean对象以多例的形式存在,可以在bean标签中指定scope属性的值为:prototype,这样Spring会在每一次执行getBean()方法的时候创建Bean对象,调用几次则创建几次。

Spring 框架中的单例 Beans 是线程安全的么?

​ Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态,所以在某种程度上说Spring的单例bean时线程安全的。

@Autowired和@Resource有什么区别?
  • 所属不同
    • @Autowired是Spring框架的
    • @Resource是jdk标准注解,更加通用
  • 默认装配方式不同
    • @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
    • @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
  • 作用范围不同
    • @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
    • @Resource注解用在属性上、setter方法上。
@Component 和 @Bean 的区别是什么?
  • @Component 注解作用于,而@Bean注解作用于方法
  • @Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
  • @Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。
将一个类声明为 Bean 的注解有哪些?
  • @Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
Spring 的 AOP 有哪几种创建代理的方式

​ Spring 中的 AOP 目前支持 JDK 动态代理Cglib 代理。

​ 通常来说:如果被代理对象实现了接口,则使用 JDK 动态代理,否则使用 Cglib 代理。另外,也可以通过指定 proxyTargetClass=true 来实现强制走 Cglib 代理。

JDK 动态代理和 Cglib 代理的区别
  • JDK 动态代理本质上是实现了被代理对象的接口,而 Cglib 本质上是继承了被代理对象,覆盖其中的方法。
  • 实现原理:JDK动态代理是基于Java的反射机制实现的;Cglib代理是基于字节码技术实现的。
  • 适用范围:JDK动态代理适用于对指定接口进行代理的场景;Cglib代理适用于对类进行代理的场景,因为它可以代理没有实现接口的类
  • 性能表现:JDK动态代理在代理接口时,性能比Cglib代理要好,因为它是基于Java的反射机制实现的;而Cglib代理在代理类时,性能比JDK动态代理要好,因为它是基于字节码技术实现的。
Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

​ Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

​ 如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

AspectJ 定义的通知类型有哪些?
  • Before(前置通知):目标对象的方法调用之前触发

  • After (后置通知):目标对象的方法调用之后触发

  • AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发

  • AfterThrowing(异常通知) :目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。

  • Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法

  • 多个切面的执行顺序如何控制?
    • 通常使用@Order 注解直接定义切面顺序
    • 实现Ordered 接口重写 getOrder 方法。

Spring其他

Spring常用注解?
  1. @Controller:用于标记控制器类,告诉 Spring 将其识别为一个控制器。
  2. @Component:用于将类标记为 Spring 容器中的一个组件,被标记的类会自动被扫描并注册为 Bean。
  3. @Configuration:用于标记配置类,告诉 Spring 容器这是一个配置类,可用于定义 Bean。
  4. @Service:用于标记服务类,告诉 Spring 将其识别为一个服务,常用于业务逻辑层的实现类。
  5. @Autowired:用于自动装配依赖对象,在 Spring 容器中查找匹配的 Bean,并将其注入到被标注的属性中。
  6. @RequestMapping:用于将 HTTP 请求映射到控制器的处理方法上,可用于指定 URL、请求方法、请求参数等。
  7. @ResponseBody:用于将控制器方法的返回值转化为 HTTP 响应体,常用于返回 JSON 格式的数据。
  8. @Transactional:用于指定事务的属性,使方法在调用时自动开启事务。
  9. @Bean:用于将方法返回的对象注册为 Spring 容器中的一个 Bean。
  10. @Value:用于从配置文件中读取属性值,并将其注入到被标注的属性中。

🚩 Spring 循环依赖的问题

什么是循环依赖
  • 从字面上来理解就是A依赖B的同时B也依赖了A,陷入循环当中
什么情况下循环依赖可以被处理?
  • 出现循环依赖的Bean必须要是单例(作用域为singleton

  • 依赖注入的方式不能全是构造器注入的方式(可以解决setter方法的循环依赖)

什么是三级缓存?
  1. 一级singletonObjects 单例池, 存放 - 完全初始化好的Bean
  2. 二级earlySingletonObject 提前曝光的单例对象的cache
  3. 三级:**singletonFactories**存放 - bean 工厂对象Bean 对象的工厂
Spring是如何解决的循环依赖?

​ Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects三级缓存为早期曝光对象工厂(singletonFactories)

​ Spring只能解决setter方法注入的单例bean之间的循环依赖。ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可,从而解决循环依赖问题。

spring循环依赖步骤

Spring事务

Spring 管理事务的方式有几种?
  • 编程式事务 : 通过编写代码的方式来实现事务的管理(不推荐使用) : TransactionTemplate或者 TransactionManager 手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
  • 声明式事务 : 在 XML 配置文件中配置,实际是通过 AOP 实现
  • 注解式事务(推荐使用):使用注解的方式进行事务的管理。 (基于@Transactional 的全注解方式使用最多)
🚩 Spring 事务传播行为?
  1. REQUIRED:**【有就加入,没有就新建】**支持当前事务,如果不存在就新建一个(默认)
  2. SUPPORTS:**【有就加入,没有则放弃】**支持当前事务,如果当前没有事务,就以非事务方式执行
  3. MANDATORY:**【有就加入,没有就抛异常】**必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常
  4. REQUIRES_NEW:**【无论是否有事务,都新建一个,将之前的挂起】**开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起
  5. NESTED:**【有就嵌套一个,没有就新建】**如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。
  6. NOT_SUPPORTED:**【不支持事务,有就挂起】**以非事务方式运行,如果有事务存在,挂起当前事务
  7. NEVER:**【不支持事务,有就抛异常】**以非事务方式运行,如果有事务存在,抛出异常
Spring 事务隔离级别?

等同于数据库隔离界别

Spring 事务的实现原理?

Spring 事务的底层实现主要使用的技术:AOP(动态代理) + ThreadLocal + try/catch。

  • 动态代理:基本所有要进行逻辑增强的地方都会用到动态代理,AOP 底层也是通过动态代理实现。

  • ThreadLocal:主要用于线程间的资源隔离,以此实现不同线程可以使用不同的数据源、隔离级别等等。

  • try/catch:最终是执行 commit 还是 rollback,是根据业务逻辑处理是否抛出异常来决定。

Spring用到哪些设计模式?

  • 工厂设计模式 : Spring 使用工厂模式通过 BeanFactoryApplicationContext 创建 bean 对象。

  • 代理设计模式 : Spring AOP 功能的实现。

  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。

  • 模板方法模式 : Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。

  • 装饰器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。Spring中类名中带有:Decorator和Wrapper单词的类,都是装饰器模式。

  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。

  • 适配器模式 : Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller

  • **策略模式:**策略模式是行为性模式,调用不同的方法,适应行为的变化 ,强调父类的调用子类的特性 。

Spring事务什么时候失效?
  1. 数据库不支持事务
  2. 事务方法未被Spring管理
  3. 方法不是public修饰
  4. 同一类中方法调用:同一类中方法调用,有一个方法未被注解标识
  5. 未配置事务管理器
  6. 方法的事务传播类型不支持事务
  7. 不正确的捕获异常
  8. 错误的标注异常类型

SpringMVC

对SpringMVC的了解?

MVC模型(Model)视图(View)、**控制器(Controller)**的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。

​ Spring MVC 是当前最优秀的 MVC 框架,Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。

Spring MVC 的核心组件有哪些?
  1. DispatcherServlet核心的中央处理器,统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
  2. HandlerMapping处理器映射器,根据请求的url、method等信息查找Handler,即控制器方法 ,并会将请求涉及到的拦截器和 Handler 一起封装。
  3. HandlerAdapter处理器适配器,根据 HandlerMapping 找到的 Handler ,适配执行对应的 Handler
  4. Handler请求处理器,处理实际请求的处理器。
  • ViewResolver视图解析器,根据 Handler 返回的逻辑视图 / 视图,进行解析并渲染得到真正的视图,并传递给 DispatcherServlet 响应客户端
  • View视图
SpringMVC工作原理?🚩
img
  1. 客户端(浏览器)发送请求, DispatcherServlet拦截请求。调用doDispath进行处理

  2. DispatcherServlet 根据请求信息调用 HandlerMappingHandlerMapping 根据 uri 去匹配查找能处理的 Handler(也就是我们平常说的 Controller 控制器) ,并会将请求涉及到的拦截器和 Handler 一起封装。

  3. DispatcherServlet 调用 HandlerAdapter适配执行 Handler

  4. Handler 完成对用户请求的处理后,会返回一个 ModelAndView 对象给DispatcherServlet

  5. ViewResolver 会根据逻辑 View 查找实际的 View

  6. DispaterServlet 把返回的 Model 传给 View(视图渲染)。

  7. View 返回给请求者(浏览器)

统一异常处理器怎么做?

推荐使用注解的方式统一异常处理,具体会使用到 @ControllerAdvice + @ExceptionHandler 这两个注解 。

SpringBoot

为什么要用SpringBoot?

​ 能快速创建出生产级别的Spring应用,是整合Spring技术栈的一站式框架,简化Spring技术栈的快速开发脚手架。无需关注版本号,自动版本仲裁

SpringBoot的优点?
  • 创建独立Spring应用
  • 内嵌web服务器
  • 自动starter依赖,简化构建配置
  • 提供生产级别的监控、健康检查及外部化配置
  • 无代码生成、无需编写XML
🚩 SpringBoot 自动装配原理?
  1. Spring Boot使用了大量的注解来实现自动装配@SpringBootApplication注解用于标识一个主应用程序类。该注解对三个注解进行封装(@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan)
  2. 其中**@EnableAutoConfiguration是实现自动化配置的核心注解**。 首先,根据classpath中的依赖和条件进行判断,加载满足装配条件的自动配置类。
  3. 实例化自动配置类中的**@Bean注解定义的组件**,并添加到Spring容器中。
GET和POST的区别?
  1. url可见性

    • GET请求:url参数可见;

    • POST请求:url参数不可见

  2. 数据传输上

    • GET请求:通过拼接url进行传递参数;

    • POST请求:通过body体传输参数

  3. 缓存性

    • GET请求:可以被缓存,也会保留在浏览器的历史记录中

    • POST请求:不可以缓存,也不好保留在浏览器的历史记录中

  4. 后退页面的反应

    • GET请求:页面后退时,不产生影响

    • POST请求:页面后退时,会重新提交请求

  5. 传输数据的大小

    • GET请求:一般传输数据大小不超过2k-4k(根据浏览器不同,限制不一样,但相差不大)

    • POST请求:传输数据的大小根据php.ini 配置文件设定,也可以无限大。

  6. 数据包

    • GET请求:产生一个TCP数据包。对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
    • POST请求:产生两个TCP数据包。对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。

适用场景

  1. GET 请求适用于获取数据,如浏览网页、搜索等。因为 GET 请求参数以明文形式传输,容易被拦截和篡改,所以不适用于提交敏感信息的操作。
  2. POST 请求适用于提交数据,如登录、注册、发布内容等。因为 POST 请求参数在请求体中传输,相对安全一些,可以提交敏感信息,但是需要注意参数加密和防止 CSRF 攻击等问题。

六、MyBatis

#{} 和 ${} 的区别是什么? 🚩
  • **#{}带引号,${}**不带引号;
  • **#{}**可以防止SQL注入;
  • **${}**常用于数据库表名、order by子句;
  • 一般能用**#{}就不要使用${}**
如何解决JavaBean名称与数据库名称不对应?
  • 使用<resultMap>自定义结果集
<resultMap id="BaseResultMap" type="com.atguigu.springcloud.bean.Payment">
    <id column="id" property="id" jdbcType="BIGINT"/>
    <result column="serial" property="serial" jdbcType="VARCHAR"/>
	<result property="userName" column="user_name"></result>
	<result property="password" column="password"></result>
	<result property="age" column="age"></result>
	<result property="sex" column="sex"></result> 
</resultMap>
  • <id> 指定主键列
  • <result> 普通列
    property 指定JavaBean的属性名
    column 指定数据库的列名
  • jdbcType 数据库类型
    BIGINT
    VARCHAR
    ……
MyBatis执行流程?

① 读取MyBatis配置文件: mybatis-config.xml加载运行环境和映射文件
② 构造会话工厂SqlSessionFactory
③ 会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)
④ 执行SQL语句
参数映射和结果映射
⑥ 提交事务
⑦ 关闭SqlSession

🚩 MyBatis是否支持延迟加载?延迟加载的原理是什么?

1、是否支持延迟加载

​ 延迟加载其实就是在需要用到数据时才进行加载而非初始化时就加载信息

​ Mybatis支持一对一关联对象一对多关联集合对象的延迟加载 。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false默认是关闭

2、延迟加载原理

​ ①使用 CGLIB 创建目标对象的代理对象
​ ②当调用目标方法时,进入拦截器invoke()方法,发现目标方法null值,先保存好的关联对象的 SQL
​ ③获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了

🚩说一下 mybatis 的一级缓存和二级缓存?
  • 一级缓存Session级别的缓存(线程级别,本地),默认开启,当第一次查询数据库时,对查询结果进行缓存,之后如果执行相同的查询则直接从一级缓存中取值,无需再访问数据库;
  • 二级缓存SessionFactory级别的缓存(全局范围),默认关闭。当进行sql语句查询时,先查看一级缓存,如果不存在,访问二级缓存,降低数据库访问压力。
MyBatis有哪些执行器(Executor)?作用范围?如何指定?

1、三种基本的Executor执行器

  • SimpleExecutor

    每执行一次Update或Select,就开启一个Statement对象,用完立刻关闭Statement对象。

  • PauseExecutor

    执行Update或Select,以sql做为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而且放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。

  • BatchExecutor

    执行Update,将所有sql通过addBatch()都添加到批处理中,等待统一执行executeBatch(),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

2、作用范围:

  • Executor的这些特点,都严格限制在 SqlSession 生命周期范围内

3、指定一种执行器:

  • 在 MyBatis 配置文件中,可以指定默认的 ExecutorType 执行器类型,也可以手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自行自路,别后无书

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值