Java实时更新面试资料大全

Java面试题(2024)

JavaSE基础

JVM和JRE和JDK的关系?

JVM:jvm是java虚拟机,java程序需要运行在虚拟机上的,不同平台有自己的虚拟机,因此java语言可以实现跨平台;

JRE:java runtime environment包括java虚拟机和java程序所需的核心库等;

JDK:jdk是提供给java开发人员使用的,其中包含了java的开发工具,也包括了jre;

Java为什么能跨平台,原理是什么?

Java跨平台性指的是通过Java语言编写的应用程序可以在不同的系统平台上运行,这些平台包括常见的操作系统如Windows、Linux、MacOS等。这种跨平台性主要归功于Java虚拟机(JVM)和Java的编译方式。

Java跨平台的原理如下:

首先,Java程序员使用Java编译器将源代码编译成Java字节码文件(.class文件)。这些字节码文件是底层实现,并不是针对特定操作系统的,因此,操作系统的类型并不会影响Java代码的执行。

然后,JVM(Java虚拟机)负责将Java字节码文件解释成特定的机器指令。JVM可以在不同的操作系统上运行,并且能够解释和执行这些Java字节码文件。由于JVM的存在,Java程序可以在不同的操作系统上运行,而无需对不同的操作系统做出修改。

最后,这些机器指令在不同的操作系统上执行,从而实现Java程序的跨平台运行。

Java有那些数据类型?

基本数据类型:byte、short、int、long、float、double、char、boolean
分别占用了多少字节:byte 1、short 2、int 4、long 8、float 4 、double 8、char 2、boolean 1
引用数据类型:类(class)、接口(interface)、数组([])
Java有那些数据类型?
基本数据类型:byte、short、int、long、float、double、char、boolean

引用数据类型:类(class)、接口(interface)、数组([])

int和integer的区别,自动拆箱和自动装箱是什么,为什么用int还要用integer

int是基本数据类型
integer是java的一个类。int的包装类,包装类主要用于基本数据类型和对象之间的转换,使得基本数据类型可以具有对象的特征()继承和多态等
int存储的是整数值,而integer对象则是整数的应用
存储的地方不一样

自动装箱是 Java 5 引入的一项功能,它允许程序员在需要 Integer 对象的地方直接使用 int 值,Java 编译器会自动将 int 值转换为 Integer 对象。例如:Integer i = 10; 实际上会进行自动装箱,相当于 Integer i = Integer.valueOf(10);
自动拆箱则是相反的过程,它允许程序员在需要 int 值的地方直接使用 Integer 对象,Java 编译器会自动调用 Integer 对象的 intValue() 方法来获取其整数值。例如:int j = i; 实际上会进行自动拆箱,相当于 int j = i.intValue();

为什么用 int 还要用 Integer:

  • 泛型:Java 泛型不支持基本数据类型,只支持对象类型。因此,当我们需要在泛型中使用整数时,必须使用 Integer 而不是 int。
  • null 值:int 类型的变量不能赋值为 null,而 Integer 类型的对象可以。这在某些情况下(如数据库交互)可能很有用。
  • 工具方法:Integer 类提供了许多有用的静态方法,如 parseInt()、valueOf()、toBinaryString()
    等,这些方法对于处理整数非常有用。
  • 缓存:Java 为 Integer 类型的值在 -128 到 127 之间(包括两端)的范围内进行了缓存。这意味着在这个范围内的
    Integer 对象是不可变的,并且对于相同的值,Java 会返回相同的 Integer 对象引用。这可以提高性能和内存使用效率。
  • 面向对象编程:使用 Integer 可以使整数具有对象的特性,如继承、多态等,这在某些面向对象的编程场景中可能很有用。

访问修饰符有那些,有什么区别?

public:公开的,对所有的类可见。

private:私有的,在同一类内可见。

protected:受保护的,在同一包内的类和所有子类可见。

default(就是缺省,什么都不写的):默认,在同一包可见,不适用任何修饰符

Java中&和&&的区别?

&和&& 都是逻辑运算符,但&&又叫短路运算符,当使用&运算符时,无论左侧条件的值时ture还是false,右侧都会计算,而使用&&时,如果左侧条件值为false,则右侧不会计算。
另外&还可以用作位运算符,当&两边的表达式不是Boolean类型的时候,&表示按位操作。

		int i = 0;
        if (i == 3 && ++i > 0) {
            System.out.println("i1= " + i);
        } else {
            System.out.println("i2= " + i);
        }
        // 结果为:i2= 0

        int a = 0;
        if (a == 3 & ++a > 0) {
            System.out.println("a1= " + a);
        } else {
            System.out.println("a2= " + a);
        }
        // 结果为:a2= 1

Java中i++和++i的区别?

		int i = 5;
        int j = i++; 
        // j的值为5,i的值增加到6  先赋值,在加
        System.out.println("i = " + i + "\t j = " + j);
        // 结果为:i = 6 j = 5

        int q = 5;
        int t = ++q;  
        // q的值为6,t的值增加到6  先加,在赋值
        System.out.println("q = " + q + "\t t = " + t);
        // 结果为:i = 6 j = 6

Java中final、finally、finalize区别?

final:final可以修饰属性,表示这个属性不能被重新赋值, 可以修饰方法,表示这个方法不能被重写,可以修饰类,表示这个不能被继承;

finally:一般在try-catch中使用,在处理异常的时候,我们将一定会执行的代码放入finally代码块中

finalize:是一个方法,属于object类的一个方法;

面向对象的三大特征?

1、封装 2、继承 3、多态
封装:封装是面向对象的核心思想,它将对象的属性和行为封装起来,不需要让外界知道具体实现细节。封装思想通过将对象的属性和行为包装到对象中,这个对象只对外公布需要公开的属性和行为,而这个公布也是可以有选择性的公布给其它对象。在Java中,能使用private、protected、public三种修饰符或不用(即默认defalut)对外部对象访问该对象的属性和行为进行限制。

继承:继承性主要描述的是类与类之间的关系,通过继承,可以在无需重新编写原有类的情况下,对原有类的功能进行扩展。子对象可以继承父对象的属性和行为,亦即父对象拥有的属性和行为,其子对象也就拥有了这些属性和行为。这非常类似大自然中的物种遗传。

多态:顾名思义,表示一个对象具有多种状态,多态性指的是在程序中允许出现重名现象,它指在一个类中定义的属性和方法被其它类继承后,它们可以具有不同的数据类型或表现出不同的行为。这使得同一个属性和方法在不同的类中具有不同的语义。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。

Java中的构造器
构造器是不能重写的,而是可以实现重载,构造器可以使用private修饰、也可以抛出异常

Java中抽象类和接口的对比?

参数 抽象类 接口
声明 抽象类使用abstract关键字声明 接口使用interface关键字声明
实现 子类使用extends关键字来继承抽象类 子类使用implements关键字实现接口
构造器 抽象类可以有构造器 接口不能有构造器
多继承 一个类最多只能继承一个抽象类 一个类可以实现多个接口

  1. 定义
    抽象类:抽象类是一个不完全的类,即它可能包含一些没有具体实现的方法(抽象方法)。抽象类不能被实例化,即不能创建抽象类的对象。但抽象类可以包含非抽象方法和抽象字段。
    接口:接口是一个完全抽象的类,它只包含抽象方法和常量(在Java 8及以后版本中,接口还可以包含默认方法和静态方法)。接口不能被实例化,但它可以定义对象应该遵循的契约。
  2. 实现方式
    抽象类:一个类可以通过extends关键字继承一个抽象类。子类必须为抽象类中的所有抽象方法提供具体实现,除非子类本身也是抽象的。
    接口:一个类可以通过implements关键字实现一个或多个接口。实现接口的类必须为接口中的所有方法提供具体实现(不包括Java 8及以后版本中的默认方法和静态方法)。
  3. 方法和字段
    抽象类:可以包含抽象方法和非抽象方法,也可以包含字段(包括静态和非静态字段)。
    接口:只能包含抽象方法(Java 8及以后版本还包含默认方法和静态方法)和常量(即静态final字段)。
  4. 多重继承
    抽象类:Java不支持多重继承(即一个类不能继承多个非接口类),因此一个类只能继承一个抽象类。
    接口:Java支持多重实现,即一个类可以实现多个接口。
  5. 设计目的
    抽象类:主要用于代码重用和类型层次结构的设计。抽象类提供了一种模板或框架,子类可以根据需要添加或覆盖方法。
    接口:主要用于定义对象的行为和属性,以及实现多重继承。接口定义了对象应该遵循的契约,而不需要关心对象的具体实现。
  6. 访问修饰符
    抽象类:可以使用任何访问修饰符(public、protected、默认或private),但通常使用public或默认(即包级别)访问修饰符。
    接口:总是隐式地具有public和abstract修饰符,因此它们必须是公开的,并且所有的方法都必须是抽象的。
  7. 默认值
    抽象类:对于非抽象方法,抽象类可以提供方法的默认实现。
    接口:在Java 8及以前版本中,接口中的方法都没有默认实现。但在Java 8及以后版本中,接口可以包含默认方法和静态方法,这些方法具有默认实现。
  8. 静态和非静态成员
    抽象类:可以包含静态和非静态成员(字段、方法和内部类)。
    接口:在Java 8及以前版本中,接口只能包含静态常量。但在Java 8及以后版本中,接口可以包含静态方法,但不能包含非静态字段和非静态内部类。

java抽象类必须有抽象方法吗
Java抽象类不一定必须有抽象方法,一个抽象类可以包含非抽象的方法

java中有抽象方法那么就一定是一个抽象类,这样说法正确吗
是正确的,在java中一个类包含一个抽象方法或多个抽象方法,那么这个类就必须声明为抽象类

java中定义一个抽象方法
public abstract void AbstractMethodName();
在对应的子类重写方法,并编写相关的逻辑代码

Java中重写和重载的区别?

重写:
重写就会在子类和父类中,子类重写了父类的方法,方法名、参数列表、返回类型相同

重载:
重载就是在一个类中方法名相同,但是参数列表、参数顺序不同就是重载

Java中普通类和抽象类有哪些区别?

普通类不能包含抽象方法,抽象类可以包含抽象方法;

普通类可以实例化,抽象了不能实例化

请你谈谈什么是泛型?

泛型在jdk1.5加入的新特性,其本质上就是一个参数类型,这种参数类型可以用在类、接口和方法中,分别称为泛型类、泛型接口、泛型方法,最常用的就是在集合中使用,在jdk1.5之前没有泛型,那么list集合 没有规定类型,那么add的时候加入objec的值计算的时候导致类型转换异常,现在通过list确定了类型,增加了安全性以及提高了效率。

什么是泛型擦除

在生成字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程就叫类型擦除

泛型擦除的原因

原因一:JDK1.5及1.5之前都是没有泛型的概念的,JDK1.5之后引入了泛型的概念并为了与之前的JDK版本兼容,所以引入了泛型擦除的概念。
原因二:若对每个泛型类型都生成不同的目标代码,现有10个不同泛型的List,就要生成10份字节码,这样会造成不仅造成代码膨胀,而且一份字节码对应一个Class对象,占据大量的内存。

Java中精度为什么会丢失?

在Java,计算小数点位数或者保存小数点到数据库,会把小数点计算为二进制的形式,有点小数会有很长的小数,从而截取,有点小数一直循环计算无限制,那么计算出来的预估值和原来的值比较,如果一样就是精度没丢失,值相差越大那么精度就丢失越高,越小精度就小,这就是为什么精度会丢失

字节字符编码讲解

https://www.bilibili.com/video/BV1n3411Q7gi?p=14&vd_source=9515754a0187a3d04d7040d2fefb43e8

Java中IO流分为几种?

按照流的流向分,可以分为输入流和输出流

按照操作单元划分,可以划分为字节流和字符流

按照流的角色划分为节点流和处理流

Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是
字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字
符输出流。

字节流用于图片视频传输,字符流用于文本文件传输
文件流
缓冲流

什么是IO?

IO简称output和intput,输入输出流,用于计算机系统从外部获取数据并将结果输出的过程,包括读取文件,网络通讯、打印和显示等

BIO,NIO,AIO有什么区别?

BIO:Block IO 同步阻塞式IO,就是我们平常使用的传统IO,它的特点就是简单使用方便,并发处理能力低

NIO:Non IO 同步非阻塞IO ,就是传统IO的升级,客户端和服务器通讯,实现了多路复用

AIO: Asynchronous IO 是NIO的升级,也叫NIO2,实现了异步非堵塞IO,异步IO的操作基于事件和回调机制

Files的常用方法都有哪些?

Files.exists(): 检测文件是否存在

Files.createFile(): 创建文件

Files.createDirectory(): 创建文件夹

Files.delete(): 删除一个文件或目录

注:java的delete删除不经过回收站,直接删除

Files.copy(): 复制文件

Files.move(): 移动文件

Files.size(): 查看文件个数

Files. read():读取文件

Files. write():写入文件

什么是反射机制?

java反射机制就是在运行状态中,对于一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个属性和方法,这种动态的获取信息的方法称为java反射机制
静态编译和动态编译

Java的反射机制的应用场景有哪些?

在我们项目开发的过程中,基本上很少会直接使用到反射机制,但实际上有很多设计、开发都会与反射机制有关,
例如动态代理设计模式也采用了反射机制,还有我们日常所用的Spring/hibernate等框架也大量使用到了反射。
举例:
1、在我们使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序
2、Spring框架也用到很多反射,最经典的就是xml的配置模式,Spring通过xml配置模式装置Bean的过程:
1)将程序内所有xml或properties配置文件加入内存中;2)java类解析里面xml和properties文件的内容,得到实体类的字节码字符串以及其他相关的属性信息;3)使用反射机制,根据这个字符串获得某个类的Class实例;4)动态配置实例的属性

Java 获取反射的三种方法?

1、通过new对象实现反射机制
2、通过路径实现反射机制
3、通过类名实现反射机制

public class Student {
		private int id;
		String name;
		protected boolean sex;
		public float score;
}
public class Get {
	//获取反射机制三种方式
	public static void main(String[] args) throws ClassNotFoundException{
		//方式一(通过建立对象)
		Student stu = new Student();
		Class classobj1 = stu.getClass();
		System.out.println(classobj1.getName());
		//方式二(所在通过路径-相对路径)
		Class classobj2 = Class.forName("fanshe.Student");
		System.out.println(classobj2.getName());
		//方式三(通过类名)
		Class classobj3 = Student.class;
		System.out.println(classobj3.getName());
	}
}

反射的优缺点:

优点:运行期间类型的判断,动态的加载类,提高代码的灵活度
缺点:性能比直接的Java代码要慢的多

String类为什么是final的

1、安全性:String 类在 Java 中被广泛使用,并且经常作为参数传递、返回值以及用于各种基础操作。如果 String 类不是 final 的,那么它可能会被恶意代码或不受信任的代码所继承,并覆盖其关键方法(如 hashCode()、equals() 等),这可能导致难以预料的行为和安全问题。通过将 String 类声明为 final,Java 确保了 String 类的行为在运行时是不可变的,从而提高了安全性。

2、不可变性:String 类的一个关键特性是它的不可变性。一旦一个 String 对象被创建,它的内容就不能被改变。这种不可变性使得 String 对象在多线程环境中可以安全地共享,而无需额外的同步机制。如果 String 类不是 final 的,那么它的子类可能会添加改变 String 内容的方法,从而破坏其不可变性的保证。

3、性能优化:由于 String 的不可变性,Java 运行时可以对 String 对象进行各种优化,例如字符串常量池(String Constant Pool)。当创建一个字符串字面量(如 “hello”)时,Java 运行时会首先检查字符串常量池中是否已存在相同的字符串。如果存在,则返回该字符串的引用;否则,创建一个新的字符串对象并将其添加到字符串常量池中。这种优化减少了不必要的内存分配和垃圾回收开销。如果 String 类不是 final 的,那么这些优化可能无法安全地应用于 String 的子类。

4、简化设计:将 String 类设计为 final 可以简化其设计和实现。由于 String 类不能被继承,因此其开发者可以专注于实现 String 类的核心功能,而无需考虑子类可能引入的复杂性和不确定性。

String类的常用方法有那些?

indexOf():返回指定字符的索引

charAt():返回指定索引处的字符

replace():字符串替换

trim():去除字符串两端的空格

split():分割字符串,返回一个分割后的数组

getBytes():返回字符串的byte类型数组

length():返回字符串长度

toLowerCase():将字符串转成小写字母

toUpperCase():将字符串转成大写字母

substring():截取字符串

equals():字符串比较

String和StringBuffer、StringBuilder的区别是什么?

可变性:
String类中使用字符数组保留了字符串,privatr finalchar value[],所以String对象是不可变的;stringbuffer与stringbuilder都是使用字符串数组保存,这两种对象都会可变的
线程安全性:
String因为对象是不可变的,是线程安全的
Stringbuffer使用同步锁,线程是安全的
Stringbuilder没有使用任何锁,是线程不安全的
性能:
每次对String对象作出改变的时候,都会生成一个新的对象,让指针指向新的string对象
stringbuilder和stringbuffer每次都会对对象本身进行操作,不会生成新的对象
对于三者使用的总结
如果要操作少量的数据用 = String
单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

内部类介绍

内部类
局部内部类、成员内部类、匿名内部类、静态内部类

重写equals为什么需要重写hashcode

保持一致性:根据Java的规范,如果两个对象通过重写的equals方法判断为相等,那么它们的hashCode也应该相同。这是因为哈希集合(如HashMap, HashSet等)依赖于这两个方法的这种一致性来确保数据的正确存储和检索。如果违反这一规则,哈希集合可能会出现错误,如错误的元素比较或数据丢失。12
提高效率:重写hashCode方法可以显著提高哈希集合的性能。通过计算对象的哈希码,可以更快地确定对象是否已存在于集合中,而无需每次都进行完整的equals比较。2
避免逻辑错误:如果不重写hashCode,即使程序在运行时不会报错,逻辑上也可能出现错误。例如,两个在业务逻辑上相等的对象可能会因为不同的哈希码而被错误地存储或检索。

Java集合面试

什么是集合

集合就存储数据的容器
在这里插入图片描述

集合和数组的区别?

按长度:集合的长度是可变的,而数组的长度是固定的
按存储类型:集合可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型
按存储方式:数组存储的元素必须都是同一个数据类型,集合存储的对象可以不同数据类型

常见的集合?

Map接口和Collection接口是所有集合框架的父接口
1、Collection接口的子接口包括,Set接口和List接口
2、Map接口的实现类主要有:HashMap、TreeMap、Hashtable、CouncurrentHashMap、Properties
3、Set接口的实现类有:HashSet、TreeSet、LinkedHahSet等
4、List接口的实现类有:ArrayList、LinkedList、Stack、Vector

// 多线程情况下不安全不推荐使用的List集合
ArrayList<String> aList = new ArrayList<>();// 底层动态数组,线程不安全
Vector<String> vector = new Vector<>();// 底层列表  使用synchronized 线程安全  (被淘汰)
Stack<String> stack = new Stack<>();// 底层后进先出的栈  使用synchronized 线程安全 (被淘汰)
LinkedList<String> linkedList = new LinkedList<>();// 底层双向列表,线程不安全
// 多线程情况下安全推荐的List集合
List<String> synchronizedList = Collections.synchronizedList(aList);
CopyOnWriteArrayList copy = new CopyOnWriteArrayList();// 使用ReentranLock线程安全


// 多线程情况下不安全不推荐使用的Set集合
HashSet<Integer> set = new HashSet<>();// HashSet底层HashMap
LinkedHashSet<String> lhs = new LinkedHashSet<>();// HashSet底层链表和哈希表
TreeSet<String> ts = new TreeSet<>();// HashSet底层红黑树
// 多线程情况下安全推荐的Set集合
Set<String> synSet = Collections.synchronizedSet(new HashSet<>());
CopyOnWriteArraySet copySet = new CopyOnWriteArraySet();// 使用ReentranLock线程安全


// 多线程情况下不安全不推荐使用的Map集合
Map<String, String> hashMap = new HashMap<>(); // 非线程安全
Map<String, String> hTable = new Hashtable<>();// 线程安全 synchronized 修饰 (被淘汰)
Map<String, String> lHashMap = new LinkedHashMap<>();// 非线程安全
Map<String, String> tMap = new TreeMap<>(); // 非线程安全
// 多线程情况下安全推荐的Map集合
Map<String, String> cHashMap = new ConcurrentHashMap<>(); // 线程安全 synchronized 修饰

list和set和map的区别?

List:一个有序(元素存入集合的顺序和取出的顺序一样)容器,元素可以重复,可以插入多个null元素,元素都有索引,常见的实现类有ArrayList、LinkedList、Vector
Set:一个无序(元素存储集合没有顺序)容器,元素不可以重复,只能存入一个null元素,常见的实现类有HashSet、LinkedHashSet、TreeSet
Map:map是一个键值对集合,key-value形式,key无序,唯一;value不要求有序,允许重复
常见的Map的实现类有:HashMap、TreeMap、HashTable、LinkedHashMap

Java集合中快速失败机制“fail-fast”?

fail-fast是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。
例如:假设存在两个线程,线程1通过遍历集合A只的元素,在某个时候线程2修改了集合A的结构,那么这个时候就会抛出ConcurrentModificationException 异常(并非修改异常),从而产生fail-fast机制
原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个
modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。
每当迭代器使用 hashNext()/next()遍历下一个元素之前,都会检测 modCount 变
量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。
解决方法:
1、在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。
2、使用CopyOnWiterArrayList来替换ArrayList

Java中怎么确保一个集合不被修改?

可以使用Colleections.unmodefiableCollection(Conllection c)方法来创建一个只读集合,这样如果集合发生改变,那么就会抛出ava.lang.UnsupportedOperationException异常(不支持的操作异常)
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size())

Collection接口

List和set接口

迭代器 Iterator是什么?

Iterator接口提供遍历任何Collection的接口,从集合中取出和修改元素

Iterator和ListIterator的区别是什么?

Iterator可以遍历set和list集合、而listIterator只能遍历list集合
Iterator只能单项遍历,而listIterator可以双向遍历

如何实现数组和list之间的转换?

数组转换为list:使用Arrays.asList(array)进行转换
list转数组:使用list自带的toArray()方法

ArrayList和LinkedList的区别?

数据结构:ArrayList是动态数组的数据结构,而linkedList是双向链表的数据结构
查询效率:因为LinkedList是线性存储方式,所以需要移动指针从前往后依次查找,ArrayList效率要比LinkedList效率要高
增加和删除操作:LinkedList要比ArrayList效率要高
线程安全:ArrayList和LinkedList都是不同步的,也就是都不保证效率安全
综合来说,在需要频繁读取集合中的元素时,推荐ArrayList,而插入删除较多时,推荐LinkedList

ArrayList和Vector的区别?

效率:ArrayList的效率要高于Vector
线程安全:ArrayList是非线程安全的。而Vector使用了Synchronized来实现线程同步,所以是线程安全的
扩容:Verctor每次扩容会增加1倍,而ArrayList只会增加50%

HashSet的实现原理?

HashSet是基于HashMap实现的,HashSet的值存放于HashMap的key上,HashMap的vaule统一为PRESENT,因此HashSet的是实现比较简单,HashSet的操作基本都是直接调用底层的HashMap的相关方法来完成的,HashSet不允许重复的值

HashSet是如何检查重复的

HashSet中add()添加元素的时候,判断元素是否存在,不仅要比较hash值,同时还要结合equles方法比较
数据结构:HashSet 的内部实际上是通过一个 HashMap 来实现的。HashMap 是一个存储键值对的数据结构,其中键是唯一的。在 HashSet 中,元素被当作 HashMap 的键,而值是一个常量(例如,在 Java 的 OpenJDK 实现中,这个常量是 PRESENT)。
检查重复:当你尝试向 HashSet 中添加一个元素时,实际上是在 HashMap 中添加一个键值对。由于 HashMap 的键是唯一的,所以在添加新元素之前,HashSet 会检查该元素是否已经存在于 HashMap 的键中。这是通过计算元素的哈希码(hashCode() 方法)并查找相应的桶(bucket)来完成的。如果桶中已经存在具有相同哈希码的元素,那么 HashSet 会进一步比较这两个元素的 equals() 方法来确定它们是否相等。

Java中equals和==的区别?

==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或者实例所指向的内存空间的值是不是相同
==是指向内存地址进行比较 equals是对字符串的内容比较

HashMap和HashSet的区别?

HashMap是实现了Map接口,而HashSet是实现了Set接口
HashMap存储键值对的,而HashSet存储对象
HashMap是用put方法来添加元素,而HashSet是add来添加元素

HashMap和HashTable的区别?

1、线程安全:HashMap是非线程安全的,HashTable是线程安全的
2、效率:因为HashTable是线程安全的,所以效率比HashMap低
3、HashMap可以存一个键为null或多个值为null的支持,而HashTable中put一个null,就会报NullPointerException。
4、底层数据结构,HashMap将链表转换为红黑树,而HashTable却没有这些

HashMap

实现原理:

  1. 当我们往 Hashmap 中 put 元素时,利用 key 的 hashCode 重新 hash 计算出
    当前对象的元素在数组中的下标
  2. 存储时,如果出现 hash 值相同的 key,此时有两种情况。(1)如果 key 相同,
    则覆盖原始值;(2)如果 key 不同(出现冲突),则将当前的 key-value 放
    入链表中,
    JDK8之前采用的是拉链法,而JDK8之后采用数组容量大于64切链表大于8,那么将链表改为红黑树
  3. 获取时,直接找到 hash 值对应的下标,在进一步判断 key 是否相同,从而
    找到对应值。
  4. 理解了以上过程就不难明白 HashMap 是如何解决 hash 冲突的问题,核心就
    是使用了数组的存储方式,然后将冲突的 key 的对象放入链表中,一旦发
    现冲突就在链表中做进一步的对比。

数组容量大于64切链表大于8,那么将链表改为红黑树
hashmap动态扩容时,链表小于8,红黑树会退会单项链表

HashMap是如何实现扩容的

1、在 jdk1.8 中,resize 方法是在 hashmap 中的键值对大于阀值时或者初始化时,
就调用 resize 方法进行扩容;
2、每次扩容的时候,都是扩展2倍
3、

CopyOnWriteArrayList是什么,和ArrayList有什么区别

CopyOnWriteArrayList和ArrayList操作都是一样的,只是
CopyOnWriteArrayList.是线程安全的,而ArrayList不是线程安全的
CopyOnWriteArrayList 里面使用加锁,ReentranLock(为了安全lock),使用volatile修饰内部数组

https://blog.csdn.net/qq_42068856/article/details/125243728

ConcurrentHashMap 详情

conurrentHashMap和hashMap一样使用,但是ConcurrentHashMap是线程安全的
ConcurrentHashMap是HashMap线程安全,并支持高并发的版本
1、线程安全问题:ConcurrentHashMap支持高并发和多线程情况下的请求访问和修改,所以无需担心数据不一致的问题
2、高性能:通过精细的锁粒度(使用分段锁或基于 CAS 的无锁算法)和内部优化,ConcurrentHashMap 提供了高性能的并发访问
3、可伸缩性:ConcurrentHashMap的内部数据量会随着增长而自动扩容,以保持性能
4、API 与 HashMap 类似:ConcurrentHashMap 的 API 与 HashMap 非常相似,这使得它易于使用和理解

jdk1.7之前
数据结构:分段的数组+链表
实现线程安全的方式:分段锁,对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。

jdk1.8之后
数据结构:数组+链表+红黑树
实现线程安全的方式:并发控制使用 synchronized和 CAS 来操作。

加锁机制,通过对node节点加锁机制,保证了安全性

Java异常

Java异常架构
在这里插入图片描述

异常的架构

Throwable

Throwable是Java语言中所有错误于异常的超类
Throwable包含两大子类:Error(错误)和Exception(异常)
Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息

Error(错误)

程序中无法处理的错误,表示在运行应用程序中出现了严重的错误。
此错误一般代码运行时JVM出现了问题,通常虚拟机运行错误、NoClassDefFoundError(类定义错误)、OutOfMemoryError(内存不足错误)、StackOverflowError:栈溢出错误。
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。

Execption(异常)

程序本身可以捕捉的异常,Exception又分为运行时异常和编译时异常

运行时异常:
Java编译器不会检测它,也就是说,当程序中可能出现这异常时,如果没有使用try-catcah语句捕捉时,那么程序会编译通过。比如NullPointerException空指针异常,ArrayInexOutBoundException数组下标越界异常,ClassCastExecption类型转换异常,如果产生运行时异常,则需要通过修改代码来进行避免

编译时异常:
Java编译器会去检查它,如果程序中出现此类异常,比如ClassNotFoundExecption(没用找到指定的类异常)IOExecption(IO异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则编译不通过。

Java异常关键字

• try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
• catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
• finally – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
• throw – 用于抛出异常。
• throws – 用在方法签名中,用于声明该方法可能抛出的异常。

若catch代码块中包含return语句,finally中的代码还会执行吗?

如果catch代码中包含了return语句那么finally中的代码还会执行,finally块中代码始终在try-catch块中的代码执行完毕后执行,并且在方法返回前执行,无论try、catch或者return语句中是否抛出异常,finally中的代码都会被执行。只有在程序退出或者虚拟机停止运行时,finally块才不会被执行。

Error和Execption的区别是什么?

Error类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测
Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。

Checked Execption和 Unchecked Exception有什么区别?

Checked Exception即 受检查异常,Java代码在编译过程中,如果受检查异常没有被catch或者throws关键字处理的话,就没有办法通过编译
比如FileInputSteam、FileOutputStream

JVM是如何处理异常的?

在一个方法中如果发生异常,这个方法会创建一个异常对象,并装交给JVM,JVM会顺着调用栈去查找看是否有处理异常的代码,如果有,则调用异常代码处理,如果没有,则 JVM就会将改异常转交给默认的异常处理器,默认的异常处理器打印出异常信息并终止应用程序。

当try块发生异常时,JVM会依次检查try块中的每个语句,看是否存在能够处理该异常的catch块。如果找到了可以处理异常的catch块,就会将异常对象传递给该catch块进行处理;如果未找到合适的catch块,则会将异常向上抛出,交由上层的try块或者调用者进行处理。

如果try块中存在finally块,则无论try块中是否发生异常,finally块中的代码都会被执行。如果try块中的异常被catch块捕获并处理,那么finally块会在catch块执行完成后被执行;如果异常没有被catch块捕获或者catch块中也抛出了异常,finally块仍然会被执行。

throw 和 throws 的区别是什么?

throw是用在方法的内部,只能抛出一种异常,用于抛出方法或代码块中的异常
throws是用在方法的声明上,可以抛出多种异常,用于表示方法可能抛出的异常列表。

try-catch-finally 中哪个部分可以省略?

catch或者finally可以省略,而try不可以省略
try-catch中try负责捕捉异常二catch用于处理异常
try-finally中try负责捕捉异常,那么无法对异常进行捕获和处理,在fainally中关闭资源等

网络编程

网络协议

四层协议,五层协议和七层协议的关系如下:
1、TCP/IP是一个四层的体系结构,主要包括:应用层、运输层、网际层和网络接口层。

2、五层协议的体系结构主要包括:应用层、运输层、网络层,数据链路层和物理层。
:五层协议的体系结构只是为了介绍网络原理而设计的,实际应用还是 TCP/IP 四层体系结构。

3、 OSI七层协议模型主要包括是:应用层(Application)、表示层(Presentation)、会话层(Session)、运输层(Transport)、网络层(Network)、数据链路层(Data Link)、物理层(Physical)。

运输层

运输层主要使用一下两种协议

1、传输控制协议-TCP:提供面向连接的,可靠的数据传输服务。
2、用户数据协议-UDP:提供无连接的,尽最大努力的数据传输服务。

UDP和TCP的区别是什么?

最主要的区别是:非连接和连接
tcp是连接的,也就是实时的需要建立连接,udp是非连接,没有建立连接
,那么udp的传输效率远远高于tcp,但是就会出现丢包的情况,一般作用域名查询、语音通话、视频直播、和隧道网络,也就是我们vpn,tcp稳定可靠,一般作用于发送邮件,发送文件,浏览网页等

从输入 URL 到页面展示到底发生了什么?

1、在浏览器中输入指定网页的url
2、浏览器通过dns协议,获取域名对应的ip地址
3、浏览器根据ip地址和端口号,向目标服务器发起一个tcp连接请求
4、浏览器在tcp连接上,向服务器发送一个http请求报文,请求获取网页的内容
5、服务器收到http请求报文后,处理请求,并返回http响应文给浏览器
6、浏览器收到http响应报文后,解析响应中的html代码,渲染网页的结构和样式,同时根据 HTML 中的其他资源的 URL(如图片、CSS、JS 等),再次发起 HTTP 请求,获取这些资源的内容,直到网页完全加载显示。
7、浏览器在不需要和服务器通信时,可以主动关闭 TCP 连接,或者等待服务器的关闭请求。

DNS(Domain Name System)域名管理系统,是当用户使用浏览器访问网址之后,使用的第一个重要协议。DNS 要解决的是域名和 IP 地址的映射问题。

理解TCP的三次握手和四次挥手

三次握手的本质是确认通信双方收发数据的能力
首先,我发送了一条消息给对方,对方收到了,那么他就知道了我的发消息能力和他的收消息能力是可以的
于是他给我回了一条消息,我若收到了,那么我的收发消息能力都是可以的,并且他知道我的收发能力是可以的,
但是,他并不知道他发的消息能力可以不可以,于是我在给他回馈一次消息,他收到了,那么我们便清楚了我们的
收消息和发消息能力都是可以的,这就是三次握手

四次挥手?
四次挥手的目的是关闭一个连接
https://zhuanlan.zhihu.com/p/668990015

客户端发送请求建立关闭连接,服务端回复确认好的,在发送服务端的关闭连接给客户端,客户端在回复确认,四次挥手
例子:A:我先走了 B:好的 B:我也走了 A:好的

什么是HTTP,HTTP 与 HTTPS 的区别?

HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范

区别HTTPHTTPS
协议运行在 TCP 之上,明文传输,客户端与服务器端都无法验证对方的身份运行于 SSL 上, 是添加了加密和认证机制的 HTTP
端口80443
资源消耗较少由于加解密处理,会消耗更多的 CPU 和内存资源
安全性比较弱由于加密机制,安全性强
开销无需证书需要证书

HTTP状态码了解

200的:成功响应
300的:重定向
400的:浏览器客户端出现的问题,404、403、402、401
500的:服务端出现的问题 505、503

GET和POST区别?

GET是不安全的,因为在传输的过程,数据会放在请求的URL中;POST的操作对用户来说都是不可见的;
GET请求提交的url中的数据最多只能2048字节,是有限制的,而POST请求则没有大小限制
GET的执行效率却要比POST的好,GET默认提交的是form表单
GET产生一个TCP数据包;POST产生两个TCP数据包
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)

Session、Cookie和Token的主要区别

1、
## 什么是cookie?
cookie是由web服务器保存在用户浏览器上的小文件,包含用户相关的信息。客户端向浏览器发起请求,如果服务器需要记录该用户的状态,就使用response向客户端浏览器颁发一个cookie。

什么是Session?

Session是依赖Cookie的,Session是服务端对象
session是浏览器和服务器会话过程中,服务器保存的一块内存对象。服务器默认为浏览器在cookie中设置sessionId,浏览器向服务端请求时传输cookie并携带sessionId,服务器就可以根据sessionId获取会话中存储的信息,然后确定身份信息

什么是token?

token的定义:token是服务端生产的一串字符串,作为客户端请求的一个令牌,当第一次登录后,服务器生成一个token便将此token返回给客户端,以后客户端只需要带上这个token即可,无需再次带上用户名和密码
token的目的:token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮

cookie和session的区别?

存储位置:cookie是存放在用户客户端上的,session是存放在服务器上的;
安全性:cookie安全性比较差,session安全性比较高
占用资源:session当访问增多时,会占用服务器性能,而cookie不会

session和token的区别

session机制存在服务器压力增大,CSRF跨站伪造请求攻击,扩展性不强等问题;
session存储在服务器端,token存储在客户端
token提供认证和授权功能,作为身份认证,token安全性比session好;
session这种会话存储方式方式只适用于客户端代码和服务端代码运行在同一台服务器上,token适用于项目级的前后端分离(前后端代码运行在不同的服务器下)

如果客户端禁用cookie,那么session还能用吗?

cookie和session是两个单独的服务,但是禁用了cookie,session就不能使用了,
因为session是基于seesionId来对应服务器的,而sessionId是通过cookie来传递的,禁用cookie那么就失去了sessionId,也就是失去了seesion。

Servlet是线程安全的吗?

servlet不是线程安全的,多线程并发的读写会导致数据不同步的问题

过滤器和拦截器的区别?

1、实现原理不同
2、
过滤器Filter的使用要依赖于tomcat容器,导致只能在web程序中使用
而拦截器(Interceptor)它是一个spring组件,并由sring容器管理,可以单独使用。
3、触发时机不同
4、拦截的请求范围不同
链接:https://zhuanlan.zhihu.com/p/146017852

MySQL数据库面试题

sql基本练习

-- 去重的数据
select * from t_admin group by age
select distinct(age) from t_admin

-- 查询年龄23-30岁之间
select * from t_admin where age between 23 and 30
select * from t_admin where age >= 23 and age <= 30

-- 查询年龄大于23和小于30岁
select * from t_admin where age > 23 and age < 30

-- 查询年龄没有登记的,也就是不为null的
select * from t_admin where age is not null
select * from t_admin where age is null
select * from t_admin where age =""

-- 查询最大最小值平均值
select MAX(expr) MIN(expr) AVG(expr) from t_admin

-- 多条件查询  age在23以上(不包括23)的重庆用户 或 age在25以上(不包括25)的北京用户
select * from t_admin where age > 23 and addr = "重庆" or age > 25 and addr = "北京" -- 还可以子查询 不过sql较多

-- 查询返回四舍五入的数字
select *,ROUND(balance) as ye from t_admin where loginacct = "lisi" 

-- 连表查询:INNER JOIN 返回两个表中满足连接条件的匹配行
select username,achievement,pass from t_admin tad INNER  join t_achievement tac on tac.aid = tad.id where username ="超哥"

-- 等值连接
select * from t_admin tad inner join t_achievement tac on tac.aid = tad.id 

-- 左连接:LEFT JOIN 返回左表的所有行,并包括右表中匹配的行,如果右表中没有匹配的行,将返回 NULL 值
select * from t_admin tad left join t_achievement tac on tac.aid = tad.id 

-- 右连接:RIGHT JOIN 返回右表的所有行,并包括右表中匹配的行,如果右表中没有匹配的行,将返回NULL值
select * from t_admin tad right join t_achievement tac on tac.aid = tad.id 

-- 查询未通过的女同学
select * from t_admin tad right join t_achievement tac on tac.aid = tad.id where pass = "0" and sex = "女"


-- 操作合并两个或者多个的select语句的结果   
-- union 操作符选取不同的值,union all 允许重复的值
-- 分别查看地区为重庆或者性别为女的用户,结果不去重
-- select * from t_admin where addr = "重庆" or sex = "女"
select * from t_admin where addr = "重庆" union all select * from t_admin where sex = "女" -- 结果为不去重复的女或重庆的

-- 添加索引
select * from t_log where error_log = "Error message for log #999980"


-- 删除表字段或者删除表
DELETE FROM table_name; # 删除所有的数据,这意味表结构,属性,索引保存不变

-- 删除表字段或者删除表

CREATE TABLE new_table(
id BIGINT NOT NULL AUTO_INCREMENT,
 PRIMARY KEY (id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


-- 删除表记录和删除表  
delete from employees where id = 5
delete from new_table  -- 0.06s   # delete 删除id:100后续插入数据还是从101插入
truncate table new_table -- 0.126s  # truncate 而是直接删除记录 从1开始
drop table new_table  -- 直接删除表

-- EXISTS 运算符用于判断查询子句是否有记录,如果有一条或多条记录存在返回 True,否则返回 False。
SELECT Websites.name, Websites.url 
FROM Websites 
WHERE EXISTS (SELECT count FROM access_log WHERE Websites.id = access_log.site_id AND count > 200);

排序语句

select * from user order by age asc --升序
select * from user order by age desc  --降序

非关系数据库和关系型数据库的区别

首先常用的关系型数据库有:Oracle、DB2、PostgreSQL、Microsoft SQL Server、MySql
常见的非关系型数据库有:NoSql、MongoDB、redis、HBase
1、存储方式不同
关系型数据库都是表格式的,因此存储在数据表的列和行中,结构化存储
非关系数据通常存储在数据集中,就像文档,键值对,列存储,图结构
2、扩展方式不同
3、对事务的支持不同

如何向表中添加百万数据?

1、创建日志表

-- 创建t_log日志表
CREATE TABLE t_log (
  id BIGINT NOT NULL AUTO_INCREMENT,
  error_log TEXT NOT NULL,
  user_name VARCHAR(255) NOT NULL,
  url VARCHAR(255) NOT NULL,
  create_time DATETIME NOT NULL,
  spend_time VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2、添加函数和执行函数

CREATE DEFINER=`root`@`localhost` FUNCTION `NewProc`(`IN` int) RETURNS int
BEGIN
  DECLARE i INT DEFAULT 0;
    DECLARE start_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP;  
    WHILE i < 1000000 DO
    # step3、循环插入数据
   INSERT INTO `t_log`(`error_log`, `user_name`, `url`,`create_time`, `spend_time`) VALUES (
			CONCAT('Error message for log #', i),
			CONCAT('User #', FLOOR(RAND() * 10000)),
			CONCAT('/page/', FLOOR(RAND() * 10000)),
			FROM_UNIXTIME(@start_ts + FLOOR(RAND() * 31536000)),
			CAST(FLOOR(RAND() * 10000) AS CHAR(255)));
   SET i =  i + 1;
  END WHILE;
	RETURN 0;
END

创建存储过程

CREATE DEFINER=`root`@`localhost` FUNCTION `NewProc`(`IN` int) RETURNS int
BEGIN
  DECLARE i INT DEFAULT 0;
    DECLARE start_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP;  
    WHILE i < 1000000 DO
    # step3、循环插入数据
   INSERT INTO `t_log`(`error_log`, `user_name`, `url`,`create_time`, `spend_time`) VALUES (
			CONCAT('Error message for log #', i),
			CONCAT('User #', FLOOR(RAND() * 10000)),
			CONCAT('/page/', FLOOR(RAND() * 10000)),
			FROM_UNIXTIME(@start_ts + FLOOR(RAND() * 31536000)),
			CAST(FLOOR(RAND() * 10000) AS CHAR(255)));
   SET i =  i + 1;
  END WHILE;
	RETURN 0;
END

call InsertLogData() – 调用存储过程

如何向百万表中添加字段?

-- 添加字段  假设要在一个有1000万行数据的表中添加一个字段
alter table t_admin add phone varchar(11) 
-- 1、alter  最不方便最费时间的方法,ALTER TABLE添加新的字段可能会导致锁表,长时间卡住查询和更新操作,影响其他业务的正常运行

alter table t_admin add column `start` varchar(3),algorithm=inplace,lock=none
-- 2、使用INPLACE算法,就能保证不会产生数据复制和重建索引的情况,而且在LOCK=NONE情况下就不需要锁表,不会对其他业务产生印象

-- 3、创建新表,复制原来的列,以及新的列,
# 1. 创建一个新表
CREATE TABLE new_table LIKE old_table;
ALTER TABLE new_table ADD remark VARCHAR(255);
# 2. 把旧表中的数据复制到新表中
INSERT INTO new_table (column1,column2,...)
  SELECT column1,column2,... FROM old_table;
# 3. 测试并且删除旧表
RENAME TABLE old_table TO backup_table;
RENAME TABLE new_table TO old_table;

DROP TABLE backup_table;

drop和delete和truncate的区别

1、删除效率 drop>truncate>delete

2、drop是整张表删除,不会保留任何数据 ,而truncate会删除整张表数据,不会回滚,不支持where,会保存字段属性、索引、表结构,delete会删除整张表数据也可以删除一条和部分数据,支持回滚,支持where,保存表结构、字段属性、索引

delete删除 如何回滚?

emmm

MySQL如何去重并求和?

select sum(salary) from user group by id
distinct
sum求和 group id 去重

Mysql事务

MySQL事务链接

Mysql事务隔离机制

mysql四种事务隔离机制

1、 READ UNCOMMITTED(读未提交)
这是最低的隔离级别。在该级别下,一个事务可以读取到另一个事务未提交的数据修改。这被称为“脏读”。由于可以读取到未提交的数据,所以存在很大的数据不一致性风险。

2、READ COMMITTED(读已提交)
在该级别下,一个事务只能看到其他事务已经提交的数据。这避免了脏读,但仍然可能发生不可重复读和幻读(Phantom Reads)。不可重复读是指,在同一个事务内,多次读取同一数据集合时,由于其他事务的修改或删除,导致两次读取的结果不一致。

3、REPEATABLE READ(可重复读)
这是MySQL的默认事务隔离级别(对于InnoDB存储引擎)。在此级别下,可以确保在同一个事务中多次读取同样记录的结果是一致的。这通过使用多版本并发控制(MVCC)来实现,从而避免了不可重复读的问题。但是,幻读仍然可能发生,即在一个事务内,由于其他事务的插入操作,导致同一个查询在不同的时间点返回了不同的结果集。

4、SERIALIZABLE(可串行化)
这是最高的隔离级别。它通过强制事务串行执行,从而避免了脏读、不可重复读和幻读。在这种级别下,事务串行执行,可以避免并发问题,但会大大降低系统的并发性能。

Mysql中MVCC是如何实现事务隔离的

MVCC(Multi-Version Concurrency Control ),即版本并发控制,是一种并发控制的方法,主要用于数据库管理系统中,实现对数据库的并发访问

一、MVCC的定义与原理
MVCC是一种乐观的并发控制方法,它允许多个事务同时读取同一数据项,而不会相互阻塞。在MVCC中,每个事务在开始时都会获得一个唯一的版本号(或时间戳),用于标识事务的起始点。随后,事务在读取数据时,会根据数据的版本号来判断数据是否对其可见。

二、MVCC实现机制
分为三个部分
1、隐式字段:在数据库中,每行记录除了用户定义的字段外,还会包含一些隐式定义的字段,如事务ID(用于标识修改该记录的事务)和回滚指针(指向旧版本的记录)。

2、Undo Log:用于记录数据的旧版本信息,以便在需要时能够恢复到旧版本的数据。

3、Read View:事务进行快照读操作时生成的读视图,用于判断当前事务能够看到哪个版本的数据。Read View包含了生成时刻系统活跃事务的ID列表,以及用于可见性判断的其他信息。

三、MVCC的工作流程
以MySQL的InnoDB存储引擎为例,MVCC的工作流程大致如下:

事务开始:当一个事务开始时,系统会为该事务分配一个唯一的版本号(或时间戳)。
读取数据:事务在读取数据时,会根据Read View和数据的版本号来判断数据是否对其可见。如果数据的版本号小于Read View中的最小事务ID,或者数据的事务ID等于当前事务ID(表示事务自己修改的数据),则数据对当前事务可见;否则,需要通过回滚指针找到旧版本的数据进行判断。
修改数据:事务在修改数据时,会将旧版本的数据复制到Undo Log中,并更新记录的事务ID和回滚指针。这样,就保留了数据的一个旧版本,以便在需要时能够恢复到该版本。
事务提交或回滚:事务提交时,其修改的数据将对其他事务可见;事务回滚时,则通过Undo Log将数据恢复到修改前的状态。

四、MVCC的应用场景与限制
MVCC主要适用于读多写少的场景,如Web应用中的用户信息查询等。然而,MVCC也存在一些限制和缺点,如需要为每行记录提供额外的存储空间来保存隐式字段和Undo Log信息,以及需要进行更多的行维护和检查工作等。此外,MVCC并不能完全解决所有的并发问题,如更新丢失等写-写并发问题仍需要通过其他机制来解决。

MySQL如何解决幻读、脏读、不可重复读?

1、幻读:使用间缝隙(gap lock)或行级锁(行锁)避免。例如使用 SELECT … FOR UPDATE 或 SELECT … LOCK IN SHARE MODE 语句获取共享和排他锁
2、脏读:使用事务管理以及MVCC(多版本并发控制)避免。当一个事务修改数据时,其他事务无法看到改修改,直接事务提交。同时也可以使用锁来避免脏读
3、不可重复读:使用行级锁(行锁)或,MVCC(多版本并发控制)避免。在同一事务中读取相同的数据会得到相同的结果,即使其他事务对该数据进行了修改。

什么是脏读,不可重复读,幻读

脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。

不可重复读 :是指在一个事务内,多次读同一数据。

幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。

Mysql中一条sql语句如何优化?

1、避免使用select * 这种会检索所有的列,应该查询精准的列
2、确保where字句中使用的时最有效的过滤条件,避免在where子句中对字段进行函数操作,这回导致索引失效,
最好避免使用or,使用in来代替多个or查询
3、优化join操作,在on字句使用了索引,避免在join字句中使用子查询,这个可能会导致索引失效,有时候改变join的顺序性能也会提高不少
4、通过explain查看是否使用索引,索引的类型是怎么样的

mysql数据表规模九千万左右,怎么优化查询?

这里的优化那么有四个维度:硬件配置、参数配置、表数据结构设计、SQL语句和索引
其中SQL语句相关的优化手段是最重要的

表结构设计
设计聚合表

2、设计冗余字段
为减少关联查询,创建合理的冗余字段,比如:一张A表有姓名和身份证号两个字段,查询的另一张B表的时候每次都需要id来关联查询到这A张表的身份证号和姓名,如果可以那么就在B这表建立姓名和身份证号两个字段,就不需要每次查询的时候来去关联查询
当然,如果冗余字段过多的话,对系统复杂成度和插入性能会有影响

3、分表:
分表为垂直分表和水平分表两种
垂直拆分,适用于字段太多的大表,比如:一个表有100多个字段,那么可以把表中经常不被使用的字段或者存储数据比较多的字段拆出来。
水平拆分,比如:一个表有5千万的数据,那么按照一定的策略拆分成十个表,每个表都有5百万数据,这种方式,除了可以解决查询性能问题,也可以解决数据写操作的热点征服问题

4、字段的设计
数据库中的表越小,在它上面执行的查询也就会越快,所以,在创建表的时候,为了获得更好得性能,我们可以将表中字段的宽度设得尽可能小

  • 使用可以存下数据最小的数据类型,合适即可
  • 尽量使用tinyint、smallint、medium_int作为整数类型而非INT,如果非负则加上unsigned
  • varchar的长度只分配真正需要的空间
  • 对于某些文本字段,比如“省份”和“性别”,使用枚举或整数代替字符串类型
  • 尽量使用timestamp而非datetime
  • 单表不要有太多字段,尽量在20以内
  • 尽可能使用 not null 定义字段,null 占用4字节空间,这样在将来执行查询的时候,数据库不用去比较NULL值
  • 尽量少用text类型,非用不可时最好考虑拆表

SQL语句及索引
如果发现SQL查询比较慢,可以开启慢查询日志进行排查

# 开启全局慢查询日志
SET global slow_query_log = ON;
# 设置慢查询日志文件名
SET global slow_query_log_file = 'slow-query.log';
# 记录未使用索引的SQL
SET global log_queries_not_using_indexes = ON;
# 慢查询的时间阈值,默认10秒
SET long_query_time = 10;

注:索引并不是越多越好,要更具查询有针对性的创建

索引的创建和使用原则
单表查询:哪个列作条件查询,就在该列创建索引
多表查询:left join时,索引添加到右表关联字段;right join时,索引添加到左表关联字段
不要对索引进行任何操作(计算、函数、类型转换之类的)

水平拆分id的问题
使用咱们的雪花算法,有序自增的

雪花id的组成:41位时间戳+10位机器码+10位序列号
雪花id生成
雪花id介绍

JDBC访问数据库的基本步骤是什么?

1、创建Class.forName()加载数据库连接驱动
2、创建DriverManager.getConnection()获取数据库连接对象
3、创建数据库配置器Statement
4、关闭连接、关闭资源、关闭会话

数据库的三大范式

第一范式(1NF):每个列都不可以再拆分。
第二范式(2NF):在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。
第三范式(3NF):在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键,非主键列之间不存在传递性依赖。
三大范式介绍

什么是索引?

自己理解:索引就一个特殊的文件也是中数据结构,他包含所有数据查找的记录的引用指针
跟简单来说索引就相当于目录,为了查找书中的内容,通过对目录查找书中对应的内容,这就是索引;
mysql官方定义:索引就是帮助sql高效获取数据的数据机构

索引的优势和劣势

优势:提高检索的效率,降低数据库的io成本
劣势:索引实际上也是一张表 保存了主键和索引的字段,并指向了实体表的记录,索引也是需要占用空间的。在索引大大提高查询的同时,却会降低表的更新速度。

索引的使用场景和什么时候使用索引?

1、主键自动建立唯一索引
2、频繁作为查询条件的字段应该创建索引(where 后面的语句)
3、查询中与其它表关联的字段,外键关系建立索引
4、多字段查询下倾向创建组合索引
5、查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
6、查询中统计或者分组字段

不推荐索引:
1、表的记录太小
2、经常增删改的表
3、where条件里用不到的字段不建立索引

索引的分类

主键索引:
1、表中的列设为主键后,数据库会自动建立主键索引
2、单独创建和删除主键索引语法
创建主键索引语法: alter table 表名 add primary key (字段);
例如:alter table customer add primary key(id);
删除主键索引语法: alter table 表名 drop primary key;

唯一索引:
1、表中的列创建了唯一约束时,数据库会自动建立唯一索引。
2、单独创建和删除唯一索引语法:
创建唯一索引语法: alter table 表名 add unique 索名(字段)或 create unique index 索名 on 表名(字段)
例如:alter table customer add unique idx_customer_no(customer_no);
删除唯一索引语法: drop index 索名 on 表名

单值索引:
即一个索引只包含单个列,一个表可以有多个单值索引
1、建表时可以随表一起建立单值索引
2、单独创建和删除单值索引:
创建单值索引: alter table 表名 add index 索引名(字段); create index 索引名 on 表名(字段);
例如:alter table customer add index idx_customer_name(customer_name)
删除单值索引: drop index 索名 on 表名;

复合索引:
即一个索引包含多个列
1、建表时可随表一起建立复合索引
2.、单独创建和删除复合索引:
创建复合索引: create index 索引名 on 表名(字段 1,字段 2);或 alter table 表名 add index 索名(字段,字段2);
例如:alter table customer add index idx_customer_no_name(customer_no,customer_name)
删除复合索: drop index 索名 on 表名;

mysql 有关权限的表都有哪几个

Mysql服务器通过权限表来控制用户对数据库的访问,权限表存放在mysql数据库里,由 mysql_install_db 脚本初始化。这些权限表分别user,db,table_priv,columns_priv和host。
user权限表:记录允许连接到服务器的用户账号信息,里面的权限是全局级的。
db权限表:记录各个账号在各个数据库上的操作权限
table_priv: 记录数据表级的操作权限
columns_priv 权限表:记录数据列级的操作权限。
host 权限表:配合 db 权限表对给定主机上数据库级操作权限作更细致的控
制。这个权限表不受 GRANT 和 REVOKE 语句的影响

explain关键字的作用

1、表的读取顺序
2、那些索引可以使用
3、数据读取操作的操作类型
4、那些索引被实际使用
5、表之间的引用
6、每张表有多少行被优化器查询
explain关键字使用:explain+sql语句

id:
select_type: 查询类型,simple:简单的,primary:derived,subquery
table: 输出结果集的表
partitions: 匹配的分区
type: 访问类型排序
possible_keys: 表示可能使用的索引。如果此列显示为 NULL,则表示没有可用的索引。
key: 表示实际使用的索引。如果此列显示为 NULL,则表示没有使用索引。
key_len: 使用索引的长度,如果此列显示为 NULL,则表示没有使用索引。长度越长越好
ref: 表示哪些列或常量被用于查找索引列。
rows: 检查的行数,越少越好
filtered:表示返回结果的行占开始查找行的百分比(百分比)。
extra: 包含不适合在其他列中显示但非常重要的额外信息。比如,是否使用了索引来排序结果(Using index for order by),是否使用了临时表(Using temporary),是否进行了文件排序(Using filesort)等。

explain中索引的类型type类型级别

all:全表查询,遍历数据全表
index:索引全扫描、遍历索引表
range:索引范围扫描
ref_or_null:
fulltext:
ref:非唯一性索引扫描,返回匹配某个单独值的所有行
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配
system/const:查找的值为一个常数(2),并且表中有多条记录,所以结果为const(常量);若表中只有一条记录,则type为system。

索引失效

1、(复合索引才需要满足最佳左前缀法则)最佳左前缀法则:如果索引了多列,要遵循最左前缀法则,指的是查询从索引的最左前列开始并且不跳过索引中的列
2、不在索引列上做任何计算,函数操作,会导致索引失效而转向全表扫描
3、存储引擎不能使用索引中范围条件右边的列。
4、Mysql在使用不等于时无法使用索引会导致全表扫描
5、is null 可以使用索引,但是 is not null 无法使用索引
6、字符串不加单引号索引会失效
7、like 以通配符开头会使索引失效导致全表扫描
== 注意:==
1)模糊匹配后面任意字符(张开头的):like ‘张%’ 不会失效
2)模糊匹配前面任意字符(张结尾的):like ‘%张’ 失效
3)模糊匹配前后任意字符:like ‘%张%’ 失效
只有第一种匹配后面的字符,索引才不会失效,其他索引都会失效

列举情况:
有张表

CREATE TABLE t_log (
  id BIGINT NOT NULL AUTO_INCREMENT,
  error_log TEXT NOT NULL,
  user_name VARCHAR(255) NOT NULL,
  url VARCHAR(255) NOT NULL,
  create_time DATETIME NOT NULL,
  spend_time VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

1、使用复合索引比如user_name和url,那么有三个条件,sql语句
-- 在使用and的情况下,这些索引都会生效,就是说满足索引中一个字段,那么索引都会生效,也就遵循最佳左前缀法则
explain select * from t_log where user_name = "User #5424" and spend_time>1 and url ="/page/8702"
explain select * from t_log where spend_time>1 and user_name = "User #5424"  and url ="/page/8702" 
explain select * from t_log where spend_time>1 and url ="/page/8702"  and user_name = "User #5424"
explain select * from t_log where user_name = "User #9213" and spend_time>30
explain select * from t_log where spend_time>30 and user_name = "User #9213"
explain select * from t_log where user_name = "User #9213"


-- 那么不生效的情况下有
explain select * from t_log where url = "/page/8702"
explain select * from t_log where url = "/page/8702" and spend_time>30

-- 在使用or的情况下都会失效

千万数据下如何实现分页
通过limit如果数据量大那么效率就会下降那么可以通过子查询

-- 通过id子查询,效率会更快
SELECT * FROM users WHERE id > (SELECT id FROM users ORDER BY id LIMIT 10000, 1) LIMIT 10;

慢查询日志

MySQL的慢查询日志是MySQL提供的一种日志记录,他用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中,可以用它来查看那些sql超过了我们最大的忍耐时间值。
慢查询日志使用:
默认情况下,MySQL数据库没有开启慢查询日志,需要手动设置参数。
查看是否开启:show variables like ‘%slow_query_log%’;
开启日志:set global slow_query_log = 1;
设置时间:set global long_query_time =1; 设置为1秒钟
查看时间:SHOW VARIABLES LIKE ‘long_query_time%’;
查看超时的sql记录日志,Mysql的数据文件夹下 \Data\设备名称-slow.log
关闭日志:set global slow_query_log = 0;
注意:非调优场景下,一般不建议启动参数,慢查询日志文件讲日志记录写入文件,开启慢日志查询或多或少会带来一定的性能影响

数据库备份

1、逻辑备份
使用M有SQL数据库自带的mysqldump命令,或者第三方工具,然后把数据库里的数据以sql语句的方式导出文件的方式,在需要恢复的时候,通过使用相关命令 如:source 讲备份文件里的sql语句提取出来重新在数据库中执行一遍,从而恢复数据库的目的
2、物理备份
物理备份就是利用命令,(cp、tar、scp等)直接将数据库的存储数据文件复制一份或多份,分别存放在其它目录,以达到备份的效果。
这种备份方式,由于在备份时数据库还会存在数据写入的情况,一定程度上会造成数据丢失的可能性。在进行数据恢复时,需要注意新安装的数据的目录路径、版本、配置等与原数据要保持高度一致,否则同样也会有问题。

超键、候选键、主键、外键分别是什么?

超键:超键定义为在关系中能唯一标识元组的属性集。在一个关系型数据表中,超键可以有多个,一个属性可以被称为超键,多个属性集也可以被称为超键(超键包含主键和候选键)。只要其中不存在重复的值,都可以作为超键。超键的主要作用是唯一标识元组,确保在数据表中每行数据的唯一性。

候选键:候选键是不含有多余属性的超键,是去掉任何一个属性都不能标识唯一性的最小超键。候选键也是一列或一组列的集合,用于唯一标识表中的记录。候选键的值必须在表中唯一,并且不允许为空。

主键:一个数据列中只能有一个主键,且主键不能丢失,也不能为空
外键:在一个表中存在另一个表的主键称之为外键

sql优化

1、查询、定位慢sql语句,并优化
2、建立索引:通过explaln字段对数据建立索引,
3、分表:当表中有多个数据或者某个字段有多个值并且不经常使用的时候,就可以使用水平分表和垂直分表
4、读写分离:当一个服务器不能满足需要时,使用多个服务器。一台服务器是对外提供增删改服务器,第二台服务器主要进读的操作;
5、使用缓存:使用redis做缓存

索引的使用

EXPLAIN SELECT * FROM USER WHERE ID < 20
查询得到possible_keys 这是可选的索引,key才是真正用来检索的索引

索引的基本原理?

1、把创建了索引的列的内存进行排序
2、对排序的结果生成倒排表
3、在排表的内容上拼上数据地址链
4、在查询的时候,先拿到倒排表内容,在取出数据链地址,从而拿到具体的数据

索引的数据结构

Hash索引、B+树索引
InnoDB默认的索引的就是B+索引
b+树的好处
b+树的空间利用高,可减少磁盘i/o的次数
b+树的查询更加稳定,

回表是什么?

简单来讲就是如果一次索引查询不能够获得所有列的信息,需要从辅助索引中获取额外额度数据列,通过辅助索引回到主键索引中进行查找,就是回表;

避免回表操作,可以使用覆盖索引来优化查询;

举例:
有一张表
id name age
1 张三 18
2 李四 19
不需要回表的情况下是通过id来查找,因为id是唯一索引
select * from one_piece where ID = 2;

需要回表的情况下
select * from one_piece where name = “张三”
这个查询语句是需要回表的,原因是通过name这个普通索引查询方式,搜索name索引库,在得到主键id的值为1,在通过id索引树在搜索一次

覆盖索引是什么?

覆盖索引就是需要所有查找的列的添加索引,就不要回表操作在来获取数据,
通过使用覆盖索引,可以提高查询性能,减少磁盘io操作和数据的传输
举个列子:
id username email
我们创建一个复合索引
CREATE INDEX idx_username_email ON users(username, email);
先在执行
SELECT username, email FROM users WHERE username = ‘Alice’;
因为我们复合索引包含了所需要查询的列,就不要在去进行回表操作

如果我只是创建了普通索引就是
CREATE INDEX idx_username ON users(username);
那么在进行查询
SELECT username, email FROM users WHERE username = ‘Alice’;
这时候通过索引查询到usernmae但是查询不到email,那么需要通过回表的操作,通过username拿到id,在通过id索引查找数据,这就是回表。

聚簇索引和非聚簇索引是什么?

聚族讲解
聚簇索引:讲数据存储与索引放到了一块,找到了索引就找到了数据,索引结构的叶子节点保存了行数据(必须有,只能由一个)
非聚簇索引:讲数据存储和索引分开的结构, (可以存在多个)
在这里插入图片描述
问:聚簇索引和非聚簇索引可以同时存在吗?
答:是的,一个表可以拥有一个聚簇索引和多个非聚簇索引,它们各自针对不同的查询需求。

问:是否每个表都需要聚簇索引?
答:不是,聚簇索引的选择取决于具体的数据和查询需求,不是每个表都必须有。

MySQL 存储引擎 MyISAM 与 InnoDB 区别?

Innodb 引擎:Innodb 引擎提供了对数据库 ACID 事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。
MyIASM 引擎(原本 Mysql 的默认引擎):不提供事务的支持,也不支持行级锁和外键。
InnoDB的四大特征:
插入缓冲、二次写、自适应哈希索引、预读

InnoDB 索引是聚簇索引,MyISAM 索引是非聚簇索引。
InnoDB 的主键索引的叶子节点存储着行数据,因此主键索引非常高效。
MyISAM 索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数
据。
InnoDB 非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此
查询时做到覆盖索引会非常高效。

Mysql千万数据如何实现分页

limit 使用子查询效率会更快
select * from t_log order by id limit 3000000,10 – 1.402s
SELECT * FROM t_log WHERE id >= (SELECT id FROM t_log ORDER BY id LIMIT 3000000, 1) LIMIT 10; – 0.538s
通常使用limit来实现分页,取出3百万中的前十行数据,数据量变大时,随着查询页码的深入,查询性能越来越差

Mysql如何实现读写分离

主机负责写数据,从机负责读数据,基于主从架构,就是多个从库,一个主库,写主库时会把数据同步到我们的从库里面去,
具体操作就是,主库变更写入到binlog日志,这个binlog日志记录所有数据库的变更的一个文件,增加、删除、修改都在里面,那么从库连接主库之后呢,从库通过io线程,将主库的binlog日志拷贝到自己的本地,从库的sql线程中间日志来读取binlog,然后执行binlog里面的sql语句内容,也就是在执行从库里面的一个sql,就能保存从库和主库的一致性

那么sql是串行的,也就是从库会有延迟,那么在高并发的情况下,出现数据不一致的情况,再有一个问题,那么我从库宕机了也出现了数据不一致的问题,mysql有两个保障机制,一个是半同步的复制,解决主库数据丢失的问题,一个并行复制,在从库开启多个线程来执行主库中的sql

nginx面试题

解释一下什么是nginx?

nginx是一个高性能web服务器和反向代理服务器,用户http和https等协议;

什么是正向代理?

正向代理就是为了从服务器端获取内容,客户端向服务器代理发送一个请求,然后代理向服务器转交请求并获取请求的内容返回给客户端;简单来说就是:代理端代理的是客户端

什么是反向代理?

反向代理就是指服务端来接受internet上的连接请求,然后将请求发内部服务器并将从服务器上的到的数据返回给internet上请求连接的客户端上,此时就表现为返现代理服务器;代理端代理的是服务端

请举例nginx和apache区别?

1、nginx是一个基于事件的web服务器;apache是一个基于流程的服务器
2、nginx所有请求都是由一个线程处理;apache是单个线程单个请求
3、nginx在负载均衡方面表现比较好;而apache并比较差
4、最核心的区别就是:apache是同步多进程模型,一个连接对应一个进程;nginx是异步的,多个连接对应一个进程;

nginx和tomcat的区别?

1、nginx主要用作代理服务器,而tomcat主要用于部署web应用;
2、nginx是一个http Server,常用做静态资源内容服务和反向代理服务和负载均衡,而tomcat更像一个应用容器;
负载均衡?简单来说就是代理服务器将接受到的请求均衡的发布到各个服务器中取

tomcat面试题

tomcat是apache软件基金会中一个核心项目,是一个免费的开发源代码的web应用服务器,属于轻量级应用服务器;

tomcat的工作模式

有三种工作模式:
1、独立的servlet容器,servlet容器是web服务器的一部分;
2、进程内的servlet容器,servlet容器是作为web服务器的插件和java容器的实现,web服务器插件在内部地址空间打开一个jvm使得java容器在内部得以运行。反应速度快但伸缩性不足;
3、进程外的servlet容器,servlet容器运行于web服务器之外的地址空间,并作为web服务器的插件和java容器实现的结合。反应时间不如进程内但伸缩性和稳定性比进程内优;

Servlet面试题

什么是Servlet?

Servlet全称是server applet,是基于HTTP协议的在服务端生成的程序,我们把实现Servlet接口的Java程序叫做Servlet程序

Servlet的使用?

导入Java Servlet Api的包
写一个类,继承HttpServlet类
重写doGet/doPost方法
配置web.xml映射路径
配置tomcat服务器

JVM面试题

什么是JVM?JVM的作用?

JVM是Java虚拟机(Java Virtual Machine)的缩写,它是一个虚拟出来的计算机。
跨平台性:JVM通过java字节码编译或解释成特定平台的机器码,使得java程序实现了跨平台运行
内存管理:JVM负责Java程序的内存管理,包括内存的分配和回收。
安全性:JVM提供了严格的安全机制,通过字节码校验,访问控制和安全沙箱手段,防止恶意代码对性能优化:系统造成破坏或攻击

JVM 的主要组成部分及其作用?

JVM包含两个子系统和两个组件,两个子系统为:类装载(class loader)、执行引擎(execution engine);两个组件为:运行时数据区(runtime data area)、本地接口(native interface);
作用: 首先通过编译器把Java代码转换为字节码,类加载器在把字节码加载到内存中,将其放在运行时数据区的方法去执行,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎,将字节码翻译成底层系统指令,在交给CPU去执行,而这个过程中需要调用其他语言的本地库接口来实现整个程序的功能。
简化:编译器把Java代码转换为字节码,类加载器再把字节码加载到内存中,将其放在运行时数据区的方法内,通过执行引擎,将字节码翻译底层系统指令,这个过程就需要本地库接口来实现

浅拷贝和深拷贝

想到拷贝就想到内存
浅拷贝只是增加了一个指针指向已存在的内存地址
深拷贝是增加了一个指针并申请了一个新的内存,使这个增加的指针指向这个新的内存

Java的堆和栈

1、堆(Heap):

  • 堆是Java中动态分配内存的地方,用于存储对象实例
  • 所有通过new关键字创建的对象都会被分配到堆中
  • 堆是线程共享的,所有线程都可以访问并修改堆中的对象
  • 堆的大小可以通过设置JVM参数来调整
  • Java的垃圾回收机制负责自动管理堆内存的分配和释放

2、栈(Stack):

  • 栈是用于存储方法调用和局部变量的内存区域
  • 每个线程都自己的栈空间,每个方法在执行时会创建一个栈帧
  • 每个栈帧都包含了方法的参数,局部变量和方法返回值等信息
  • 栈是一种后进先出的数据结构,方法的调用顺序与栈的操作顺序相反
  • 栈的大小是固定的,当栈空间不足时会抛出栈溢出异常

说一下堆栈的区别?

物理地址
堆的物理地址分配对对象是不连续的 ,因此性能慢些;
栈使用的数据结构中的栈,先进后出的原理,物理地址分配是连续的,所有性能快;
内存分配
堆因为是不连续的,所有分配的内存是在运行期确认的,因此大小不固定。
栈是连续的,所以分配的内存大小要在编译期就确定,大小是固定的
存放的内容
堆存放的对象的实例和数据
栈存放的是局部变量,操作数栈,返回结果。

队列和栈有什么区别?

最主要的就是操作方法不同:队列是先进先出,而栈为后进先出

如何为对象分配内存?

类加载完成后,接在会Java堆中划分一块内存分配给对象。内存分配根据Java堆是否完整,有两种方式:
指针碰撞:如果 Java 堆的内存是规整,即所有用过的内存放在一边,而空
闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移
动一段与对象大小相等的距离,这样便完成分配内存工作。
空闲列表:如果 Java 堆的内存不是规整的,则需要由虚拟机维护一个列表
来记录那些内存是可用的,这样在分配的时候可以从列表中查询到足够大
的内存分配给对象,并在分配后更新列表记录。

Java会内存泄露吗,请简单描述

内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java
是有 GC 垃圾回收机制的,也就是说,不再被使用的对象,会被 GC 自动回收掉,
自动从内存中清除。
但是,即使这样,Java 也还是存在着内存泄漏的情况,java 导致内存泄露的原因
很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,
尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导
致不能被回收,这就是 java 中内存泄露的发生场景。

简述Java垃圾回收机制

在Java中,程序员不需要手动去释放一个对象的内存的,而是虚拟机自行执行。在JVM中,有一个垃圾回收线程,正常情况下是不会执行的,只有当虚拟机空闲或者当前内存不足时,才会触发执行,扫描那些没有被引用的对象,进行回收

GC是什么?为什么要GC

GC就是垃圾收集的意思( Collection),忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的

垃圾回收的优点和原理?

JVM
JVM内存分区
JVM为什么分为新生代和老年代

运行时数据区包括:
方法区、虚拟机栈、本地方法栈、程序计数器、堆

新生代分区:
Eden(ED)区:朝生晚死的对象存放
Survior(色辣舞)区:一次gc将存放在这个区,也就是幸存者区
Survior区又分为
s0区:为了空间碎片化的
s1区:
在这里插入图片描述

垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

垃圾回收器的基本原理就是扫描所有活动对象并将其从内存中删除,同时将非活动对象内存空间释放回操作系统。简单来说,垃圾回收器会遍历程序的所有对象,标记哪些对象是可以的,然后释放那些不可达的对象所占用的内存空间
垃圾回收器并不能立即回收内存,它通常会在适当的时机进行内存回收
在Java中,可以通过调用System.gc()方法来主动通知虚拟机进行垃圾回收,但是Java语言规范并不保证GC一定会执行。

Java中都有那些引用类型?

强引用:发生gc的时候不会被回收
软引用:有用但是不是必须的对象,在发生内存溢出之前会被回收
弱引用:有用但不是必须的对象,在下一次 GC 时会被回收。
虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用
PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。

说一下 JVM 有哪些垃圾回收算法?

  • 标记清除算法:标记无用的对象,然后进行清理回收。
  • 复制算法:按照容量划分相同大小的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后把已使用的内存空间一次清理掉。
  • 标记整理算法:标记无用的对象,将所有存活的对象都向一端移动,然后清除掉端边界以外的内存。
  • 分代算法:根据对象存活周期的不同将内存划分为几块, 一般是新生代和老年代,新生代采用复制算法,老年代采用标记整理算法。

说一下有那些垃圾回收器

Java垃圾回收器
新生代回收器:
包括 Serial(射瑞噢)、PraNew(普洱牛)、Parallel Scavenge(佩尔牛 十gai未几)
Serial 翻译为串行,也就是说它以串行的方式执行。
PraNew它是 Serial 收集器的多线程版本。

老年代的收集器包括 :
Serial Old、Parallel Old

同时适用于新生代和老年代的回收器:
1、CMS
2、G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。
3、ZGC

除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式执行。

什么是双亲委派模型?

双亲委派型是一种加载器之间的委托模型,在这个模型中,如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此,一直递归到顶层,当父类加载器无法完成这个请求时,子类才会尝试加载。
这个模型不是强制性约束,也就是你不这么做也不会报错怎么样的,它是一种设计Java设计者推荐使用类加载器的方式

简单来说:当一个类加载器需要加载某一个类的时,它首先将这个任务委托给父类加载器,只有在父类加载器无法加载该类时,才由当前类加载器自己来加载
那么它的优势在于:它能够保证Java类的全局唯一性和一致性,同一个类值被加载器所加载一次,避免重复加载。

JVM参数调优

你是怎么JVM调优的?

参考:
jvm调优
jvm调优
一般的Java项目是不需要JVM调优的,最多调整-Xms和-Xmx参数,JVM的调优需要根据具体的应用场景和硬件环境来选择合适的参数和配置。

1、堆内存设置:通过调整-Xms和-Xmx参数来设置推内存的初始化大小和最大大小。合理设置堆内存可以避免频繁的垃圾回收,并减少OutOfMemoryError的发生。
2、垃圾回收器选择:根据应用程序的特点和硬件环境,选择合适的垃圾回收器。
3、监控和分析:使用JVM自带的JVisualVM、JConsole等工具
4、线程栈大小:对于线程比较多的应用程序,可以适当调整线程栈的大小,以减少内存占用。
5、类加载优化:通过预先加载类、使用类加载缓存等技术,减少类加载时间和内存占用。

调整 -Xms 参数的方法如下:
1、如果你使用java命令启动项目,那么在项目后面添加
java -Xms256m -Xmx512m -jar your-application.jar
xms:初始化内存256M,xmx:最大内存512M

1、监控发现问题(通过监控工具例如Prometheus+Grafana
jvm
比如GC频繁、cpu负载过高、oom、内存泄漏、死锁、程序响应时间较长
2、常见的调优

  • 堆内存调优:调整JVM堆的内存大小,包括初始内存大小和最大内存大小来使用程序的内存需求
  • 垃圾回收调优:适当选着合适的垃圾回收器和参数调整,入Serial、Parallel、CMS、G1,如调整堆的分区大小、垃圾回收的线程数等,
    以提高垃圾回收的效率和减少停顿时间。
  • 线程调优:堆jvm的线程相关的参数,如线程栈大小,线程池大小,以优化线程的并发性能和资源利用
  • 类加载调优:对类加载器进行调优,包括设置适当的类加载器层次结构、减少类加载的次数和提高加载速度。
    了解:
    编译器调优:Java虚拟机的即时编译器(JIT)将热点代码编译成本地机器码,提高执行速度。可以通过调整编译器相关参数,如编译
    级别、内联策略等,以获得更好的性能。
    I/O调优:对输入输出操作进行优化,包括使用合适的缓冲区大小、选择高效的I/O操作方式(如NIO)、优化文件和网络操作等。
    监控和分析:使用工具监控JVM的运行情况,如内存使用情况、垃圾回收情况、线程状态等,并进行性能分析,以找出性能瓶颈和优化 的潜在点。

什么时候才能JVM调优呢?

要知道JVM调优不是常规手段,性能问题一般第一选择是程序,代码优化,最后才是选择进行JVM调优。
1、内存持续上涨到设置的最大内存值
2、应用出现OutOfMemory(内存不足)等内存异常问题
3、GC(垃圾回收)停顿时间过长
4、应用中有使用本地缓存且占用大量内存空间的
5、系统吞吐量与响应性能不高或下降

Jvm出现OOM如何快速排斥和处理和出现fullgc该如何解决

假如某个JAVA进程的JVM参数配置如下:-Xms1G -Xmx2G -Xmn500M -XX:MaxPermSize=64M -XX:+UseConcMarkSweepGC -XX:SurvivorRatio=3,请问eden区最终分配的大小是多少?

先分析一下里面的各个参数含义:
-Xms:1G ,就是初始堆大小为 1G
-Xmx:2G,就是说最大堆大小为2G
-Xmn:500M,就是说年轻大小是500M
-XX:MaxPermSize:64M , 就是说设置持久代最大值为64M
-XX:+UseConcMarkSweepGC , 就是说使用使用CMS内存收集算法
-XX:SurvivorRatio=3 , 就是说Eden区与Survivor区的大小比值为3:1:1
题目中所问的Eden区的大小是指年轻代的大小,直接根据-Xmn:500M和-XX:SurvivorRatio=3可以直接计算得出
500M*(3/(3+1+1))
=500M*(3/5)
=500M*0.6
=300M
所以Eden区域的大小为300M。

Servlet请求转换和重定向

请求转发
req.getRequestDispatcher(“/servlet内部url地址或jsp页面”).forward(请求对象, 响应对象);
重定向
req…sendRedirect(String url)

请求转发和重定向的区别?

请求转发主需要请求一次而重定向需要请求两次
请求转发最终地址是不变的 ,而重定向的地址是发生了改变的

redis面试题

redis是什么,他有什么优缺点?

redis高性能的数据库,也是中间件,单线程,能够每秒读十万次,写的速度是八万每秒
优点:
缺点:

什么样的数据适合放缓存?

1、高频被访问的数据,热点数据
2、数据的变化频率不高
3、非敏感数据

redis如何保证数据都是热点数据?

1、一般情况会设置数据的过期时间,那么如果在过期之前,又访问一次或者多次,那么继续增加过期时间,比如3分钟或者多少分钟,按照业务场景来定,这时候redis中保存的就是热点数据了

reids如何确保和数据库的数据的一致性?

1、原始的做法
流程:查询数据库时将数据也同步在redis中,来确保和数据库的一致性,但是这种做法对代码入侵比较严重

先更新数据库,在更新缓存
先删除缓存,在更新数据库

2、使用消息队列:
当数据库中的数据发生变化时,发送一个消息到消息队列中。
Redis订阅这些消息,并在接收到消息时同步更新数据。
这种方法可以确保Redis中的数据与数据库中的变化保持同步

3、使用数据库的触发器:
在数据库中设置触发器,当数据发生变化时,触发器自动将变化的数据同步到Redis中。
这种方法可以确保Redis中的数据与数据库的实时变化保持一致。

4、加锁的方式保证数据强一致性

5、延迟双删策略:
在更新数据库前,先删除Redis中的缓存数据。
更新数据库后,休眠一段时间(根据业务逻辑的耗时确定)再次删除Redis中的缓存数据。
这种方法可以确保在高并发场景下,数据的一致性得到一定程度的保障

谈谈redis的基本数据类型和用途?

基本数据类型有五种,字符型、集合、有序集合、散列表、列表
用途:缓存、消息队列、排行、秒杀、
字符串(string):存储用户凭证,分布式锁,验证码,点赞和点踩
散列表(hash):可以快速定位,需要存储信息,且这个信息需要频繁被修改时,就可以采用这个结构,比如购物车就可以使用hash
列表(list):队列和栈,双向链表(可以做秒杀,保存代抢购的商品列表)
集合(set):无序唯一(秒杀,保存抢购到商品的用户,保证每个人每件商品只能抢购一件)
zset(有序集合):可排序特征 分数、数值(可以做排行榜)

redis实现点赞和签到和统计用户访问量

简单来讲
redis实现点赞通过键值对,
签到使用bitmap来实现
统计使用hash或者bitset来实现访问量

什么是redis的过期键(内存管理策略)

设置有效期,到期了之后不是立马回收,占据内存
没有设置有效,就只存在,命中率很低的资源,占据内存
我们都知道redis是key-value数据库,我们可以设置redis中缓存的key的过期时间
惰性回收:访问一个key时,刚好这个key过期了,那么这个时候回删除
定时回收:默认10次没秒,每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除

redis的持久化机制是什么?

redis提供两种持久化RDB(默认)和AOF机制;
RDB,默认是开启的持久化方式,按照一定的时间将内存的数据以快照的形式保存到磁盘中,对应产生二进制文件(dump.rdb);
AOF,默认是关闭的,redis执行的每次写命令都会记录到日志文件中,当重启redis会重新持久化的日志文件恢复数据。

reids有那些数据类型?

redis的有五种数据类型:String、List、Hash、Set、Zset
string:字符串、浮点数、整数的操作 应用场景:做简单的键值对缓存
list:列表 应用场景:做文章列表和文章的评论列表之类的数据
set:无序集合 应用场景:交集、并集、差集、共同好久、粉丝列表的集合
hash:键值对的无序散列表 应用场景:存储一个对象
zset:有序集合 应用场景:可以去重、可以获取用户的排名情况

什么是redis持久化?

持久化就是把内存的数据写到磁盘中去,防止服务器宕机了内存数据的丢失

redis的持久化机制是什么?各有什么优缺点呢?

redis提供两种持久化机制 :RDB和AOF机制
RDB:rdb是redis默认的持久化机制。将内存的数据已快照的形式保存到硬盘中,然后对应产生 dump.rdb文件。
AOF:redis每次执行写的命令记录到单独的日志文件中,当重启redis会重新将持久化文件日志文件恢复数据。
注意当两种模式开启时,redis会默认执行AOF恢复

一问道区别时很多情况下记住:安全和效率问题
RDB效率比AOF效率高,但是没有AOF安全

redis怎么持久化

一:RDB持久化
1、手动持久化
在客户端执行
save或者bgsave命令,save是主线程执行备份操作,bgsave是子线程执行备份操作
2、自动持久化
在redis.conf中设置
// 在900秒内,如果至少有一个key被修改,则执行bgsave
save 900 1
save 300 10
save 60 10000

二:AOF持久化
在配置文件中redis.conf设置appendonly yes 和设置文件名称
aof的命令记录的频率也可以通过redis.conf文件来配置
appendfsync always :表示每执行一次写的命令,立即记录到aof文件中
appendsync everysec :写命令执行完先放入aof缓冲区,然后表示每个一秒钟将缓冲区数据写到aof文件中,是默认的方案
appendsync no:写命令执行完先放入aof缓存区,由操作系统决定何时将缓冲区内容写回磁盘
在这里插入图片描述

redis的过期键的删除策略

我们都知道redis是key-value数据库,我们可以设置redis中的缓存的key的过期时间。redis中缓存key过期了,redis如何处理。

1、惰性过期
当访问一个key时,才会判断key是否已经过期,如果过期了那么就清除。但是会出现大量的key没有被访问到这不会清除,则会导致内存的浪费和占用
2、定时过期
顾名思义设置定时时间,没隔一段时间,会扫描redis数据库中的一定数量的key,并清除其中过期的key,不同的情况有不同的耗时情况,可以使不同的情况使cup和内存资源达到最优的平衡效果

reids设置key的过期时间和永久的分别怎么设置?

expire :设置key的过期时间 expire key 60 60秒
persist:设置永久有效的状态 persist key
expire(ex拜儿)
persist (坡儿射死特)

reids查询key的过期时间?

ttl key

MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据

1、缓存策略选择,更具自己业务和逻辑来选择合适的缓存策略,常见的缓存策略为:写回策略,写穿透策略,淘汰算法等
2、热点数据识别:通过mysql的日志查询,查询出访问频率较高的日志,设置成为热点日志
3、设置合适的过期时间:对于热点数据,可以设置较长的过期时间,以免避免缓存的失效和加载
4、针对不同数据的模式的不同访问优化
5、等等

redis的淘汰策略?

redis淘汰策略是指在redis的用于缓存的时候内存不足时,怎么处理新写入且需要额外申请空间的数据

redis的内存淘汰机制有那些?

全局的键空间选择性移除:
noeviction:当内存不足以容纳新写入数据时,新写入就会报错
allkeys-lru: 当内存不足以纳入新写入数据时,在键空间中,移除最近最少使用的key(这个最常用)
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key

设置过期时间的键空间选择性移除:
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键
空间中,移除最近最少使用的 key。
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的
键空间中,随机移除某个 key。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键
空间中,有更早过期时间的 key 优先移除。

什么是事务?

事务就是单独的隔离操作,事务中的所有命令都会序列化,按照顺序的执行,事务在执行的过程中,不会被其他的客户端发送过来的命令所打断
事务是一个原子操作,事务中的命令要么全部被执行,要么全部都不被执行
事务的四个特征:原子性,一致性,隔离性,持久性
redis事务介绍:
1、redis是不支持回滚操作的,redis在执行失败的时候不进行回滚,而是继续执行余下的命令
2、如果事务中命令出现错误,那么所有的命令都不会被执行
3、如果在事务中出现运行错误,那么正确的命令都会被执行

redis事务相关的命令?

redis事务功能是通过multi,exec,discard很watch四个原语实现的
redis会将一个事务中的所有命令序列化
mutil: 命令用于标记一个事务块的开始
exec: 开始执行事务
discard: 取消事务
watch: 用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断

哨兵模式

sentinel(生特楼)中文名就是哨兵,哨兵模式包括
集群监控:负责监控redis mater 和 slave(十礼物) 进程是否正常工作
消息通知:如果redis中实例有故障,那么哨兵会负责发送消息作为报警通知管理员
故障转移:如果mster挂掉了就会转移到slave node节点上
配置中心:如果故障转移发生了,通知clicent客户端新的master地址
Redis哨兵模式是指在Redis集群中,有一组专门的进程(即哨兵进程)负责监控主节点和从节点的状态,并在发现故障时自动进行故障转移,以保证Redis集群的高可用性。

redis主从架构?

redis单机能够承载的OPS大概上万到几万不等,对于缓存来说,一般都是用来支持读高并发的。
因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的slave节点,从节点负责读。所有读的请求全部走从节点。这样也可以很轻松实现水平扩容

什么叫缓存雪崩?

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉
解决方案:
1、redis高可用,使用主从+哨兵,避免全局崩溃
2、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。比如设置3秒过期,那么在3上加上0.1-0.8之间随机数,而不是同一时间过去
3、给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失败,则更新数据缓存。

什么叫缓存穿透?

缓存穿透是指缓存和数据库中都没有数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉
比如用户请求id=-1,缓存和数据库中都没有
解决方案:
1、接口增加校验,比如用户权限校验,id做校验,id<=0的直接拦截
2、缓存和数据库中都没有数据,那么可以将key-value设置为key-null,也就是可以查询缓存,直接返回null,在设置有效时间可以是短点,如30秒
3、采用布隆过滤器
4、缓存空数据,如果从数据库中查询是空,那么将空数据放入缓存(方便、不推荐,会发生数据不一致的问题)
缓存穿透讲解

对于空间的利用达到了一种极致,那就是Bitmap和布隆过滤器

什么叫缓存击穿?

缓存击穿讲解
缓存击穿是指缓存中没有数据但是数据库中有数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没有读到数据,又同时去数据库取数据,引起数据库压力瞬间增大,照成过大压力。和缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:互斥锁、逻辑过期(不设置过期时间)
缓存预热、缓存降级、热点数据和冷数据

缓存预热

缓存预热就是你知道一些都是热点数据,那么先将热点数据添加到缓存中,那么下次查询数据就会从 缓存中获取数据,就叫缓存预热

布隆过滤器

bitmap(位图):相当于是一个以(bit)位为单位的数组,数组中每个单元只能存储二进制数0或1

010110000

布隆过滤器的作用:布隆过滤器可用于检索一个元素是否在一个集合中
在这里插入图片描述

reids中会有数据不一致的情况吗,那么怎么解决

会有,比如在正常情况下,保存到数据库的同时在保持到redis中,那么这时候保持到redis中因为网络原因导致redis中数据没有更新,当时数据库更新了数据,那么这时候就会形成缓存中的数据和数据库中的数据不一致的情况

解决方案:
1、可以使用重试机制,redis更新失败了,则将重试失败的key写入mq,,当我们缓存恢复之后,在将这些key从缓存删除掉,
2、缓存设置的时间调短,

并发编程面试题

并行和并发有什么区别?

并行就是多个处理器或多核处理器同时处理多个任务
并发就是多个任务在同一个cpu上,按时间轮流执行

进程和线程有什么区别?

进程是操作系统资源分配的基本单位,而线程是处理器任务调度的执行的基本单位

什么是上下文切换?
上下文切换指的是操作系统在多任务处理中,由于需要暂停当前正在执行的线程或进程,并保存当前状态(上下文),然后切换到另一个进程或线程继续执行,这个过程就被称之为上下文切换

什么是线程死锁?
死锁是指两个或者两个以上的线程在执行过程中,由于竞争资源或者彼此通信造成一种阻塞的现象,此时系统处于死锁或者系统产生了死锁,这种永远互相等待的进程被称为死锁线程;

创建线程的四种方式?

1、继承 Thread 类
2、实现 Runnable 接口
3、实现 Callable 接口
4、使用 Executors 工具类创建线程池
四种最终底层都是实现了runnable接口拉实现的

线程的run()和start()有什么区别?

1、run()方法用于执行线程运行时代码,start()方法用于启动线程
2、run()方法可以重复调用,start()只能调用一次
3、run()方法只是线程里的一个函数,而不是多线程 ,start()来启动一个线程,真正实现了多线程运行

Executors()中submit和execute有什么区别呢?

1、返回值
submit方法会返回一个future对象,它代表了异步计算的结果
execute方法没有返回值
2、异常处理
当使用submit方法时,如果执行任务中抛出了异常,这个异常会封装到返回的futrue对象中,调用者可以通过Future.get()方法获取任务执行的结果时捕获到这个异常。
使用execute()并且任务执行过程中抛出了异常,那么这个异常不会被调用者直接捕获到。异常会由ExecutorService捕获,并在后续某个时间点(比如线程池关闭时)通过RejectedExecutionException等异常形式被抛出,但这与任务执行时的异常并不直接相关。因此,使用execute()时,通常需要在提交的任务内部进行异常处理。
3、任务类型
submit方法可以接受cunnable和callable类型的任务
execute方法接受runnable类型的任务
4、使用场景:
如果你需要获取任务执行的结果,或者需要处理任务执行过程中可能抛出的异常,那么应该使用submit()方法。
如果你只是简单地想要将任务提交给线程池执行,而不需要关心任务执行的结果或异常,那么可以使用execute()方法。

为什么我们调用start()方法会执行run()方法,为什么我们不能直接调用run()方法?

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

sleep()和wait()有什么区别?

两者都可以暂停线程的执行
类的不同:sleep()是Thread()线程类的静态方法,wait()是Object类的方法。
释放锁:sleep()不释放锁,wait()释放锁。
用途不同:sleep通常用于暂停执行,wait()通常用于线程间交互或通信
用法不同:sleep()方法执行完成后,线程会自动苏醒。wait方法被调用后线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法

wait 和 notify 这个为什么要在synchronized 代码块中?

wait和notify用来实现多线程之间的协调的,wait表示线程进入阻塞状态,notify表示唤醒阻塞的线程
wait和notify成对出现是必然的,如果一个线程被wait阻塞,那么就会有另一个线程notify()方法来唤醒阻塞的线程,从而实现多线程之间的通信

synchronized关键字确保同一时间只有一个线程可以执行同步代码块或方法,从而保证了线程安全

如果不在同步代码块中调用,Java就会抛出IllegalMonitorStateException (非法监控状态异常)

而 Synchronized 同步关键字就可以实现这样一个互斥条件,也就是在通过共享变量来实现多个线程通信的场景里面,参与通信的线程必须要竞争到这个共享变量的锁资源,才有资格对共享变量做修改,修改完成后就释放锁,那么其他的线程就可以再次来竞争同一个共享变量的锁来获取修改后的数据,从而完成线程之前的通信。

这段话描述了synchronized关键字在Java中用于实现线程同步和线程间通信的基本原理。下面我用更简单的语言来解释这段话:
想象一下你有一间小屋子(可以看作共享变量或对象),屋子里有一些资源(可以是数据或状态)。多个线程(可以看作是不同的人)想要进入这个屋子,并对这些资源进行操作(比如修改数据)。但是,屋子太小,不能同时容纳多个人。
synchronized关键字就像是这个小屋子的门锁。当一个线程想要进入屋子时,它必须首先获得门锁(也就是获得对象的锁)。一旦它获得了锁,它就可以进入屋子,对资源进行操作。其他想要进入屋子的线程就必须等待,直到当前线程完成操作并释放锁(也就是离开屋子)。
这样,就保证了在任何时候,只有一个线程能够对共享资源进行操作,从而避免了多个线程同时修改资源导致的冲突或数据不一致。
线程间通信则是基于这种互斥访问的。一个线程修改完资源后,释放锁,其他线程就可以获得锁并看到修改后的资源状态。通过这种方式,线程之间可以通过共享变量来传递信息或协调行动。
简而言之,synchronized保证了同一时间只有一个线程可以访问共享资源,从而实现了线程间的同步和通信。

什么是阻塞式方法?

阻塞式方法是指程序一直会等待该方法完成,期间不做任何其他的事情。ServerSocket 的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

Java中你怎样唤醒一个阻塞的线程?

首先,wait()、notify()方法是针对对象的,调用任意对象的wait()方法都会导致线程阻塞,阻塞的同时也将释放该对象的锁,相应的
1、使用notify()方法唤醒一个等待在该对象上的线程
2、如果有多个线程在该对象上等待,可以使用notify()方法唤醒全部等待的线程
3、

如何停止一个正在运行的线程呢

使用interrupt,线程中断,调用该方法的线程的状态将被置为“中断”状态,不会停止线程,需要用户自己监视状态并做处理
支持线程中断的方法(也就是线程中断后会抛出
interruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状
态被置为“中断状态”,就会抛出中断异常。

interrupted:是静态方法,查看当前中断信号是 true 还是 false 并且清除中断
信号。如果一个线程被中断了,第一次调用 interrupted 则返回 true,第二次
和后面的就返回 false 了。
isInterrupted:查看当前中断信号是 true 还是 false

notify() 和 notifyAll() 有什么区别?

主要的原因:
notify()只能唤醒一个等待的线程,而notiyfAll()会唤醒所有等待的线程

合理解释:
notify()方法用于唤醒一个正在等待该对象的单个线程,使它进入就绪状态。被唤醒的线程将获取到对象的锁,然后运行。如果有多个线程在等待同一个对象,那么只有一个线程会被唤醒,但具体哪个线程被唤醒是不确定的。
notifyAll()方法则用于唤醒所有正在等待该对象的线程,使它们都进入就绪状态。所有被唤醒的线程都会去竞争对象的锁,获得锁的线程将运行。

在 Java 程序中怎么保证多线程的运行安全?

1、使用安全类,比如java.util.concurrent下的类,使用原子类AtomicInteger
2、使用自动锁,synchronized
3、使用手动锁 lock

Java中如何停止一个正在运行的线程?

在java中有三种方法可以终止正在运行的线程
1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
2、使用stop方法强制终止(废弃)
3、使用interrupt方法中断线程(推荐)

Java如何实现多线程的执行顺序?

1、常用的使用Thread1.jion()方法,使等待线程1执行完毕
2、

线程中的状态

NEW(new):尚未启动的线程的线程状(新建状态)
RUNNABLE(runnable):可以运行状态(运行状态)
BLOCKED(blocked):等待监视器锁的线程的线程状态(阻塞状态)
WAITING(waitng):等待线程的线程状态(等待状态)
TIMED_WAITING(timed_waiting):指定等待时间的等待线程的线程状态(时间等待状态)
TERMINATED(terminated):终止线程的线程状态。线程已完成执行(结束状态)

在thread源码中提供了六种状态

	 /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;

并发理论

Java中垃圾回收有什么用呢,什么时候进行垃圾回收?

垃圾回收就是:在内存中存在没有引用的对象或超过作用域的对象时候才进行的
垃圾回收的目的就是:识别并丢弃对象从而释放内存和重用资源

如果对象的引用被置为 null,垃圾收集器是否会立即释放对象占用的内存?

不会,也就是说垃圾回收器不会立即回收,而是在下一次垃圾回收时才会释放暂用的内存

Java中synchronized关键字的作用?

在Java中synchronized关键字可以修饰类、方法、变量,主要用于实现多线程之间的互斥访问,通过在方法或代码块前加上synchronzied关键字,可以使同一时刻只有一个线程能够进入该方法或代码块中执行,从而避免多个线程同时访问共享资源导致的数据不一致或其他的问题
另外在java早期版本中,synchronzied属性重量级,效率低下。。。

说说自己是怎么使用synchronzied关键字的,在项目中用到了吗?

synchronzied关键字最主要的三种使用方式:
修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法:也就是给当前类加锁,会作用
修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

多文件上传
public synchronized Object uploadPics(HttpServletRequest request, List filedatas) {

说下synchronzied的底层原理?

Java中的synchronized关键字通过使用monitor对象实现同步机制。每个Java对象都有一个monitor对象,当一个线程进入synchronized块时,它会尝试获取该对象的monitor对象锁。如果该锁已经被其他线程占用,则当前线程将进入阻塞状态直到锁被释放。一旦该线程成功获取了锁,它就可以执行临界区代码。

在底层,monitor对象是由每个Java对象的头部信息中的标志位来实现的。具体而言,Java对象头包含了两个标志位:一个用于表示该对象是否被锁定,另一个用于表示锁定该对象的线程ID。当一个线程请求锁时,它会检查对象头的锁定标志位是否为0,如果是,则将其设置为1,并将锁定线程ID设置为自己的ID。如果锁定标志位已经为1,则说明该对象已经被其他线程锁定,当前线程就会进入阻塞状态等待锁的释放。

一旦获得锁的线程退出synchronized块,它会释放monitor对象的锁定标志位,从而允许其他线程进入临界区代码。如果当前线程持有多个monitor对象的锁定标志位,则它需要按照获取锁时的顺序依次释放这些锁,避免死锁的产生。

简单说法:
synchronized的底层实现是通过字节码指令来实现的。当编译器看到synchronized关键字时,它会生产相应的字节码指令,在执行时,JVM会自动处理对象的加锁和解锁操作。

锁的升级
随着Java版本的更新,synchronized进行了多项优化,其中引入了锁的升级机制,以提高并发性能。在Java SE 1.6及之后的版本中,synchronized的锁一共有四种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。

锁的升级过程如下:
1、无锁状态:
没有线程对资源进行锁定,所有线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

2、偏向锁状态:
当一个线程访问同步块并获取锁时,JVM会在对象头和栈帧的锁记录里存储锁偏向的线程ID。
以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。
如果测试成功,表示线程已经获得了锁;如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁)。
如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

3、轻量级锁状态:
如果有另外的线程试图锁定某个已经被偏向过的对象,JVM就需要撤销偏向锁,并切换到轻量级锁实现。
线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的Mark Word复制到锁记录中(即Displaced Mark Word)。
然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁;如果失败,表示其他线程在竞争锁,当前线程使用自旋来获取锁。
当自旋次数达到一定次数时,锁就会升级为重量级锁。

4、重量级锁状态:
当锁升级为重量级锁后,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。

自己理解:
底层实现原理:
syn底层实现原理是通过jvm的monitor对象来实现的,修饰同步代码块的时候使用moitorenter和monitorexit来个指令来实现同步的
monitorenter指令指向同步代码块开始的位置,monitorexit指令指向同步代码块的结束位置
修饰方法的时候,jvm采用acc_synchronized标记符来实现同步,这个标识指明了改方法是一个同步方法。
锁的升级:
偏量锁->轻量锁->重量级锁

当一个线程首次访问某个synchronized同步代码块或方法时,JVM会尝试为该线程加上偏向锁。

当有第二个线程尝试访问改同步代码块时,偏量锁会被撤销升级为轻量锁
轻量锁通过自旋锁的方式来实现,即线程会不断尝试锁,而不会立即堵塞

如果自旋次数超过一定的限制(这个限制是由JVM内部参数控制的,如-XX:PreBlockSpin),或者检测到有其他的线程也在自旋尝试获取锁,那么轻量级锁可能会升级为重量级锁。

重量级锁通过monitor(监视器锁)来实现,它依赖于底层操作系统的Mutex Lock。

synchronized 和 Lock 有什么区别?

1、synchronized是java的关键字,而lock是一个接口
2、synchronized在执行相应的同步代码块或方法后,会自动释放锁。而lock必须手动释放,否则可能导致线程锁死
3、lock提供了中断锁,超时锁,公平锁。而synchoronzied没有这些功能

volatile 关键字的作用

volatile是Java中的关键字,主要有两个作用
1、保证可见性: volatile可以确保多个线程之间对该变量的读写操作是可见的。
2、禁止指令重排序:为了提高执行代码效率,JVM会对指令进行优化,包括重新调整指令的顺序
关键字还会禁止指令重排序优化,保证了程序执行的顺序与代码的顺序一致,这样可以防止某些情况下的线程安全问题,例如双重检查锁定问题。

例如,假设我们有两个线程A和B,它们共享一个变量flag,线程A在某个时刻修改了flag的值,如果flag被声明为volatile,那么线程B在下一次读取flag时,会读取到线程A修改后的值,而不是它在自己工作内存中的旧值。

java
volatile boolean flag = false;

// 线程A
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println(“线程A设置flag为true”);
}

// 线程B
public void run() {
while (!flag) {
// do something
}
System.out.println(“线程B检测到flag为true,退出循环”);
}

synchronized 和 volatile 的区别是什么?

1、volatile修饰变量;synchronized可以修饰类,方法,变量
2、volatile保证变量的可见性;synchronized则可以保证变量的可见性和原子性
3、volatile不会造成线程阻塞;synchronized可能会造成线程阻塞
4、volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
5、volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好

ReentranLock是什么,和synchronized有什么区别

锁的获取方式:
synchronized的锁获取是隐式的,即在进入同步代码块或方法时自动获取锁,退出时自动释放锁。而ReentrantLock的锁获取是显式的,需要手动调用lock()方法获取锁,unlock()方法释放锁。

锁的公平性:
synchronized是一种非公平锁,不能保证等待时间最长的线程最先获取锁。而ReentrantLock可以是公平锁,通过构造函数指定,公平锁可以保证等待时间最长的线程最先获取锁。

锁的灵活性和功能:
ReentrantLock提供了许多synchronized不具备的功能,例如可以设置超时时间,可以判断锁是否被其他线程持有,可以使用Condition类实现线程等待/通知机制等。同时,ReentrantLock还可以中断正在等待锁的线程,而synchronized无法中断一个在等待的线程。

synchronized是java关键字,ReentranLock是jdk的一个类
synchronized是不可中断的,ReentranLock 是可以中断的
synchronized底层有锁的升级,ReentranLock 没有锁升级

ReentrantLock的实现原理

ReentranLock

ReentrantLock的实现依赖于内部AbstractQueuedSynchronizer类 (AQS)的一个实现

AQS使用一个int类型的变量来表示同步状态,ReentrantLock用它来表示锁的持有计数和持有线程的信息。当计数为0时,表示锁未被任何线程持有。当一个线程首次成功获取锁时,JVM会记录这个锁的持有线程,并将计数器设置为1。如果同一个线程再次请求这个锁,它将能够再次获得这个锁,并且计数器会递增。当线程释放锁时(通过调用unlock()方法),计数器会递减。如果计数器递减为0,则表示锁已经完全释放,其他等待的线程有机会获取它。

1、可重入性:ReentrantLock的一个主要特点是它的名字所表示的含义——“可重入”。简单来说,如果一个线程已经持有了某个锁,那么它可以再次调用lock()方法而不会被阻塞。这在某些需要递归锁定的场景中非常有用。锁的持有计数会在每次成功调用lock()方法时递增,并在每次unlock()方法被调用时递减。
2、公平性:与内置的synchronized关键字不同,ReentrantLock提供了一个公平锁的选项。公平锁会按照线程请求锁的顺序来分配锁,而不是像非公平锁那样允许线程抢占已经等待的线程的锁。公平锁可以减少“饥饿”的情况,但也可能降低一些性能。
3、可中断性:ReentrantLock的获取锁操作(lockInterruptibly()方法)可以被中断。这提供了另一个相对于synchronized关键字的优势,因为synchronized不支持响应中断。
4、条件变量:ReentrantLock类中还包含一个Condition接口的实现,该接口允许线程在某些条件下等待或唤醒。这提供了一种比使用wait()和notify()更灵活和更安全的线程通信方式。

乐观锁和悲观锁有什么区别?

悲观锁:顾名思义总是假设最坏的情况,每次拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁
比如Java里面的synchronized关键字的实现也就是悲观锁
乐观锁:顾名思义很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制。乐观锁适用于多读的应用类型,这样可以提交吞吐量,像是数据库提供的类似于write_condition机制,其实都是提供乐观锁。

乐观锁的实现方式:

1、使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略。

2、java 中的 Compare and Swap 即 CAS ,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置 V 的值与预期原值 A 相匹配,那么处理器会自动将该位置值更新为新值 B。否则处理器不做任何操作。

什么是CAS?

当我们在多线程操作统一资源的情况下,为了保证多线程的安全问题,都会采用互斥锁,也就是悲观锁,但是大多数多线程都是读操作,那就没有必要每次调用的时候都锁定资源,并不是很合理,那么这时候就可以通过cas,也就是compoar a swap,
当然这样并不能完成实现多线程的一个安全,而是我们基于底层的cup,进行调用
在代码中实现,通过AtomicInteger 来实现原子的操作

什么是死锁?

死锁是指两个或者多个线程因为相互等待对方持有的资源而陷入一种无期限的等待状态,导致无法继续执行发生阻塞,这种就被称之为死锁。

怎么避免死锁

1、避免嵌套锁:
尽量避免在一个线程中嵌套地获取多个锁。如果必须这样做,确保以固定的顺序获取锁,这样可以减少死锁的机会。
2、设置超时:
当尝试获取锁时,设置一个超时时间。如果线程在指定的时间内无法获取到锁,则放弃获取并返回错误,而不是无限期地等待。
3、使用锁顺序:
当多个线程需要获取多个锁时,确保它们总是以相同的顺序请求锁。这样可以防止循环等待条件的发生,从而减少死锁的可能性。
4、锁粒度:
尽量减小锁的粒度,即减少每次锁定所保护的代码范围。这样可以减少线程之间的冲突,提高并发性能。
5、避免死等:
线程在请求锁时,应该检查锁的持有者是否正在等待自己持有的锁,如果是,则应该释放自己持有的锁,从而打破死锁循环。
6、使用锁超时和重试机制:
当线程尝试获取锁失败时,可以等待一段时间后重试。这样可以避免线程无限期地等待,从而防止死锁。
7、使用读写锁:
如果多个线程只是读取共享资源而不修改它,那么可以使用读写锁来提高并发性能。读写锁允许多个线程同时读取共享资源,但只允许一个线程修改它。

什么是活锁?

活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。

线程面试题

谈谈你对线程安全的理解?

当多个线程访问一个对象时,不同同步或者加锁的操作,调用这个对象的行为可以获取这个对象的结果,我们说的这个对象就是线程安全的;

sleep和wait什么区别?

sleep方法是定义在thread上
wait方法是定义在object上
sleep不会释放锁
wait会释放锁
sleep可以使用在任何代码块
wait必须使用在同步代码块执行

谈谈你对ThreadLocal的理解?

ThreadLocal叫做线程变量,如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有个这个变量的一个本地拷贝,多线程操作这个变量的时候,实际是在操作自己本地内存里的变量,从而起到线程隔离的作用,避免了并发线程下线程安全问题。

作用:解决多线程并发访问问题
参考链接:
ThreadLocal详解1
ThreadLocal详解2

举例说明:
日期工具类

public class DateUtil {

    private static final SimpleDateFormat simpleDateFormat =
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static Date parse(String dateString) {
        Date date = null;
        try {
            // 使用SimpleDateFormat不是线性安全的,它以共享变量出现时,并发多线程场景下即会报错
            // date = simpleDateFormat.parse(dateString);
            date = dateFormatThreadLocal.get().parse(dateString); // 加上ThreadLocal 不会出现
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            String s = String.valueOf(i);
            executorService.execute(() -> {
                System.out.println(DateUtil.parse("2022-10-241 16:34:3" + s));
            });
        }
        executorService.shutdown();
    }
}

ThreadLocal的应用场景

ThreadLocal的很重要一个注意点,就是用完,需要手动调用remove()
而ThreadLocal的应用场景
1、使用日期工具类,当用到SimpleDateFormat,使用ThreadLocal保证线性安全
2、全局存储用户信息(用户信息存入ThreadLocal,那么当前线程在任何地方需要时,都可以使用)
3、保证同一个线程,获取的数据库连接Connection是同一个,使用ThreadLocal来解决线程安全的问题
4、使用MDC保存日志信息。

ThreadLocal与Synchronized的区别?

1、synchronized用于线程间的数据共享,threadloacl则用于线程间的数据隔离
2、synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

synchronized和lock有什么区别?

1、作用的位置不同
首先synchronized可以给方法,代码块加锁
lock只能给代码块加锁
2、获取锁和释放锁的机制不同
synchronized无需手动获取锁和释放锁,发生异常会自动解锁,不会出现死锁
lock需要自己加锁和释放锁,可能会出现死锁(如lock()和unlock(),如果忘记使用unlock(),则会出现死锁,所以,一般我们会在finally里面使用unlock())。

说说synchronized的底层原理

在java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchonized代码段不被多个线程同时执行;synchronized可以修饰类,方法,变量
在java6之前,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统锁来实现的;而在java6之后对synchronized锁的升级,实现引入大量的优化,如自旋锁,适应性自选锁,锁消除,锁粗化,偏向锁,轻量锁等技术来减少锁的开销。
执行原理:在java中执行同步代码块都会有一个monitor字样,就是监控器,其中前面的是monitorenter,后面的是离开monitorexit,首先是获取锁就是monitorenter,然后在是否锁就是monitorexit
多线程中 synchronized 锁升级的原理是什么?
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。

锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。

volatile关键字的作用

保证可见性: 当一个变量被volatile修饰时,在一个线程中对该变量的修改会立即被其他线程所见。这是因为volatile修饰的变量会被立即写回主内存,并且从主内存中读取最新的值,保证了各个线程之间对变量的修改是可见的。

禁止指令重排序: volatile关键字还会禁止指令重排序优化,保证了程序执行的顺序与代码的顺序一致。这样可以防止某些情况下的线程安全问题,例如双重检查锁定问题。

synchronized 和volatile的区别?

1、作用的位置不同
synchronized修饰方法,代码块
volatile修饰变量
2、作用的不同
synchronized可保证可见性和原子性,可能会造成堵塞
volatile仅实现可见性,但无法保证原子性,不会造成线程堵塞

什么情况叫做线程安全

1、多线程环境
2、访问同一个资源
3、访问资源具有状态性

什么是悲观锁和是什么乐观锁

悲观锁顾名思义它比较悲观比较严谨,每次需要那数据的时候认为别人都会修改,所有在每次拿数据的时候去上锁。synchronzied这里也是使用的悲观锁
乐观锁顾名思义,就是很乐观,每次去拿数据的时候认为别人不会修改,所有不会上锁

悲观锁是利用数据库本身的锁机制来实现,会锁记录
乐观锁是一种不锁记录的实现方式,采用cas模式,采用version字段来作为判断依据,每次对数据的更新操作,都会version+1,这样提交更新操作时,如果version的值已被更改,则更新失败

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如 Java 里面的同步原语 synchronized 关键字的实现也是悲观锁。
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。在 Java中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。

线程池的使用

线程池是一种常见的多线程编程技术,它允许我们在系统中使用一个固定数量的线程来执行任务,以免过多的线程拉低了系统的性能。
现线程池的使用

线程池的使用场景

并发任务处理:线程池可用于处理并发的任务,例如处理请求,批量处理数据,并行计算等,提高执行效率
异步任务执行:线程池可以用于执行异步任务,将任务提交给线程池后,可以立即返回继续执行后续代码,不必等待任务完成。适用于需要在后台执行耗时的任务,同时不阻塞主线程的场景。
定时任务调度、资源池管理

对接第三方指标接口,指标就是规定时间内,不能太慢,
线程池针对数据量比较大的情况下,使用线程池,

使用任务调度的时候使用线程池,比如用户下单,传统下单系统调用完,在调用库存系统,效率就会慢些
通过线程池,实现异步调用,效率就会更加快

线程池的七大参数

/**
 * @param corePoolSize 核心线程数 线程池中保持的线程数量,即使它们是空闲的也不会销毁
 * @param maximumPoolSize 最大线程数 线程池中允许接受的最大线程数量
 * @param keepAliveTime 最大存活时间 当前线程数大于核心线程数的时候
 * @param unit 最大存活时间的单位
 * @param wordQueue 工作队列 保证任务直到任务被提交到线程池的线程中执行
 * @param threadFactory 线程工厂  当线程池需要创建线程得时候会从线程工厂获取新的实例
 * @param handler 当线程数量等于最大线程数并且工作队列已满的时候,再有新的任务添加进来就会进入这             				
 * 				  个handler,可以理解为设置拒绝策略(此处不清楚的可以看一下
 * 				  ThreadPoolExecutor中的execute方法的注 释)
 *
 */
 pulic ThreadPoolExecutor(int corePoolSize
						  int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler){
}
参数说明
corePoolSize核心线程数量,线程池维护线程的最少数量
maximumPoolSize线程池维护线程的最大数量
keepAliveTime非核心线程的最长空闲时间,超过该时间的空闲线程会被销毁
unitkeepAliveTime的单位,有NANOSECONDS(纳秒)、MICROSECONDS(微秒)、MILLISECONDS(毫秒)、SECONDS(秒)
workQueue任务缓冲队列(阻塞队列)
maximumPoolSize线程池维护线程的最大数量
threadFactory线程工厂,用于创建线程,一般用默认的即可
handler线程池对拒绝任务的拒绝策略

创建一个核心线程数为5,最大线程数为10,任务队列容量为100的线程池

 // 创建一个线程
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 10,
                TimeUnit.SECONDS, new LinkedBlockingDeque<>(100), new ThreadPoolExecutor.DiscardPolicy());

ThreadPoolExecutor可以创建自定义线程池

Executors 和 ThreaPoolExecutor 创建线程池的区别

Executors 和 ThreadPoolExecutor 在创建线程池方面存在显著的区别。

Executors 是 Java 并发包 java.util.concurrent.Executors 中提供的一个工具类,它提供了一些静态方法来创建不同类型的线程池。这些线程池包括固定大小的线程池(FixedThreadPool)、可缓存的线程池(CachedThreadPool)、单线程的线程池(SingleThreadExecutor)以及可以执行延迟任务的线程池(ScheduledThreadPool 和 SingleThreadScheduledExecutor)。然而,使用 Executors 创建线程池存在一些问题。例如,FixedThreadPool 和 SingleThreadExecutor 的请求处理队列可能会堆积大量请求,从而消耗大量内存,甚至导致内存溢出(OOM)。而 CachedThreadPool 和 ScheduledThreadPool 允许创建的线程数量最大为 Integer.MAX_VALUE,这可能导致创建大量的线程,同样也可能引发 OOM(out of memory 内存溢出)。

另一方面,ThreadPoolExecutor 是 Java 中最原始的创建线程池的方式,也是更加推荐的方式。ThreadPoolExecutor 允许我们更加明确地控制线程池的运行规则,例如核心线程数、最大线程数、线程存活时间、任务队列等,从而避免资源耗尽的风险。因此,阿里巴巴的《Java开发手册》中建议避免使用 Executors 创建线程池,而是通过 ThreadPoolExecutor 来创建。

总的来说,虽然 Executors 提供了一些方便的方法来创建线程池,但由于其潜在的资源耗尽风险,使用 ThreadPoolExecutor 来创建线程池是更加安全和可控的选择。

什么是线程池?有哪几种创建方式?

在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内
存资源或者其它更多资源。
Executors 面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:
(1)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一
个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因
为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺
序按照任务的提交顺序执行。
(2)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一
个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保
持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。如
果希望在服务器上使用线程池,建议使用 newFixedThreadPool 方法来创建线程池,
这样能获得更好的性能。
(3) newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过
了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,
当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会
对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建
的最大线程大小。
(4)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时
以及周期性执行任务的需求。

线程池中 submit() 和 execute() 方法有什么区别?

接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行
Runnable 和 Callable 类型的任务。
返回值:submit()方法可以返回持有计算结果的 Future 对象,而 execute()没有
异常处理:submit()方便 Exception 处理

线程池中的状态
running:运行状态
stop:停止状态
shutdown:关闭状态
tidying:整理状态
terminated:终止状态

线程池如何自定义命名?

线程自定义命名
在Java中,我们可以通过实现ThreadFactory接口来自定义线程创建的过程。我们需要实现newThread方法,在方法中创建线程并设置名称

线程池的拒绝策略

1、AbortPolicy:
默认策略,在需要拒绝任务时抛出RejectedExecutionException;
2、CallerRunsPolicy:
直接在 execute 方法的调用线程中运行被拒绝的任务,如果线程池已经关闭,任务将被丢弃;
3、DiscardPolicy:
直接丢弃任务;
4、DiscardOldestPolicy:
丢弃队列中等待时间最长的任务,并执行当前提交的任务,如果线程池已经关闭,任务将被丢弃。

如何实现自定义拒绝策略
通过实现RejectedExecutionHandler接口,自定义一个拒绝策略类,重写它的rejectedExecution()方法;

多线程的使用场景

在项目开发中有很多使用多线程的情况
1、业务系统需要调用多台服务完成业务时,才能完成其他后续的业务,如果依次调用这些服务,那么会花费一定时间,网络io的等待,这时候使用线程池让多线程同时去调用多个服务
2、项目中使用异步的时候,也可以使用多线程的方式去处理,比如发短信发邮件,以及通知的操作
3、项目中大文件下载,或者是excel的导入时,也可以使用多线程方式去处理。从而提升咱们的效率

Java中大文件上传实现方式

1、断点续传:将大文件分割成多个小块进行上传、每个小块都有独立的唯一标识,当上传中断时,可以根据已上传的小块标识,从断点继续上传,而不需要重新上传整个文件,这样可以大大提高上传速度和减少传输数据
2、多线程上传,也是将文件分为多个块,并使用多个线程同时上传这些块,每个线程负责上传一个块,可以同时上传多个块,在上传完成后,服务器可以将这些块合并成一个完整的文件。

大文件上传

Sping面试题

你对Spring的理解?

Spring是一款轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。最主要的就是解决企业级应用开发的复杂性,就是简化Java开发

Spring最主要的核心是什么?

最主要的两个核心就是依赖注入(ID)和面向切面编程(AOP)

那么Spring由那些模块组成的呢?

spring core:提供了框架的基本组成部分,包括控制反转(IOC)和依赖注入(DI)
spring beans:是工厂模式的一个经典实现,spring将管理对象称为Bean
spring context:提供了一种框架式的对象访问方法
spring jdbc:用于简化JDBC
spring aop:提供了面向切面的编程实现,让你自定义拦截器,切点等
spring web:针对web的开发
spring test:spring提供的测试集成

Spring中用到了那些设计模式?

  1. 工厂模式:BeanFactory 就是简单工厂模式的体现,用来创建对象的实例;
  2. 单例模式:Bean 默认为单例模式。
  3. 代理模式:Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成
    技术;
  4. 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate,
    JpaTemplate。
  5. 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生
    改变时,所有依赖于它的对象都会得到通知被制动更新,如 Spring 中
    listener 的实现–ApplicationListener。

Java中的常见设计模式和使用场景

设计模式
设计模式从大的板块分为:创建型模式、结构型模式、行为型模式

  • 单例模式:指的是在一个类在运行期间始终只有一个实例,我们称之为单例模式
    单例模式典型的应用场景是spring中bean实例,它默认的就是singleton单例模式,应用程序的日志一般都是采用的单例模式或者多线程的线程池的设计一般采用单例模式
  • 原型模式:它指的是克隆来产生的一个新的对象,它的核心方法就是clone(),我们通过该方法可以复制一个新的对象
    原型模型的典型使用场景是Java语言中Object.clone()方法
  • 命令模式:它是将一个请求封装成一个对象,并且提供命令的撤销和恢复功能,将发送者、接收者和调用命令封装成独立的对象,已供客户端使用
    spring中的jdbctemplate就是用命令模式
  • 工厂模式:它提供了创建对象的最佳的方式,在工程模式中,我们创建对象时不会对客户端暴露创建逻辑,并且是通过一个共同的接口来指向新创建的对象,实现了创建者和调用者分离。工厂模式分为:简单工厂、工厂方法、抽象工厂模式
    springboot中的ioc就是使用的工厂模式

Spring控制反转是什么?

控制反转即IOC,它就是把以前的由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理,所谓的控制反转 的概念就是对组件控制权的转移,从代码本身转移到了容器

控制反转用什么用?

  1. 管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂的时候,如果依赖关系需要程序猿来维护的话,那是相当头疼的
  2. 解耦,由容器去维护具体的对象

什么是Spring的依赖注入?

Spring依赖注入(DI)是一种设计模式,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是容器动态的将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中

Spring依赖注入是一种设计模式,在传统的java开发中 ,对象之间的依赖通常是通过创建对象实例并直接调用它们的方法来实现,这种方式会导致代码的耦合度很高,通过spring的依赖注入,我们可以将对象之间的依赖关系移动到配置文件中,Spring容器负责创建对象并自动注入它们所需的依赖性,这样可以提高代码的可测性和可维护性

什么是Spring beans?

spring beans 是那些形成spring应用的java对象。它们被spring ioc容器初始化,装配,和管理,这些beans通过容器中配置的元数据创建,比如,以XML文件中的形式定义
可以通过xml文件进行配置
可以基于注解进行配置
可以基于Java的配置

当一个Java类被标记为@Component、@Service、@Repository等注解时,它就成为了一个Spring bean,并被Spring容器管理。Spring容器会自动创建和管理bean的生命周期,包括实例化、属性注入、依赖注入、初始化和销毁等过程。

Spring的bean作用域有那些?

1、默认是singleton,即单例模式
2、prototype,每次从容器调用bean时都会创建一个新的对象
3、request,每个http请求都会创建一个对象
4、session,同一个session共享一个对象
5、global-session

Spring的bean是线程安全的吗?

首先Spring的bean模式单例的,而且后端的程序,天然处于一个多线程的工作环境,所以bean基本是无状态的就是安全的
所谓的无状态的就是没有存储数据,即没有通过数据的状态来作为下一步操作的判断依据

@Component, @Controller, @Repository, @Service 有何区别?

@Component 是一个通用的注解,用于标记一个普通的Spring组件。当类不属于特定的层次(如控制层、服务层或数据访问层)时,可以使用@Component进行标注。
它是Spring IoC容器识别bean的一种方式,容器会自动检测并配置这些带有@Component注解的类。

@Controller 注解用于标记MVC架构中的控制层组件。
在Spring MVC中,@Controller注解的类负责处理来自客户端的HTTP请求,并返回相应的视图或数据。
控制器通常会与@RequestMapping、@GetMapping、@PostMapping等注解一起使用,以定义请求处理方法。

@Repository 注解用于标记数据访问层(DAO层)的组件。
它表示该类提供了对数据的存储、检索、更新、删除和搜索等操作。
在Spring框架中,@Repository注解的类通常用于封装对数据库的访问,使得持久层的接口能够提供数据的CRUD操作。

@Service 注解用于标记业务逻辑层的组件。
服务层通常包含业务逻辑的实现,比如调用数据访问层进行数据的操作,或者处理与业务相关的计算或逻辑。
@Service注解告诉Spring容器这个类是一个服务类,容器会自动扫描并创建其实例。
区别总结:

@Component是一个通用注解,用于标记任何Spring组件。
@Controller特定于MVC架构中的控制层,用于处理HTTP请求。
@Repository特定于数据访问层,用于封装对数据库的访问。
@Service特定于业务逻辑层,用于实现业务逻辑。

@Autowired注解有什么作用?

@Autowired注解是spring框架中的一个核心注解,用于自动装配,可以标记在字段,构造函数,setter方法上,告诉spring容器要自动装配这些属性或参数。
当使用@Autowired注解时,spring会在容器中查找匹配的bean,并将其注入到注解的属性或参数中,如果匹配多个bean,则根据@Qualifaler注解或其他规则来选择合适的bean注入

@Required注解有什么作用?

在spring5.1已废弃
@Required注解是spring框架中一个注解,用于标记在setter方法上,表示改属性必须在配置文件中进行配置,如果在bean的定义没有为改属性设置值或引用,则会抛出BeanInitializationException异常

@Autowired和@Resource之间的区别?

@Autowired可用于:构造函数、成员变量、Setter方法
@Autowired默认是按照类型装配注入的
@Resource默认是按照名称来装配注入的

@Qualifier 注解有什么作用

当您创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,您可以使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。

@RequestMapping 注解有什么用?

@RequestMapping注解是映射请求的url

Spring面向切面编程(AOP)?

OOP称为面向对象编程
AOP称为面向切面编程,作为面向对象的一种补充,将多个对象公共行为抽取并封装为一个可重用的模块,这个模块被称为切面,就是减少系统中重复的代码,降低了模块建的耦合度,提高了可维护性,一般用于日志,权限认证,事务处理等

SpringMVC面试

什么是SpringMVC,你对SpringMVC的理解?

SpringMVC是基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过模型-视图-控制分类器,将web层进行职责解耦,简化开发,减少出错

请描述Spring MVC的工作流程?

1、客户端发起请求:客户端通过浏览器或者其他发起http请求
2、DispatcherServlet接收请求:DispatcherServlet是spring前端控制器,接受http请求,并查找url对应的处理器
3、处理映射器:处理映射器会根据url查找对应的处理器(Controller),并返回给DispatcherServlet
4、HandlerAdapter处理适配器:HandlerAdapter负责将请求传递给处理器(Controller)进行处理,并将处理的结果封装到ModeAndView对象返回给DispatcherServlet
5、ViewResolver解析视图:ViewResolver根据视图名称查找对应的视图,并将视图返回给DispatcherServle。
6、视图渲染:DispatcherServlet将ModelAndView对象传递给视图,视图根据模型数据生成HTML、JSON或其他格式的响应数据,并将其返回给客户端。

1、用户发送请求到前端控制器(‌DispatcherServlet)‌:‌用户通过HTTP请求向服务器发送请求,‌这个请求首先到达前端控制器DispatcherServlet。‌DispatcherServlet是Spring MVC框架的核心组件之一,‌负责接收和处理所有的HTTP请求。‌

2、请求处理器的映射:‌DispatcherServlet接收到请求后,‌会调用HandlerMapping(‌处理器映射器)‌来确定请求应该由哪个Controller处理。‌HandlerMapping根据请求的URL和其他条件(‌如HTTP方法)‌找到对应的Handler。‌

3、执行处理器适配器:‌一旦找到了对应的Handler,‌DispatcherServlet会调用HandlerAdapter(‌处理器适配器)‌来执行这个Handler。‌HandlerAdapter负责将Handler(‌通常是Controller中的方法)‌与具体的请求进行匹配,‌并执行相应的逻辑。‌

4、处理请求并返回视图:‌Handler执行后,‌通常会返回一个ModelAndView对象,‌这个对象包含了处理结果的数据和要展示的视图信息。‌然后,‌这个ModelAndView对象会被传递给ViewResolver(‌视图解析器)‌,‌由ViewResolver将逻辑视图名解析为具体的视图(‌如JSP页面)‌。‌

5、渲染视图并返回响应:‌ViewResolver解析出具体的视图后,‌会将模型数据填充到视图中,‌然后DispatcherServlet将渲染好的视图返回给用户,‌完成整个请求-响应周期

Java注解原理是什么?

Java注解原理主要包括两个方面:注解定义和注解解析

注解定义:注解是通过@interface关键字定义的,本质上就是一个接口,注解可以用于类、方法、字段等元素上,并都有默认值

注解解析:Java提供了反射机制,使用反射API读取注解信息,并根据注解信息进行相应的处理。比如,通过注解可以试下自动配置、代码生成、代码检查等

Spring MVC怎么样设定重定向和转发的?

转发:在返回值前面加"forward:“,譬如"forward:user.do?name=method4”
重定向:在返回值前面加"redirect:“,譬如"redirect:http://www.baidu.com”

转发和重定向有什么区别?

转发是服务器内部的请求方式,而重定向是通过客户端向浏览器重新发送了一个新的请求
转发前后浏览器地址都是不会变的,而重定向后浏览器的地址是会发生改变的
转发只需要请求一次,而重定向需要请求两次,所以转发效率要高于重定向的

SpringBoot面试

什么是SpringBoot,说下你对springboot的理解

springboot是一款轻量级的开源的Java开发框架,主要就是简化了spring的难度,减少繁琐的配置,提供了各种启动器,开发能够快速上手

那么SpringBoot有什么优点呢?

1、容易上手,提升开发效率
2、开箱即用,减少了繁琐的配置
3、提供了非业务功能,内嵌服务端,安全管理,运行数据监控
4、没有代码生成,也不需要xml的配置
5、避免了大量的Maven冲突

SpringBoot 自动配置原理是什么?

添加注解@EnableAtuoConfigurtion和@Configuration就是自动配置的核心
1、SpringBoot会自动扫描所有classpath下的类,并寻找@Configuration的注解的类
2、在配置类中,如果条件满足相关的配置才会生效,比如数据库连接池,消息队列等

SpringBoot如何使用事务?

在springboot中有两种事务的方式:编程式事务和声明式事务
1、编程式事务
在springboot中有两种实现方法
1)使用TransactionTemplate 对象实现编程式事务;
2)使用更加底层的TransactionManager 对象实现编程式事务

要使用 TransactionTemplate 对象需要先将 TransactionTemplate 注入到当前类中 ,然后再使用它提供的 execute 方法执行事务并返回相应的执行结果,如果程序在执行途中出现了异常,那么就可以使用代码手动回滚事务,具体实现代码如下:

TransactionManager 实现编程式事务相对麻烦一点,它需要使用两个对象:TransactionManager 的子类,加上 TransactionDefinition 事务定义对象,再通过调用 TransactionManager 的 getTransaction 获取并开启事务,然后调用 TransactionManager 提供的 commit 方法提交事务,或使用它的另一个方法 rollback 回滚事务,它的具体实现代码如下:

2、声明式事务
只需要在方法或者类上加注解@Transactional 就可以实现在方法前执行,自动开启事务

@Transactional注解详情

链接:https://blog.csdn.net/qq_57581439/article/details/132086303

@Transactional注解可以作用于哪些地方?

1、作用于类:当把@Transactional 注解放在类上时,表示所有类的public方法都配置相同的事务属性信息
2、作用于方法:当类也配置@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息
3、作用于接口:不推荐这种方式,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效

@Transactional注解有那些属性?

propagation属性:

propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:

Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )

Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )

Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。

Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。

Propagation.NESTED :和 Propagation.REQUIRED 效果一样。

isolation 属性
isolation :事务的隔离级别,默认值为Isolation.DEFAULT

Isolation.DEFAULT:使用底层数据库默认的隔离级别。
Isolation.READ_UNCOMMITTED:读未提交
Isolation.READ_COMMITTED:读已提交
Isolation.REPEATABLE_READ:可重复读
Isolation.SERIALIZABLE:串行化

timeout 属性
timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

readOnly 属性
readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性
rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor属性
noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

@Transacational失效场景?

1、@Transactional 应用在非 public 修饰的方法上
原因:spring创建代理、添加事务、前提条件都是改方法是public的
2、@Transactional 注解属性 propagation 设置错误
3、@Transactional 注解属性 rollbackFor 设置错误
4、@Transactional中使用异常处理,比如try-cathy,可能导致事务回滚失效
解决方法:将异常抛出即可
5、抛出检查异常,比如FileNotFoundExcpetion(),会导致事务失效
原因:spring默认检查运行时异常,并不会检查编译时,只需要添加rollbackFor = Exception.class即可

有A、B两个方法,A方法有 @Transacational,B方法没有,A调用B方法,那么事务会生效吗

A方法(或其他带有@Transactional的方法)调用,并且A方法的事务传播行为允许这样做(默认的传播行为是Propagation.REQUIRED,它会使用已存在的事务或创建一个新的事务)。

有A、B两个方法,A方法有 @Transacational,B方法没有,B调用A方法,那么事务会生效吗

不会生效

分布式事务和微服务中如何使用事务?

通过Seata来实现分布式事务
通过seate配置nacos和mysql 添加spring seata的依赖
seata架构:seata事务管理中有三个角色:
TC (Transaction Coordinator) - 事务协调者: 维护全局和分支事务的状态,协调全局事务提交或回滚
TM (Transaction Manager) - 事务管理器: 定义全局事务的范围、开始全局事务、提交或回滚全局事务
RM (Resource Manager) - 资源管理器: 管理分支事务处理的资源,与TC交谈以注册分支事务和汇报分支事务的状态,并驱动分支事务提交或回滚
在这里插入图片描述
Seata提供了四种不同的分布式事务解决方案:

XA模式: 强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
TCC模式: 最终一致的分阶段事务模式,有业务侵入(常用)
AT模式: 最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式(常用)
SAGA模式: 长事务模式,有业务侵入

xa:事务注册,执行业务sql,但是不提交,而是等其他服务成功之后在进行提交,如果其他服务出现问题,那么就回滚
at:事务注册,执行业务sql,直接提交,但是会记录数据快照,如果有问题,那么会通过快照的形式进行数据回滚
tcc:和at差不多,执行提交事务,但是有代码入侵,需要实现三个方法来实现对事务的一个回滚操作
saga:一阶段:直接提交本地事务,成功则什么都不做; 失败则通过编写补偿业务来回滚业务

给发起全局事务的入口类或方法添加@GlobalTransactional注解,我这边就直接将order-service服务中的全局事务入口类OrderServiceImpl之前的@Transactional注解换成了@GlobalTransactional注解:
在这里插入图片描述
黑马视频讲解seata

seata详情知乎

SrpingCloud面试

SpringCloud

1、Nacos:服务注册于发现
2、EureKa:服务注册于发现
3、Rabbon:注册中心的负载均衡(轮询、权重)
4、Fegin:服务发送http请求
5、Zuul:网关
6、Gataway:网关,过滤器

Spring Cloud 和 dubbo 区别?

1、注册中心,dubbo是zookeeper,springCloud是eureka
2、服务网关,dubbo本身没有实现,而是通过第三方技术整合,springcloud有zuul路由网关

Hystrix是什么?

在微服务架构中,网络延迟,服务超时,服务失败都是常见问题,那么hystrix的设计就是帮助解决这些分布式服务的问题,提高系统的可用性,和稳定性和容错能力。
它包括熔断器:当某个服务出现故障或响应过慢时,它会自动触发跳闸的功能,从而避免雪崩效应,当服务恢复正常后,hystix重新恢复对服务的请求
包括隔离:通过线程池对不同服务之间的隔离,避免某个服务出现问题是影响到其他服务的调用
还包括降级、监控

Sentinel是什么

sentinel是一款微服务流量控制组件
Sentinel讲解

什么是雪崩问题:
多个服务之间依赖的,微服务调用链路中的某个服务故障,引起整个链路中所有的微服务都不可用,被称之为雪崩

sentinel的作用:
舱壁处理:限定每个业务能够使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离
熔断降级:由断路器统计业务执行的异常比例,如果超出阀值则回熔断改业务,拦截访问改业务的一切请求
流量控制:限制业务访问的QPS,避免服务因流量的突增而故障

Sentinel和Hystrix的区别

主要是红色的关系
在这里插入图片描述

Sentinel限流

簇点链路:就是项目内调用链路,链路中被监控的每个接口就是一个资源。默认情况下sentinel会监控springmvc每一个端点,因此spirngmvc的每一个端点就是调用链路中的一个资源

流量限流
热点参数限流

Nacos干什么用的,你项目里怎么用的?

nacos是服务注册与发现,通过微服务架构,多个服务之间的通信,通过naocs注册中心管理,也提供配置管理,通过配置文件来实现数据的共享和管理配置

nacos面试题:https://blog.csdn.net/u011919808/article/details/126851482

Nacos中的热部署如何实现

在nacos中配置一般都是开关设置,比如那些菜单是否开启是否使用
或者共享配置,通过注解@RefreshScope,添加在我们contoller层,获取到配置数据

Nacos配置加载顺序

Eureka注册中心

自己理解: 不同微服务之间需要调用数据,不需要通过请求调用,而是全部通过Eureka注册中心负载均衡(轮询、随机、权重)来实现多个微服务之间的调用和通信
如果在注册中心中某个微服务挂了,而注册中心会有心跳检查,没分钟监测你是否运行,如果没有那么在客户端不会在向这个服务发送请求

作用:
服务提供者启动时向eureka注册自己的信息
eureka保存这些信息
消费者根据服务名称向eureka拉取提供者信息
通过负载均衡的算法在服务列表中挑选
服务每30秒向eureka发送心跳检测,报告健康状态

Ribbon负载均衡

eureka通过ribbon来实现负载均衡
负载均衡策略
在这里插入图片描述

nacos配置ribbon

 	@Bean
    @LoadBalanced // 注解轮询
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

通过配置实现Ribbon的负载均衡策略,默认是轮询,下图修改为随机
在这里插入图片描述
Ribbon饥饿模式
在这里插入图片描述

Ribbon自定义负载均衡策略

iRule是所有负载均衡的父接口,那么需要自定义实现负载均衡,只需要实现iRule子接口
自定义随机的负载均衡

1、定义一个类,实现Irule的子类,也就是AbstractLoadBalancerRule ,实现两个方法,来自定义随机的负载均衡

/**
 * 自定义ribbon负载均衡的随机
 */
public class RibbonRandomCustom extends AbstractLoadBalancerRule {

    // 初始化信息
    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    /**
     * 选择
     */
    @Override
    public Server choose(Object o) {
        ILoadBalancer loadBalancer = this.getLoadBalancer();
        // 获取当前请求的实例
        List<Server> reachableServers = loadBalancer.getReachableServers();

        int random = ThreadLocalRandom.current().nextInt(reachableServers.size());

        Server server = reachableServers.get(random);
        if (server.isAlive()) {
            return null;
        }
        return server;
    }
}

2、通过配置文件来启动自定义的配置

userservice: # 要做配置的微服务名称
  ribbon:
    NFLoadBalancerRuleClassName: RibbonRandomCustom # 自定义随机的负载均衡

或者通过添加bean里面

Nacos注册中心

Nacos和Eureka的区别?

1、eureka中只有临时实例,而nacos中有临时实例和非临时实例
临时实例:30秒心跳检测 非临时实例:注册中心主动发送请求到实例,判断是否健康,如果没有则一直等待,知道好了为止
2、eureka中注册只能通过定时拉取消费者,而nacos注册可以通过拉取和订阅推送给消费者
3、nacos有配置中心,而eureka没有配置中心

http客户端Feign

什么是Feign?
feing是一个声明式的http客户端,其作用就是帮助我们优雅的实现http请求发送,而不是使用restTemplate发送请求。
如何使用?
1、导入依赖Fegin

<!-- http客户端Fegin -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency> 

2、添加EnableFeignClients在application
3、添加celient接口

@FeignClient("userservice")
public interface UserClient {

    @GetMapping("/user/{id}")
    User fandUserById(@PathVariable("id") Long id);
}

Feign是基于ribbon

通过ribbon实现负载均衡

Feign契约配置是什么

也就是原来早期springclound中使用原生feign,在netfix停更后才使用的openfeign
如果有老的项目,使用的原生feign的原生注解,在不改代码的情况下,对原来fegin的进行升级,

Feign的性能优化

Feign底层的客户端实现

  • URLConnection:默认实现,不支持连接池
  • Apache HttpClient:支持连接池
  • OKHttp:支持连接池

实现:
在这里插入图片描述

抽取Feign
在这里插入图片描述

Gateway网关

为什么需要网关
在这里插入图片描述

Gateway如何配置

路由id:路由唯一标识
uri:路由目的地,支持lb(loadbalance)和http两种
predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
filters:路由过滤器,处理请求或响应

Gateway断言和过滤器

断言:断言(Predicate)这个词听起来极其深奥,它是一种编程术语,我们生活中根本就不会用它。说白了它就是对一个表达式进行 if 判断,结果为真或假,如果为真则做这件事,否则做那件事。

Gateway如何配置全局过滤器

1、实现全局过滤器GlobalFilter,重写filter方法
2、添加@Component注解,注入到spring容器
3、实现Ordered 接口,设置过滤器执行顺序

// GlobalFilter 全局过滤器
// @Order 设置过来执行顺序 也可以实现 Ordered 这个接口 通过getOrder来设置过滤器的执行顺序
// @Order(-1)
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1、获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> params = request.getQueryParams();
        // 2、获取参数中的 Authentication  参数 todo 真实开发会验证白名单、验证token、是否登录
        String auth = params.getFirst("Authentication");
        // 3、判断参数中是否等于admin
        if (auth.equals("admin")) {
            // 4、放行
            return chain.filter(exchange);
        }
        // 5、拦截 设置状态码
        exchange.getResponse().setRawStatusCode(401);
        return exchange.getResponse().setComplete();
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

Gateway过滤器执行顺序

Gateway实现跨域问题

跨域:域名不一致就是跨域,主要包括
域名不同:www.taobao.com和www.taobao.org和www.jd.com和miaosha.jd.com
域名相同:端口不同:locahost:8080和localhost:8081
跨域问题:浏览器禁止请求的发起者与服务端发生ajax请求,请求被浏览器拦截的问题

网关处理跨域同样采用CORS方案

MyBatis面试题

MyBatis是什么?

MyBatis是一款ORM半自动化框架,MyBaits避免了几乎所有的JDBC的代码和手动配置参数,MyBaits可以使用简单的xml或注解来配置映射原生类型、接口和Java的POJO

ORM是什么

ORM是对象关系映射,简单来说就是数据库数据与java对象pojo的映射关系的技术

MyBatis是如何连接数据库的?

1、在配置文件指定数据源,URL和用户名和密码等信息
2、使用数据源创建SqlSessionFatctory对象
3、通过SqlSessionFatctory创建SqlSeesion对象
4、在SqlSeesion对象中执行SQL语句,包括更新,删除,插入,更新等

那么JDBC是如何连接数据的?

1、Java通过类名Class.forName()方法加载指定的数据库驱动类
2、创建数据库连接,使用DriverManager.getConnection()方法来创建与数据库的连接,需要指定数据库的URL、用户名和密码等信息
3、创建Statement对象,使用statement对象执行sql语句,比如查询、更新、插入操作
4、处理结果集,通过ResultSet对象处理查询结果,包括获取结果集中的数据等
5、最后调用相关的close()方法关闭资源

  public static void main(String[] args) throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo", "root", "chaoge");
        // DriverManager 注册驱动
        // Connection 数据库连接对象  url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
        Statement stat = connection.createStatement();
        //执行 sql 语句的对象
        String sql = "SELECT * FROM t_admin";
        ResultSet rs = stat.executeQuery(sql);
        // 执行 增删改查 (DML)语句用 int executeUpdate(Sting sql);
        // 执行 DQL 语句 ResultSet executeQuery(String sql);
        // 对象释放 void close();
        while (rs.next()){
            System.out.println(rs.getInt("id") + "\t" + rs.getString("username"));
        }
        connection.close();
        stat.close();
    }

MyBatis的编译步骤和工作原理?

编译步骤:
创建SqlSessionFatory
通过sqlSessionFatory创建SqlSession
通过sqlSession执行数据操作
调用seesion.commit()提交事务
调用seesion.close()关闭会话
工作原理:
读取mybatis配置文件
加载映射文件
创建会话对象
executor执行器执行sqlsession中sql语句
执行mappedstatement对象

为什么需要预编译

定义:SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译
自己理解:简单来讲预编译就是为了防止重复的编译,也就是防止重新执行执行过的SQL语句
预编译主要是为了提高SQL查询的执行效率和安全性
1、预编译可以减少重复的编译的开销
2、预编译可以提高SQL查询的执行效率
3、预编译可以提高应用程序的安全性

MyBatis缓存机制,从一级缓存到二级缓存

缓存提高查询性能,减少了跟数据库交互的次数,从而也减轻了数据库承受的压力
一级缓存:
1、一级缓存模式是默认开启的
2、一级缓存作用域在SqlSession
3、如果中间有对数据的更新操作,则将清空一级缓存
自己的理解:一级缓存
当用户查询一个sql语句时,会向数据库做查询,第一次查询会把数据放入sqlsession中做缓存,第二次查询不会再向数据库中查询数据,而是重缓存中获取,减轻数据压力,但是用户做更新操作,将会清空一级缓存,在查询数据库,重新把数据翻入sqlsession缓存中;
二级缓存
1、二级缓存默认关闭的,需要开启二级缓存
2、在yml配置文件中cacheEnable设置为ture,然后在mapper.xml中添加一个cache标签即可;
自己的理解:二级缓存
二级缓存的范围就是mapper级别,二级缓存和 一级缓存一样的,二级缓存的是多个sqlsession去操作同一个mapper映射的sql语句,然后多个sqlsession可以共用二级缓存这样的一个思想,它是跨sqlsession的。

#{}和${}的区别?

#{}是占位符,表示一个参数,KaTeX parse error: Expected 'EOF', got '#' at position 30: …直接把参数拼入SQL语句当中 #̲{}是可以防止SQL注入的,{}是没有防止SQL注入的

MyBaits中在mapper中如何传递多个参数?

方法一:顺序传参法

public User selectUser(String name, int deptId);

<select id="selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{0} and dept_id = #{1}
</select>

#{}里面的数字代表传入参数的顺序。
这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。

方法二:使用@Param注解传参法

public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);

<select id="selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>

#{}里面的名称对应的是注解@Param括号里面修饰的名称。

这种方法在参数不多的情况还是比较直观的,推荐使用。

方法三:Map传参法

public User selectUser(Map<String, Object> params);

<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>

#{}里面的名称对应的是Map里面的key名称
这种方法适合传递多个参数,且参数易变能灵活传递的情况。

方法四:Java Bean传参法

public User selectUser(User user);

<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>

#{}里面的名称对应的是User类里面的成员属性。
这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用。

Java中MyBatis如何获取生成的主键

一、对于支持主键自增的数据库(MySQL)
mapper中parameterType(参数类型)应该是Java实体类或Map,当数据插入的时候通过getUserId来获取主键

二、对于不支持自增的数据库(Oracle)
对于Oracle这样的数据,没有提供自增主键的功能,而是使用序列的方式获取自增主键
可以使用< selectKey >标签来 获取主键的值

如何在Mybatis插入数据返回Mysql自增id

通过useGeneratedKeys=“true"和 keyProperty=”id"

<insert id="insertUser" parameterType="com.example.User" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user(username, password) VALUES(#{username}, #{password})
</insert>

Java中当实体类中的属性名和表中的字段名不一样 ,怎么办

一、如果使用mapper文件
1、通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
2、通过< resultMap >来映射字段名和实体类属性名的一一对应的关系
二、如果使用对应表
1、可以通过Column注解 来指定实体类属性对应的表中字段名
例如:
@Column(name = “table_column_name”)
private String entityAttributeName;

Java实体类中属性如何不被持久化到数据库中-

通过注解@Transient注解,该注解被标记不会被持久化到数据中

MyBatis的分页方式?

分页方式分为逻辑分页物理分页
逻辑分页:所谓的逻辑分页,是指使用mybatis自带的RowBounds进行分页,它会一次性查出多条数据,然后在检索分页中的数据,具体一次性查询多少条数据
物理分页:而物理分页,是从数据库中查询指定条数的数据,而我们用的分页插件pageHelper实现了物理分页
怎么实现物理分页的呢?
就是在mybatis内部定义了一个拦截器,拦截你的sql语句,添加limit语句,重写sql语句;

Mybatis中resultType和parameterType

resultType:数据库返回对象类型
parameterType:参数类型,接口中方法参数的类型, 类型的完全限定名或别名
注意:parameterType不是强制的,mybatis通过反射机制能够发现接口参数的数据类型 可以省略不写(一般省略不写)

Mybatis中当字段名和属性名不一致时?

1、mybatis-config.xml在配置文件中开启驼峰映射下划线功能

<settings>
	<!--        将下划线映射成小驼峰-->
	<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

2、resultype只能解决字段名和属性名一样的情况,当不一样时可以使用resulttmap

 <!--resultmap:设置自定义映射关系
    id:唯一标识符不能重复 type:指映射关系中实体类类型
    id:主键的映射关系 result:普通字段的映射关系
    property:属性名 column:字段名
    属性全部需要写-->
    <resultMap id="UserResultMap" type="User">
        <id property="uid" column="uid"></id>
        <property property="userName" column="user_name"></property>
        <property property="password" column="password"></property>
    </resultMap>
    <select id="getAllUser" resultMap="UserResultMap">
        select * from user ;
    </select>

3、为mapper.xml的sql语句中为字段起别名,来保持字段名与属性名一致。

<select id="getUserByName"  resultType="User">
    select eid,emp_name empName,age,sex from user where emp_name = #{empName}
</select>

Mybatis-plus分页查询

在实际的项目中,经常需要进行分页查询。MyBatis Plus提供了一种简单的分页查询方式,只需要在查询方法中传入一个分页参数即可。例如:

Page<User> page = new Page<>(1, 10);
IPage<User> userPage = userMapper.selectPage(page, null);
List<User> userList = userPage.getRecords();

在以上代码中,我们创建了一个Page对象,表示要查询第1页的10条记录。然后,我们使用selectPage方法进行查询,并将查询结果存储在userPage对象中。最后,我们从userPage对象中获取查询结果。

JAVA其他面试八股文

Mysql如何解决幻读、脏读、不可重复读?

1、幻读:使用间缝隙(gap lock)或行级锁(行锁)避免。例如使用 SELECT … FOR UPDATE 或 SELECT … LOCK IN SHARE MODE 语句获取共享和排他锁
2、脏读:使用事务管理以及MVCC(多版本并发控制)避免。当一个事务修改数据时,其他事务无法看到改修改,直接事务提交。同时也可以使用锁来避免脏读
3、不可重复读:使用行级锁(行锁)或,MVCC(多版本并发控制)避免。在同一事务中读取相同的数据会得到相同的结果,即使其他事务对该数据进行了修改。

脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。

不可重复读 :是指在一个事务内,多次读同一数据。

幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。

如何保证MQ消息不丢失,如何实现延迟推送等?

保证MQ消息不丢失
1、消息持久化:将消息存储到磁盘中,即使服务器宕机,也能够恢复消息
2、多副本备份:通过在多个节点上保存消息的副本来提高消息的可靠性和

MQ如果有那些消息丢失的情况,如果丢失了那么怎么做

出现消息丢失的情况
1、生产者发送消息丢失
2、MQ服务器内部消息丢失
3、消费者处理消息失败导致消息丢失
4、网络传输过程中消息丢失

出现消息丢失了如何处理:
1、日志记录
在生产者和消费者中记录所有发送和接受的消息详情信息,包括消息id、时间戳、内容等
2、重试机制
在生产者和消费者中实现重试逻辑,当消息发送或者处理失败时,尝试重新发送或者处理消息
可以设置重试次数和重试间隔,以避免无限循环和服务器过载
3、消息确认机制
使用mq提供的消息确认机制来确保消息被成功处理
当消费者成功处理消息后,发送确认消息给MQ服务器。如果消费者崩溃或处理失败,消息将不会被确认,并可以重新发送。
4、消息持久化
将消息持久化到磁盘或其他存储介质中,以确保即使MQ服务器故障,消息也不会丢失
当MQ服务器恢复后,可以从持久化存储中恢复消息并继续处理。
5、分布式事物

6、死信队列
为未能成功发送消息的设置一个死信队列
当消息达到最大重试的次数,TTL过期或者其他条件时,讲消息路由到死信队列中
你可以定期检测死信队列,手动处理或重新发送这些消息
7、

MQ出现消息丢失了,什么情况下可以恢复?

1、生产者发送消息丢失:
网络故障:如果网络故障是暂时的,并且生产者有重试逻辑,那么在网络恢复后,消息可以重新发送。
生产者发送消息到错误的主题或队列:这种情况通常意味着消息被发送到了不期望的位置,而不是真正的丢失。要恢复,需要修改生产者的配置或代码,以确保消息发送到正确的主题或队列。
生产者程序崩溃:如果生产者有持久化机制(如事务性消息或消息确认机制),并且配置了重试逻辑,那么在生产者恢复后,可以重新发送丢失的消息。.

2、MQ服务器内部丢失消息:
MQ服务器故障:如果MQ服务器有持久化存储,并且配置了正确的复制和故障转移策略,那么在服务器恢复后,消息可以从持久化存储中恢复。但是,如果没有持久化或配置不当,消息可能会永久丢失。
磁盘损坏:如果磁盘损坏并且没有备份,那么存储在上面的消息可能会永久丢失。如果有备份,则可以从备份中恢复。
数据同步问题:这通常与集群同步或复制有关。如果配置了正确的同步策略并且集群中的其他节点上有消息的副本,那么消息可以从其他节点恢复。

3、消费者处理消息失败导致丢失:
消费者程序崩溃:如果消费者有重试逻辑和持久化机制(如消息确认机制),那么在消费者恢复后,可以重新接收并处理未确认的消息。
处理消息时出现异常:如果异常是暂时性的,并且消费者有重试逻辑,那么消息可以在异常解决后重新处理。
消费者处理速度过慢导致消息超时:这通常不是消息丢失,而是消息被重新放入队列以供其他消费者处理。如果消费者能够在超时之前处理完消息,则不会发生丢失。.

4、网络传输过程中丢失消息:
网络故障:如果网络故障是暂时的,并且生产者有重试逻辑,那么在网络恢复后,消息可以重新发送。
网络拥塞:这通常不会导致消息永久丢失,但可能会导致消息传输延迟。如果生产者或消费者有适当的超时和重试机制,那么消息最终可以成功传输和处理。

MQ中如何实现消息重试机制和消息确认机制?

1、消息重试机制
原因:生产者因为网络波动或者连接mq失败
通过配置文件设置消息重试机制:
retry开启消息重试机制,设置消息重试次数和重试时间间隔
建议连接200毫秒、重试次数3次、重试间隔1秒

listener:
      direct:
        retry: # 是否支持重试
          enabled: true
          max-attempts: 3  # 重试3次,超过3次抛出异常
          max-interval: 1000 # 重试间隔1s
connection-timeout: 200  # 设置连接MQ的时间 建议200毫秒

通过配置RetryTemplate消息监听器
并在监听器中捕获异常,然后使用RetryTemplate来重试处理逻辑。
@Retryable(maxAttempts = 5, backoff = @Backoff(delay = 2000))

2、生产者消息确认机制
rabbitmq支持手动确认消息,
rabbitmq有两种确认机制,publisher confirm和publisher return两种确认机制,开启确认机制后,在mq成功收到消息后会返回确认消息给生产者,返回的结果有以下的几种情况:

  • 消投递了MQ,但是路由失败,此时会通过publisher return返回路由异常原因,然后返回ACK,告知投递成功
  • 临时消息投递了MQ,并且入队成功,返回ACK,告知投递成功
  • 持久化消息投递到了MQ,并且入队完成持久化,返回ACK,告知投递成功
  • 其他的情况都会返回NACK,告知投递失败

如何实现,接受ack
配置文件设置

 rabbitmq:
	publisher-confirm-type: correlated # 开启 publisher confirm 机制,并设置confirm类型
    publisher-returns: true  # 开启 publisher return 机制  一般是路由导致,或者代码导致,一般情况下是不用开启return机制的

publisher-confirm-type有三种模式:
none:关闭confirm机制
simple:同步阻塞等待mq的回执消息
correlated :mq异步回调方式返回回执消息

/**
     * 发送消息确认机制
     */
    public void senMQCallback() {
        //创建一个CorrelationData Correlation:关联
        CorrelationData correlationData = new CorrelationData();
        //设置消息的id
        correlationData.setId("msg-888888");

        //1、准备好 回调函数:Callback,用于接收 将来:Future 的确认结果
        correlationData.getFuture().addCallback(
                /**
                 * 当消息发送没有出现异常时,这个方法会被调用
                 */
                result -> {
                    if (result.isAck()) {
                        System.out.println("发送消息成功,消息已到达交换机,消息id:" + correlationData.getId());
                    } else {
                        System.out.println("发送消息成功,消息没有到达交换机,消息id:" + correlationData.getId() + " 原因:" + result.getReason());
                    }
                },
                /**
                 * 当消息发送出现异常时,这个方法会被调用
                 */
                ex -> {
                    System.out.println("发送消息异常,消息id:" + correlationData.getId() + " 异常:" + ex);
                }
        );

        // 2、通过ListenableFutureCallback实现
        // correlationData.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
        //     @Override
        //     public void onFailure(Throwable throwable) {
        //        System.out.println("发送消息异常,消息id:" + correlationData.getId() + " 异常:" + ex);
        //     }
        //
        //     @Override
        //     public void onSuccess(CorrelationData.Confirm confirm) {
        //         if (confirm.isAck()) {
        //             System.out.println("发送消息成功,收到ack");
        //         }else {
        //             System.out.println("发送消息失败,收到nack");// 做消息重发
        //         }
        //     }
        // });

        //自己随便定一个没有的交换机
        rabbitTemplate.convertAndSend("xxxx.exchange", "demo", "到达交换机了吗", correlationData);
    }

3、消费者消息确认机制
RabbitMQ 同样也支持消费者确认机制,即当消费者处理消息后可以向 MQ 发送 ack 回执,当 MQ 收到 ack 回执后才会删除该消息。而Spring AMQP 则允许配置三种确认模式:
manual:在代码中手动 ack,需要在业务代码结束后,调用Spring AMQP 提供的 API 发送 ack,但是这种情况存在代码侵入的问题。
auto:基于 AOP 自动发送 ack,由 Spring 监测 listener 代码是否出现异常,没有异常则返回 ack;抛出异常则返回 nack;
none:关闭 ack,MQ 假定消费者获取消息后会成功处理,因此消息投递后立即被删除。

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1
        acknowledge-mode: auto # none,关闭ack;manual,手动ack;auto:自动 ack

特殊情况:配置auto
出现了rabbitmq发送给消费者消息,消费者处理失败又返回给你mq,一直重复
通过retry,本地重试,设置重试次数

spring:
  # rabbitmq配置
  rabbitmq:
    listener:
      simple:
        prefetch:
        acknowledge-mode: auto  # 消费者自动处理消息 交给spring
      direct:
        retry: # 是否支持重试
          enabled: true
          max-attempts: 5  # 重试5次,超过5次抛出异常
          max-interval: 3000 # 重试间隔3s

MQ消息可靠性

消息重试机制
消息确认机制
消息持久化
消息日志

MQ消息重复了怎么办

链接:https://blog.csdn.net/2401_84182602/article/details/137659992
出现重复的原因:
1、网络问题
2、消费者故障
3、消费者设置重复传递策略
4、消费者超时设置不当
解决方案:
1、消息幂等性(最有效)
消息幂等性是一种处理重复消息的有效方法。它要求消息的处理逻辑保持幂等性,即多次处理相同消息的效果与处理一次相同。这意味着如果消息已经成功处理过一次,再次处理相同消息时不会产生副作用。
2、消息去重
另一种解决重复消费问题的方法是使用消息去重机制。这种方法通过记录已经消费过的消息,然后在消息到达时检查它是否已经在记录中存在,从而避免重复处理。
3、消息确认机制
4、事务性消费
5、消息状态追踪

MQ如何创建死信队列

1、消息处理失败:消息没有正常被消费,消费代码出现异常无法正常处理一条消息时,该条消息可以标记为死信。
2、消息过期:RabbitMQ中消息可以设置过期时间,如果在规定时间内没有被消费,它可以被认为是死信并被发送到死信队列。
3、消息被拒绝:当消费者明确拒绝一条消息时,它可以被标记为死信并发送到死信队列。
4、消息无法路由:当消息不能被路由到任何队列时,例:没有绑定关系或者是路由键,消息可以被发送到死信队列。

1、创建死信队列:用来存储死信消息的队列
2、创建死信交换机:定义一个死信交换机
3、死信队列和死信交换机进行绑定:将死信队列和死信交换机进行绑定,以便消息发送到死信队列上
4、在主队列上配置死信属性:通过设置"x-dead-letter-exchange","x-dead-letter-routing-key"属性指定死信消息应该被发送到那个交换机和路由键

MQ怎么保证消息一致性?

事务消息:一些MQ支持事务消息,例如,RabbitMQ的事务支持或者Apache Kafka的事务支持,可以保证消息要么全部成功,要么全部失败,从而保证消息的一致性。

消息确认机制:大多数MQ系统都支持消息确认机制,比如RabbitMQ的发送确认机制、消费者确认机制,可以通过配置这些确认机制来保证消息被正确处理。

分布式事务:如果涉及多个系统之间的消息传递,可以使用分布式事务(例如XA事务)来保持系统之间的消息一致性。

消息跟踪机制:通过消息跟踪机制,跟踪消息的生产、传递、消费过程,在出现异常时可以进行回滚或重试。

消息重试机制:配置消息队列客户端的消息重试策略,当消费者处理消息失败时,可以进行一定次数的重试。

消息持久化:将消息持久化到MQ中,即使系统崩溃,消息也不会丢失,可以在系统恢复后重新处理。

死信队列和延时队列:配置死信队列(Dead Letter Queue)和延时队列,可以在消息处理失败时将其放入特定队列,并在未来特定时间重新处理。

MQ出现消息堆积,如何进行紧急扩容?

1、首先分析堆积的原因
消费者处理消息慢,导致消息堆积
消费者的计算资源(如cpu、内存、磁盘/io)不足时,导致处理速度下降
网络原因网络慢导致消息传递慢,从而消息堆积
配置的问题,如队列长度限制,消费者不足等导致消息堆积

针对以上原因,进行临时的扩容策略

1、增加消费者数量:
在RabbitMQ中,多个消费者可以并行地从同一个队列中接收消息,这称为工作队列模式。通过增加消费者数量,可以显著提高消息的处理速度,从而减少积压。
可以根据当前消费者处理能力和消息积压量,动态调整新增消费者的数量。
2、优化消费者性能:
检查并优化消费者的代码和配置,确保其能够高效地处理消息。
改进消费者处理消息的算法,减少处理时间。
为消费者分配更多的计算资源,如增加CPU核心数、内存或磁盘I/O带宽。
如果消费者是单线程的,考虑将其改为多线程或异步处理模式。
3、增加RabbitMQ节点:
如果单个RabbitMQ节点的性能达到瓶颈,可以考虑增加节点来扩展RabbitMQ集群。
通过添加更多的RabbitMQ服务器,可以分散消息处理负载,提高系统的整体处理能力。
4、调整RabbitMQ配置:
调整队列的配置参数,如增加队列的最大长度、调整内存阈值等,以更好地管理消息积压。
如果消息有不同的优先级,可以配置RabbitMQ使用优先级队列,确保高优先级的消息能够优先被处理。
5、创建临时队列:
在消息积压严重时,可以创建临时队列来分流积压的消息。
部署额外的消费者来处理这些临时队列中的消息,以加快消息的处理速度。
一旦积压问题得到缓解,可以删除这些临时队列。

如何保证MQ消息的顺序性

1、单个队列与单个消费者
将需要保存顺序的消息发送到用一个队列中去
确保队列中的消费者数量限制为11个,这样同一个时刻只有一个消费者能够处理消息,从而确保处理的顺序
2、消息排序字段
在消息中间添加一个排序的字段,消费者在处理消息时根据该字段进行排序,保证消息的顺序性
3、有序消息中间件
如RocketMq

如何实现MQ延迟消息

1、通过设置死信交换机
声明一个具有 TTL 的队列。TTL 可以是消息级别的(在发布消息时设置)或队列级别的(在声明队列时设置)。当消息在队列中的时间超过 TTL 时,消息将变为“死信”。
声明一个死信交换(DLX)和一个与该交换绑定的队列。当消息变为死信时,它将被发送到 DLX,然后路由到与该交换绑定的队列。
消费者从与 DLX 绑定的队列中接收消息。

2、通过mq插件来设置过期时间
安装插件(如果你的 RabbitMQ 服务器支持)。
声明一个带有 x-delayed-type 参数的交换,该参数指定了实际要使用的交换类型(例如,direct、topic 等)。
当你发布消息时,你可以使用 x-delay 属性来指定消息的延迟时间(以毫秒为单位)。
消费者从与延迟交换绑定的队列中接收消息。

项目中是如何使用MQ的?

mq主要用于异步处理,应用解耦,流量削锋和消息通讯四个场景
异步处理:
场景说明:在用户注册或者用户在开票成功情况下,需要发送邮箱和短信
用户注册保存注册信息时同时使用mq发送邮箱和短信**

应用解耦:**
场景说明:用户下单时,订单系统需要通知库存系统,传统做法是通过订单系统调用库存系统
通过mq
订单系统:在用户下完单后,将消息写入消息队列,返回用户下单成功
库存系统:订阅下单消息,采用拉/推的方式,获取下单信息,库存通过下单信息,进行库存操作

流量削锋
应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。
秒杀系统,通过mq可以控制活动人数,可以抗压短时间的高流量,高并发
通过请求秒杀系统,服务接受后,首先写入消息队列,假如消息队列长度超过最大数据量,则直接抛弃用户请求或者跳转到错误页面,秒杀业务根据消息队列中请求消息,在做后续处理

消息通讯
消息队列配置了高效的通信机制,也因此可以使用在纯的消息通讯,比如实现点对点消息队列,或者聊天室等
点对点通讯,客户端A和客户端B使用同一队列,进行消息通讯。

https://blog.csdn.net/LinggoLing/article/details/128253292

项目中是如何使用ES的?

es的最主要的作用就是搜索,在海量的数据中,找到对应的数据

那么mysql也可以在海量的数据中找打对应的数据呢,为什么不用mysql,而用es呢
1、一般搜索比如商品名称,小米手机,那么在mysql就需要使用like查询,那么mysql添加了索引使用like查询都会失效,从而无法实现快速搜索
2、es使用倒排索引,快速的查找数据

一般最多用于搜索,商城搜索,图书搜索,通过关键字

那么es是如何实现高亮的
es通过highlighting查找到关键字

项目中是如何使用redis的?

redis一般用于缓存、消息队列、排行榜和计数器、分布式锁
缓存:
第一次访问请求数据库,然后讲数据放入redis中做缓存,下次就请求缓存中的数据,从而减轻对数据库访问的压力

消息队列:
通过redis实现消息队列

排行榜和计数器:
存储用户行为数据,通过有序集合实时计算排名

分布式锁:
利用 Redis 提供的原子性操作和过期时间功能,实现分布式环境下的锁机制

在项目中synchronzied的使用场景

sychronzied在多线程情况下,

在项目中双亲委派的使用场景

反射的使用场景

那么在你们redis中一般存储那些数据呢?*
在redis中,可以存储很多不同类型数据,比如string类型,一般可以存储字符串和token和验证码
list集合一般存储数据列表,比如热点的博客列表和新闻列表
set是无序集合可以做交集并集合集
zset是有序集合做一般存储排行榜,
hash存储一些对象,比如用户对象

O(n)时间复杂度

在计算机科学中,时间复杂度是一个用于描述算法执行时间如何随着输入大小变化的度量。通常,我们使用大O表示法(Big O notation)来表示时间复杂度。
当我们说某个操作的时间复杂度是O(1)时,我们意味着这个操作的执行时间是一个常数,它不依赖于输入的大小。换句话说,无论输入有多大,该操作所需的时间都是固定的。例如,访问数组或列表中的某个固定索引位置通常具有O(1)的时间复杂度,因为你可以直接计算内存地址并访问它,而不需要遍历整个数据结构。

然而,当我们说某个操作的时间复杂度是O(n)时,我们意味着这个操作的执行时间与输入的大小n成线性关系。也就是说,如果输入的大小翻倍,执行时间也大致会翻倍。例如,遍历一个列表或数组中的每个元素通常具有O(n)的时间复杂度,因为你必须检查每个元素一次。

在你提到的ArrayList的add(int index, E element)方法中,如果在头部(即索引0)插入元素,那么需要移动头部之后的所有元素以腾出空间给新元素。因此,这个操作的时间复杂度是O(n),因为你需要移动n个元素(假设列表中有n个元素)。

相比之下,LinkedList是一个双向链表实现,它在头部或尾部插入元素时只需要修改几个指针,而不需要移动任何元素。因此,在LinkedList中,在头部插入元素的时间复杂度是O(1)。

总结:
O(1)时间复杂度意味着操作所需的时间是常数,不随输入大小变化。
O(n)时间复杂度意味着操作所需的时间与输入大小成线性关系。

除了O(1)和O(n),还有多种常见的时间复杂度表示方法,它们用于描述算法执行时间随输入规模增长的不同方式。以下是一些常见的时间复杂度表示及其解释:

O(log n):对数时间复杂度。这表示算法的执行时间随着输入规模n的对数增长而增长。例如,二分查找算法就具有O(log n)的时间复杂度。

O(n2):平方时间复杂度。这表示算法的执行时间随着输入规模n的平方增长而增长。嵌套循环的算法通常具有O(n2)的时间复杂度。

O(n^3):立方时间复杂度。这表示算法的执行时间随着输入规模n的立方增长而增长。尽管这种时间复杂度的算法在实际应用中可能较少见,但在某些情况下(如三维空间中的某些计算)可能会出现。

O(2^n):指数时间复杂度。这表示算法的执行时间随着输入规模n的指数增长而增长。这种时间复杂度的算法通常被认为是非常低效的,除非n非常小。

O(n!):阶乘时间复杂度。这表示算法的执行时间随着输入规模n的阶乘增长而增长。这种时间复杂度的算法在实践中很少使用,因为即使对于相对较小的n值,计算量也会迅速变得非常大。

O(n log n):线性对数时间复杂度。这表示算法的执行时间介于线性时间复杂度和平方时间复杂度之间。许多高效的排序算法(如归并排序和快速排序)具有O(n log n)的时间复杂度。

需要注意的是,时间复杂度只是衡量算法效率的一个方面。在实际应用中,还需要考虑空间复杂度(即算法所需的额外存储空间)以及算法的实际运行时间等因素。此外,对于某些问题,可能存在多种不同时间复杂度的算法,需要根据具体需求选择合适的算法。

什么是RestTemplate

    RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。

    传统情况下在java代码里访问Restful服务,一般使用Apache的HttpClient。不过此种方法使用起来太繁琐。Spring提供了一种简单便捷的模板类RestTemplate来进行操作:

如何实现定时任务

JDK的Timer和TimerTask
Quartz异步任务调度框架
分布式定时任务XXL-JOB
Spring Task注解@Scheduled

在sp
ringboot中

通过注解@EnableScheduling开启定时任务,@Scheduled实现定时任务

/**
 * 定时任务测试
 * 参考链接:https://www.jb51.net/program/285292j3o.htm
 * @EnableScheduling 开启定时任务
 */
@Component
@EnableScheduling
public class ScheduledTaskTest implements SchedulingConfigurer {

    @Autowired
    private ScheduledServer scheduledServer;

    /**
     * 定时任务注解+cron表达式
     * cron表达式参考地址:https://www.matools.com/cron/?spm=a2c6h.12873639.article-detail.133.6cdb27beXkUYPb
     * 0/5 * * * * ?: 每5秒执行一次
     *
     * @Scheduled除过cron还有三种方式:fixedRate,fixedDelay,initialDelay 修改定时任务的时间时需要重启项目才行
     */
    @Scheduled(cron = "0/5 * * * * ?")
    public void testScheduleTask() {
        System.out.println("基于注解的执行定时任务:" + LocalDateTime.now());
    }

    /**
     * 实现SchedulingConfigurer接口
     * 查询数据库cron
     * @param taskRegistrar
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addTriggerTask(() -> process(), triggerContext -> {
            // 查询cron表达式
            com.chao.demo.bean.Scheduled cron = scheduledServer.getCronById(1);
            if (cron.getCron().isEmpty()) {
                System.out.println("cron is null");
            }
            return new CronTrigger(cron.getCron()).nextExecutionTime(triggerContext);
        });
    }

    private void process() {
        System.out.println("基于接口的定时任务:" + LocalDateTime.now());
    }

    /**
     * 多线程的定时任务
     * https://www.jb51.net/program/285292j3o.htm
     */
}

如何实现扫码登录

扫码分为三个端手机端、PC端、服务端
1、首先,在0网页展示二维码,这个二维码是服务端生成,并有一个唯一id编号。然后浏览器会定时轮询查询这个二维码的状态,未扫码,已扫码,已失效,所以需要将唯一id存储到redis里面,设置过期时间,如果过期就是已失效,那么需要重新生成,再次刷新
2、手机端一定是登录状态,会有保存token,手机扫码二维码,就是解析二维码,将唯一id获取到,手机端点击确定登录,将token和唯一id携带发送给客户端
3、服务端拿到token验证是否有效,如果有效那么会验证唯一id的状态,在redis里面如果唯一di不存在了那么就是失效了,那么唯一id加token就是登录扫码登录成功,其他就是失败
4、返回给pc端,登录成功

支付订单失效

单点登录实现原理

指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的系统。简而言之,多个系统,统一登陆。

什么是倒排索引?

简单来讲:对文档内容分词,对词条创建索引,并记录词条所在文档的信息。查询时先根据词条查询到文档id,而后获取到文档。

倒排索引是一种特殊的索引方法,它主要用于存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。这种索引结构在文档检索系中非常常见,被广泛应用于现代搜索引擎中。

倒排索引有两种不同的形式:

一条记录的水平反向索引,它包含每个引用单词的文档的列表。
一个单词的水平反向索引,它包含每个单词在一个文档中的位置。
后者的形式提供了更多的兼容性(比如短语搜索),但需要更多的时间和空间来创建

在这里插入图片描述

接口文档是如何写的?

1、如何生成响应头,里面就包括appid和appkey和加密的签名 以及其他的参数
2、生成接口,包括接口的请求方式,接口地址,接口描述,接口参数和接口的返回响应
3、公共的返回响应和接口参数需要的数据

我们知道java的一些数据结构在多线程场景下扩容的时候都会有问题,什么数据结构在扩容的时候分别有什么问题

1、ArrayList
ArrayList是基于数组的,其长度是固定的。在多线程环境中,如果多个线程尝试同时扩容ArrayList(例如,通过添加元素导致数组长度不足时),则可能会引发ConcurrentModificationException异常或数据不一致的问题。因为ArrayList没有提供同步机制,所以它不是线程安全的。
2、HashMap
HashMap在扩容时(当元素数量超过当前容量与负载因子的乘积时)会创建一个新的数组,并重新计算所有元素的哈希值和新位置,然后将元素复制到新数组中。这个过程称为rehashing。在多线程环境下,如果两个线程同时检测到需要扩容,并各自开始执行rehashing,则可能会导致数据丢失、元素覆盖或其他不一致的问题。Java 1.8中引入了ConcurrentHashMap来解决这个问题,它使用了分段锁(在Java 8之前)或CAS(Compare-And-Swap)操作(在Java 8及之后)来确保线程安全。
3、HashSet:
HashSet是基于HashMap实现的,因此它在扩容时也会遇到与HashMap相同的问题。多个线程同时修改HashSet可能会导致数据不一致。

什么是分布式锁,在项目中你用到了吗?

1、基于synchronized,在多线程中,一个线程对资源进行修改,其他线程需要等待释放锁,
2、基于redis分布式锁,通过sentx命令
配置分布式锁
B站redis分布式锁讲解

127.0.0.1:6379> SETNX test 'try'
(integer) 1
127.0.0.1:6379> get test
"try"
127.0.0.1:6379> SETNX test 'tryAgain'
(integer) 0
127.0.0.1:6379> get test
"try"
127.0.0.1:6379> del test
"1"

第一次给test赋值,返回表示成功;第二次再次尝试给test赋值,返回0,表示失败,并未再次操作,因为值已经存在

setnx命令:set if not exists,当且仅当 key 不存在时,将 key 的值设为 value。若给定的 key 已经存在,则 SETNX 不做任何动作。
返回1,说明该进程获得锁,将 key 的值设为 value
返回0,说明其他进程已经获得了锁,进程不能进入临界区。

释放锁:del test

3、使用redis中redisson框架实现分布式锁

4、基于zookeeper实现分布式锁

分布式锁的使用场景
1、在分布式中多个节点同时访问共享的资源时,可能出现数据不一致的问题。

分布式锁场景
对统一资源的写操作都需要使用分布式锁,定时任务,抢单、幂等性的场景

注解的实现原理?

蓝绿发布和灰度发布

蓝绿发布:
定义:蓝绿部署是一种将生产环境直接从当前版本(蓝色)切换到新版本(绿色)的策略。这要求同时运行两个完全相同的生产环境,但只有一个对外提供服务。
优点:
快速回滚:如果新版本出现问题,可以立即切换回蓝色环境。
减少停机时间:切换环境的过程通常是自动的,可以实现零停机时间部署。
缺点:
资源需求高:需要两套生产环境,增加了成本。
数据同步挑战:在切换过程中保持两个环境中数据的一致性可能很复杂。

灰度发布:
定义:灰度发布是逐步将新版本推送给一部分用户,而不是一次性对所有用户发布的策略。这允许团队收集反馈并确保新版本的稳定性,从而降低发布新版本的风险。
优点:
降低风险:问题版本影响的用户数量有限。
真实反馈:可以从真实用户环境中收集数据和反馈。
缺点:
复杂的流量控制:需要复杂的路由逻辑来控制哪些用户接收到新版本。
长时间部署:整个过程可能需要较长时间,特别是对于大型用户基础。

如何实现限流操作

限流链接

关于项目

线上是如何解决系统诡异问题的

如果不知道问题出在哪里,那么先不去重启服务,看看是否是硬件先关的问题
1、通过linux查看我的网络问题,那些ip访问呢,一些进程资源,一些io磁盘读写的一些信息
2、多线程并发的问题
3、内存溢出的问题
4、cpu飙高的排查
5、当前一些进程的情况

查询相关的日志

Linux是如何查看进程详情和端口是否被暂用的?

Linux怎么查看一个进程占用的内存情况
如何查看Linux网络情况?如何知道当前哪些端口被占用了

在Linux系统中,可以使用多种命令来查看进程占用的内存情况。以下是两种常用的方法:

使用top命令
打开终端,输入top命令。
在top命令的界面中,按下Shift + M键,这样进程就会按照内存占用率进行排序。此时,你可以看到占用内存最多的进程位于列表的顶部。
使用ps命令
打开终端,输入ps aux --sort -rss命令。
该命令会列出系统中所有进程的详细信息,并按照内存占用量(RSS,Resident Set Size)进行排序。RSS表示进程当前占用的物理内存大小。

Linux查看网络情况
在Linux系统中,可以使用多种命令来查看网络情况。以下是两种常用的方法:

检查网络接口状态
使用ifconfig或ip addr命令可以查看网络接口的状态,确保网络接口正常工作并且没有配置错误。
检查网络连接状态
使用netstat -s命令可以查看网络连接状态,查看是否有大量的重传或丢弃。此外,还可以使用netstat -tuln命令查看所有TCP和UDP的监听端口。

Linux查看当前哪些端口被占用
在Linux系统中,可以使用多种命令来查看当前被占用的端口。以下是四种常用的方法:

使用netstat命令
打开终端,输入netstat -tuln命令。该命令将显示所有TCP和UDP的监听端口。
使用lsof命令
如果要查看特定端口被哪个进程占用,可以使用lsof -i:端口号命令。其中,端口号是要查看的具体端口号。
使用ss命令
ss -tuln命令也可以显示所有TCP和UDP的监听端口,与netstat命令类似。
使用nmap命令
nmap -p端口号 IP地址命令可以用来扫描特定主机和端口的状态。其中,端口号是要查看的具体端口号,IP地址是要扫描的主机IP地址。

jdk8和jdk17的新特性

jdk8:
lambda表达式、新的日期、Optional 、Stream API:
Stream详情Stream视频学习
jdk17:
switch表达式的模式匹配、文本块、增强的伪随机数生成器、密封性、性能提升和安全性增强、工具等

Java工具

接口测试工具:postman、apifox
接口性能测试或者抗压工具:jmaster
jmaster怎么使用:https://blog.csdn.net/weixin_45014379/article/details/124190381
jvm可视化工具:jconsloe

Stream流的使用

Stream流的实现原理

设计原理
Stream流的实现

1、stream通过StreamSupport创建一个流
2、创建ReferenPipeline.head(链表头)
3、将Spliterator(分离器、迭代器)放入链表头中,是stream底层实现,
4、有一个中间操作比如filter、map、limit、skip、sorted、distinct,返回一个新的流,而不是一个结果
5、终端操作也就是操作流的最后一步,比如foreach、max、min、count,产生一个结果

中间操作又包括无状态和有状态,
终端操作又包括非短路操作和短路操作

Stream上的所有操作分为两类:中间操作和结束操作,中间操作只是一种标记,只有结束操作才会触发实际计算。中间操作又可以分为无状态的(Stateless)和有状态的(Stateful),无状态中间操作是指元素的处理不受前面元素的影响,而有状态的中间操作必须等到所有元素处理之后才知道最终结果。
比如排序是有状态操作,在读取所有元素之前并不能确定排序结果;结束操作又可以分为短路操作和非短路操作,短路操作是指不用处理全部元素就可以返回结果,比如找到第一个满足条件的元素。之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。

3、然后就是链接头节点
4、判断是否是短路方法,做一个递归

关于专业词汇

ACID:一致性,原子性,隔离性,持久性
CAP: 一致性、可用性、容错性
aop:
ioc:
di:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值