【2023面试题大全,都是常问面试题】
JAVA基础
面向对象和面向过程的区别
面向过程:基于步骤的编程方式,用函数把这些步骤一步一步地实现,然后在使用的时候一一调用则可 面向对象:基于对象的编程方式,通过定义类来描述对象的属性和行为,面向对象具有封装继承多态三种特性
封装继承多态
封装:将代码封装起来,只暴露对外的接口,提高了安全性和代码复用 继承:继承指子类对父类的继承,子类可以拥有父类的方法和属性,也可以自定义方法和属性 多态:类和类之间,父类引用指向子类对象,同一种操作作用于不同的对象在不同情况下可以有不同的行为。
Integer的valueOf方法
public class Main {public static void main(String[] args) {Integer i1 = 100;Integer i2 = 100;Integer i3 = 200;Integer i4 = 200;// trueSystem.out.println(i1==i2);// falseSystem.out.println(i3==i4);} }
Integer i = 100
底层是调用了 valueOf(),可以看出如果数值在[-128,127]之间,便会返回已经存在的对象的应用,否则新建一个对象
public static Integer valueOf(int i) {if(i >= -128 && i <= IntegerCache.high)return IntegerCache.cache[i + 128];elsereturn new Integer(i); }
重写和重载
重写:
-
发生在父类与子类之间
-
方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
-
访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
-
重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
重载:
-
重载Overload是一个类中多态性的一种表现
-
重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)
-
重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准
JDK8新特性
-
接口默认方法:java8中,接口可以添加 default 修饰的方法,并且可以有方法实现。
-
lambda表达式:用更简洁的方式编写匿名函数,可以用于遍历、排序、过滤等操作
-
Stream流:用于处理集合数据,提高代码可读性和简洁性
-
Optional类:用于处理可能为空的值,避免空指针
接口和抽象类
-
抽象类可以有构造器,接口不行
-
抽象类可以有抽象方法和具体方法,接口只能有方法定义
-
抽象类中的成员可以是private, default, protected, public,接口只能是public,除了默认方法
-
抽象类可以定义成员变量,接口中定义的成员变量实际上都是常量,接口中的成员变量默认被 static final 修饰。
-
有抽象方法的类必须是抽象类,抽象类未必要有抽象方法;
-
单继承多实现
使用场景:
-
抽象类:有具体概念的,建议用抽象类,比如animal
-
接口:共同事物之间具有的共同特征,比如flyable
数组下标为甚从0开始
数组有个寻址公式:数组首地址+数组下标*数组中元素类型的大小。如果从1开始,对于cpu来说多了一个减法指令。
ArrayList底层的实现原理
-
底层用数组实现
-
初始容量为0,当第一次添加元素的时候会初始化容量为10
-
后续进行扩容的时候都是原来的1.5倍,每次扩容都需要拷贝数组。
用Arrays.asList转List后,如果修改了数组内容,list受影响吗?List用toArray转数组后,如果修改了List内容,数组受影响吗
Arrays.asList返回的ArrayList不是真正的ArrayList,而是Arrays的一个内部类,他们都是指向同一个内存地址,所以会有影响
list.toArray是通过数组拷贝,所以不会影响
CopyOnWriteArrayList的底层实现
-
内部也是通过数组实现,新添加元素时会复制出新数组,在新数组上写
-
写操作结束时会把原数组指向新数组,并且写操作会通过ReentrantLock加锁
-
CopyOnWriteArrayList允许在写操作时进行读操作,读的时候直接在旧数组上读,提升了读的性能,适合读多写少且有线程安全需求的场景
Hashcode的作用
java的集合有两类,一类是List,还有一类是Set。前者有序可重复,后者无序不重复。当我们在set中插入的时候怎么判断是否已经存在该元素呢,可以通过equals方法。但是如果元素太多,用这样的方法就会比较慢。 hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
hashcode和equals的关系
alibaba开发手册中有提到重写了equals方法也要重写hashcode方法
-
如果两个对象的hashcode不同,则他们一定不同
-
如果两个对象的hashcode相同,他们也不一定是同一个对象
-
如果两个对象相等,则他们的hashcode一定相同
hashmap 1.7和1.8的区别
-
1.7采用数组+链表,1.8采用数组+链表+红黑树,加红黑树是为了提高添加和查询的速度
-
1.7中链表插入使用的是头插法,1.8是尾插法,采用尾插法是为了避免多线程情况下同时竞争往链表头部插入节点造成死循环。
-
1.7中的hash算法更复杂,包含各种位运算和异或运算,1.8则进行了简化
map接口
HashMap: 存储键值对,是线程不安全的
在jkd1.7之前采用数组+链表,当存入一个新元素时,会先通过对key进行hash运算得到一个hash值也就是即将插入的位置(即桶号),随后判断这个位置(hash值)是否有元素,若没有则直接添加,若有则产生了hash冲突,采用拉链法解决即生成一个链表并插入链表,在插入链表前,会先遍历链表中已经存在的节点并对它们的key进行 equals()比较,判断新加入的节点是否和已经存在的节点的 key 相等,若相等则覆盖,不等则插入
在jdk1.8开始采用数组+链表+红黑树,加红黑树是为了提高添加和查询的速度,当链表长度>8时,会转为红黑树,将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树,只有当数组容量达到 64 且链表长度大于 8 时,才会将链表转换为红黑树。
负载因子是指元素的数量与数组的数量之比,也就是说当元素的数量>负载因子*数组的数量时,就会对数组进行扩容,负载因子默认是0.75
TreeMap:有序 Map
底层实现为红黑树,根据键的顺序进行排序,也可以自定义排序
LinkedHashMap:基于哈希表和双向表实现的有序 Map。
它维护了一个按照插入顺序排序的双向链表,因此能够保证遍历 Map 时的顺序与插入时的顺序相同。
ConcurrentHashMap:线程安全的hashmap
在1.7之前,底层由多个segment数组组成,每个segment数组包含一个hashentry数组,用于存储键值对。在并发时每个 Segment维护着自己的锁,这样不同的线程可以同时访问不同的 Segment,从而提高并发访问的效率。Segment 的数量默认为 16
在1.8开始,底层实现由数组+链表+红黑树,并且不再使用分段锁,而是cas和synchronized,每次只锁住当前链表或红黑树的根节点,保证并发效率。
具体如下:
-
如果当前存储桶没有元素,则使用CAS操作将元素插入桶中;
-
如果当前存储桶已经存在元素,则使用synchronized锁住对应的桶,在锁的保护下,只能有一个线程能操作链表/红黑树,就不会有并发的问题,效率也得以提升。
CAS(Compare And Swap)是一种乐观锁技术,它认为多个线程不会共享同一份数据,所以在实行cas操作时,首先会读取该数据的当前值,并预期当前值不会被其他线程修改。如果后面读取到的数据与当前值相同,则表示没有被其他线程修改,则可以更新该数据的值。若不同则表示被其他线程修改,则返回失败。cas只能保证单个共享变量的原子性,若要保证多个共享变量(多个键值对)的原子性需要加锁
hashtable: 线程安全,效率低,不推荐使用
在每个方法都使用了synchronized来保证线程安全,性能差
hashmap的扩容机制原理
1.7版本
-
先生成新数组,容量是原来的两倍
-
遍历老数组中每个位置上的链表中的元素
-
用链表中的元素的key与新数组的长度进行计算,得到元素存放在新数组中的下标位置
-
将元素添加到新数组中
1.8版本
-
先生成新数组,容量是原来的两倍
-
遍历老数组中的每个位置上的链表或红黑树
-
如果是链表,则遍历链表中的元素,用元素的key与新数组的长度计算得出存放在新数组中的下标位置
-
如果是红黑树,则遍历红黑树,先计算出每个元素应该存放在新数组的下标位置
-
然后统计新数组中每个下标位置的元素个数
-
如果该位置下的元素数量超过了8,则生成一个红黑树存储元素
-
如果没有超过8,则生成链表存储元素
HashMap寻址算法
-
计算对象的hashcode()
-
再进行hash()进行二次哈希,二次哈希就是将得到的hashcode右移16位再异或运算,让哈希分布更均匀
-
最后通过(capacity-1)&hashcode得到数组下标位置
为什么hashmap的数组长度是2的次幂
-
可以使用位与运算替代取模运算,提高运算效率,例如:当长度为8时,它的二进制表示为1000,长度为16时,二进制表示为10000,二进制位数最多,可让散列均匀地分布在每个数组下标中
HashMap1.7多线程死循环的问题
jdk1.7链表采用头插法,假设两个线程,链表是A->B。
-
线程1准备扩容时,线程2进来了
-
线程2这时也准备扩容,然后因为是头插法,链表顺序会颠倒,从A->B变成B->A
-
轮到线程1,也会颠倒,变成A->B,但是由于线程2之前的操作,导致B的next指向了A,最终导致B->A->B,产生循环
-
jdk8采用尾插法避免这种问题
ConcurrentHashMap的扩容原理
1.7
-
基于分段锁
-
每个segment存放一个entry数组(存储键值对)
-
每个entry数组都可以进行扩容,和1.7的hashmap类似
-
先生成新的数组,然后转移元素到新数组中
-
扩容的判断也是各个segment内部单独判断是否超过阈值
1.8
-
不基于分段segment
-
当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容那么该线程调用hepltransfer()一起进行扩容
-
如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中, 然后判断是否超过闻值,超过了则进行扩容
-
ConcurrentHashMap是支持多个线程同时扩容的
-
扩容之前也先生成一个新的数组
-
在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移工作
hashmap和hashtable区别
-
父类不同:HashMap继承自AbstractMap类,HashTable继承自Dictionary类。不过都实现了map, Cloneable, Serializable这三个接口
-
对null的支持不同:HashMap只支持一个为null的key,为null的value可以有多个。HashTable都不支持。
-
线程安全不同
-
hashtable和hashmap都使用iterator迭代器,hashtable也支持Enumeration
-
hashtable数组默认大小11,增长方式是2倍+1,hashmap默认16,增长原来的2倍
Java的四种引用:强弱软虚
-
强引用:内存不足也不会被回收
-
软引用:内存不足就会被回收
-
弱引用:被jvm垃圾回收期发现就会被回收
-
虚引用:比较特殊的引用类型,为了能够在对象被回收时收到系统的通知
cookie session
-
cookie存放客户端,session服务端
-
cookie保存字符串,session保存对象
-
cookie可以直接从浏览器获取,不安全,session存放在服务器,相对安全
-
cookie大小在4kb,session一般没有限制
-
cookie支持跨域,session不支持
localStorage sessionStorage
-
localStorage永久存储,除非手动清除;sessionStorage临时存储,关闭浏览器就清除
-
localStorage和sessionStorage都只能存储字符串类型,如果存储其他类型,编译器会自动toString()转成字符串来存储
内部类
-
静态内部类:定义在类内部的静态类
-
静态内部类可以访问外部类的所有静态变量和方法,即时是private也可以
-
其他类使用静态内部类可以这样
Outer.Inner inner = new Outer.Inner();
-
-
成员内部类:定义在类内部的非静态类,不能定义静态方法和变量(final修饰的除外),因为类初始化时是先初始化静态成员,如果成员内部类允许定义静态变量,那么初始化时的顺序是有歧义的(外部类初始化时会先初始化静态成员,而此时内部类尚未初始化,导致内部类中的静态变量无法得到有效的初始化。)。
-
局部内部类:在方法中的类,如果一个类只在某个方法中使用,则可以考虑
-
匿名内部类:要继承一个父类或实现一个接口(因为匿名内部类需要隐式地继承外部类或实现某个接口才能进行方法的覆盖或实现),使用new来生成一个对象的引用来重写父类的方法,适用于一次性,特定需求的场景,可以简化代码并提高可读性。
泛型
泛型意味着编写的代码可以被不同类型的对象所重用,使我们不必因为添加元素类型的不同而定义不同类型的集合
泛型擦除
编译器在编译时擦除了所有类型相关的信息,在运行时不存在任何类型相关的信息,也就是说:泛型只存在于编译阶段,而不存在于运行阶段。使用类型擦除的主要目的是确保能和 JDK1.5 之前的代码进行兼容,并且实现简单,几乎不需要更改 JVM代码
泛型的extend和super
泛型中经常会有extend和super来修饰
<? extend T>表示包括T在内的所有T的子类
<? super T>表示包括T在内的所有T的父类
如果在声明自定义集合类的时候定义了泛型,则以定义的泛型为主,如果没有定义泛型,则以自定义集合类上的泛型为主。
java中有哪些类加载器
-
AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件
-
ExtClassLoader是AppClassLoader的父类加载器,负责加载 %JAVA_HOME%/lib/ext文件夹下的jar包和class类
-
BootStrapClassLoader是ExtClassLoader的父类加载器,负责加载 %JAVA_HOME%/lib下的jar宝和class文件
类加载器双亲委派模型
AppClassLoader的父加载器是ExtClassLoader的父加载器是BootstrapClassLoader
JVM在加载一个类时,会调用AppClassLoader的方法来加载类,不过会先使ExtClassLoader的方法来加载类。同样ExtClassLoader方法中会先调用BootstrapClassLoader来加载类,如里BootstrapClassLoader加载到就直接。如里BootstrapClassLoader没有加载到,ExtClassLoader就会自己尝试加载该类,如果没有加载到,那么则会由AppClassLoader来加载这个类。
所以,双亲委派指得是,JVM在加载类时,会委派给ExtClassLoader和BootstrapClassLoader进行加载,如果没加载到才由自己进行加载.
为什么采用双亲委派
-
为了安全,保证类库API不会被修改,只有经过验证的类才能被加载
-
通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。
浅拷贝和深拷贝
-
浅拷贝:如果是基本类型,则拷贝基本类型的值,如果是应用类型,拷贝的就是地址,即两个对象指向同一个地址,修改其中一个对象的值,另一个对象也会受到影响
-
深拷贝:深拷贝是将一个对象从内存中完整的拷贝一份,在堆空间内开辟一个新的区域存放新对象,两个对象是独立互不影响的
static的用法
静态变量、方法:被static修饰的变量/方法只属于类,不属于某个对象实例,它在类加载时初始化且仅初始化一次,可以直接通过类名.访问。,静态方法只能访问类的静态方法和静态变量,如果要访问非静态方法/变量,只能通过对象实例来访问。
静态代码块:被static修饰的代码块只在类加载时初始化一次,且优先于构造函数。多用于初始化操作
静态内部类:定义在类中的静态内部类,只能访问外部类的静态方法和变量,如果要访问非静态方法/变量,只能通过对象实例来访问。
静态导包:可以直接调用方法名而不需要类名
import static java.lang.Math.*; public class Test{public static void main(String[] args){//System.out.println(Math.sin(20));传统做法System.out.println(sin(20));} }
final有哪些用法
-
修饰类:被final修饰的类不能被被继承
-
修饰方法:不能被重写,因为不能被重写,所以在编译时期就可以确定调用哪个方法,因此jvm在运行时会将该方法的字节码嵌入到调用该方法的位置,而不是函数调用的形式,这个过程叫做内联,内联可以避免函数调用的开销,提高程序运行效率
-
修饰变量:被final修饰的变量称作常量,不能被修改,如果修饰的是引用(如数组),则引用不可变,引用指向的内容可变。同时常量会在编译阶段存入常量池中
final int x = 1; x = 2; // 这会报错,因为x的值不能被改变final int[] arr = {1, 2, 3}; arr = new int[]{4, 5, 6}; // 这会报错,因为arr引用不能指向其他对象 arr[0] = 4; // 这是可以的,因为arr指向的对象的内容可以改变
常量池
编译时常量池: 编译阶段生成的常量池用于存储字面量和符号引用
运行时常量池:运行时常量池在类加载完毕后,将每个编译时常量池中的符号引用值转存过来,这样每个class都有个运行时常量池。类被解析时,将符号引用替换成直接直接应用。这样做的目的是为了在运行时能够快速定位到所需的对象,从而加快程序运行速度。
tip: 字面量可以理解为实际值:int i = 10; char a = 'a'; 这里的10和a都是字面量。符号引用是编译时期生成的、以字符串形式表示的符号名称,它并不直接指向内存中的地址
try catch finally,try里有return,finally还执行么?
执行,finally在try里的return前执行。
-
不管有无异常,finally都会执行
-
try和catch都有return,finally也会执行
-
finally是在try中的return后面的表达式运算后才执行的(就是说表达式运算后但并没有返回,而是先保存要返回的值,不管finally如何执行,返回的值都不会改变,仍然是之前保存的值)
-
finally中最好不要包含return,当try和finally都有return时,会忽略try的return,执行finally的return。
Exception与Error
他们都继承自顶级父类Throwable
Throwable可分为三种结构:被检查异常(CheckedException),运行时异常(RuntimeException),错误(Error)。
-
运行时异常: RuntimeException及其子类都被称为运行时异常。java编译器不会检查它,就是说当程序出现运行时异常时,就算没有抛出、捕获,还是会编译通过,常见的运行时异常:
-
ClassCastException
-
IndexOutOfBoundsException
-
NullPointerException
-
-
被检查异常:Exception类本身,以及Exception类下的非运行时异常及其子类的都属于被检查异常,这类异常会被java编译器检查,若不捕获或抛出则无法通过编译,常见的被检查异常:
-
IOException
-
FileNotFoundException
-
SQLException
-
-
错误:Error类及其子类,和运行时异常一样,编译器不会对Error进行检查。程序本身无法修复这种,出现错误会导致程序终止运行,常见如下:
-
VirtualMachineError
-
OutOfMemoryError
-
线程、进程、程序
进程: 进程是程序的一次执行过程,是系统运行程序的基本单位,一个进程包含一个或多个线程,一个线程同时只能被一个进程拥有,不同的进程使用不同的内存空间
线程: 与进程相似,线程是比进程更小的执行单位,与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源。因此系统在产生一个线程或多个线程切换时负担要比进程小得多。
程序: 程序是含有指令和数据的文件,简单说程序就是静态的代码。
并发和并行
并发:看起来同时进行,利用时间片轮转实现多个任务的交替执行
并行:真正同时进行,依靠多个执行单元实现多个任务同时进行
线程有哪些基本状态
-
NEW: 初始状态
-
Runnable: 运行状态(就绪和运行统称为运行中)
-
Blocked: 阻塞状态,调用同步方法时没有获取到锁会进入该状态
-
Waiting: 等待状态,通过Object.wait()等待,Object.notify()唤醒
-
Time_Waiting: 超市等待,通过Object.wait(long)或Thread.sleep(long)置于该状态,到时见会自动回到Runnable状态
-
Terminated: 终止状态,线程执行完毕
线程池有哪些状态
-
RUNNING:会接受新任务且会处理队列中的任务
-
SHUTDOWN:不会接收新任务,但会执行队列中的任务,任务处理完会中断所有线程
-
STOP:不会接收新任务也不会执行队列中的任务,会立即中断所有线程
-
TIDYING:所有线程停止后,状态会变为TIDYING,一但达到此状态,就会调用线程池的terminated()
-
TERMINATED:terminated()执行后会变为此状态
Java 序列化中如果有些字段不想进行序列化,怎么办?
transient,被transient修饰的变量,序列化时不会被保存到序列化流中,反序列化时会被设为默认值。
java中IO流
-
按照流的流向分:输入流和输出流
-
按照操作单元分:字节流和字符流
-
按照流的角色划分:节点流和处理流
java中io流有很多个类,但基本都是从这四个抽象类派生出来的:
-
InputStream
-
OutputStream
-
Reader
-
Writer
反射原理
在运行时,对于任意一个类,都可以知道他的方法和属性,对于任意一个对象,都可以调用他的任意一个方法,这种动态获取类信息以及动态调用对象方法的功能就是反射机制
反射的优缺点
优点:
-
能够动态获取类的信息,动态调用对象方法
-
与动态编译结合
缺点:
-
性能低,需要解析字节码
解决方法:
-
若是多次创建一个实例时,有缓存会快很多
-
通过setAccessible(true)来关闭Jdk的安全检查
-
-
相对不安全,破坏了封装线(因为通过反射能够获取私有方法和属性)
IO和NIO
-
IO是面向流的,NIO是面向通道缓冲区的。IO面向流意味着每次从流中读取一个或多个字节直至读取完,而NIO意味着数据可以从通道读取到缓冲区,然后从缓冲区中处理。同样,数据也可以写入缓冲区,然后再通过通道写出
-
IO是阻塞的,NIO既支持非阻塞也支持多路复用(多路复用允许单个线程同时监听多个socket)
-
IO只能单向,NIO可以双向
多路复用: 假设开发一个聊天系统,需要同时管理多个客户端操作,传统io的话需要为每个客户端维护一个线程进行读写操作,客户端数量多时,会造成资源浪费,性能下降。使用 多路复用技术,可以使用一个选择器来管理所有客户端连接,每个客户端连接对应一个通道,这些通道都注册在选择器上。选择器会不断轮询这些通道,检查是否有数据需要读写,这样就只需要用一个线程来管理多个客户端连接。
IO事件的模型
这三种都是NIO的实现方式,都属于多路复用技术
select模型:使用数组来存储Socket连接文件描述符,容量固定,需要通过轮询来判断是否发生了IO事件
poll模型:使用链表来存储Socket连接文件描述符,容量不固定,需要通过轮询来判断是否发生了IO事件
epoll模型:epoll跟poll是完全不同的,epoll是一种事件通知模型,当发生了IO事件时才进行IO操作,不需要像poll那样去主动轮询
java程序是如何执行的
-
先把java代码编译成字节码文件(.java->.class)
-
再将class文件放置到jvm,jvm使用类加载器装载class文件
-
装载完毕后进行字节码校验
-
校验通过后jvm会把字节码文件翻译成机器码由操作系统执行。
设计模式
工厂模式
-
简单工厂:
-
所有的产品共有一个工厂,如果新增产品,则需要修改代码,违反开闭原则
-
BeanFactory就是采用这种
-
-
工厂方法模式:
-
给每个产品都提供一个工厂,不同工厂负责对应的产品生产,遵循开闭原则
-
项目中用的多
-
-
抽象工厂方法模式
-
工厂方法针对的是一个产品等级结构,而抽象工厂方法模式则面向多个产品等级结构。
-
如果有多个种类的产品需要生产时,优先建议这种
-
当某个品牌要增加新的产品时,所有工厂类都需要修改
-
策略模式
主要角色分为:
-
抽象策略类:这是一个抽象角色,通常是个接口或抽象类,给出所有的具体策略类所需的方法
-
具体策略类:实现了抽象策略类中的方法,提供具体的实现
-
环境类:持有策略的引用,给客户端调用
比如旅游出行:
-
抽象策略类 :Strategy接口中定义了traval方法
-
具体策略类:汽车,飞机,高铁等实现了Strategy接口,并重写了traval方法
-
环境类:TravalContext:则提供了选择哪一项出行方式的方法。
JVM
JDK和JRE和JVM区别
-
jdk:java标准开发包,包含jre,如果要开发java程序,就需要安装jdk
-
jre:java运行环境,包含jvm,如果只是要运行java字节码文件,只安装jre就可以了
-
jvm:java虚拟机
编写java代码也可以在txt文件写,如果要编译需要通过jdk中的javac编译器,编译后的字节码文件需要通过jvm来执行并翻译成各个平台的机器码。
JVM内存模型
线程独占:栈、本地方法栈、程序计数器
线程共享:堆、方法区
-
栈:又称方法栈,是线程私有的,线程执行方法时会创建一个栈帧,用来存储执行方法的信息。调用方法时执行入栈,方法返回时执行出栈。
-
本地方法栈:与栈类似,也是用来存储执行方法的信息。执行java方法时使用栈,执行Native方法(其他语言编写的方法)时使用本地方法栈。
-
程序计数器:记录当前线程正在执行的字节码指令的地址,每个线程都有独立的计数器,并且只为java方法服务,如果是native方法,程序计数器的值为undifined。
-
堆:堆被线程共享,存放对象的实例,当对没有可用空间时会报OOM异常。堆是垃圾回收的主要区域,根据对象的存活周期不同,JVM把对象进行分代管理,由垃圾回收期进行垃圾回收
-
方法区:被线程共享,主要存储类的信息,静态变量,常量,即时编译器优化后的代码等数据,jvm启动时创建,关闭时释放
方法内的局部变量是否线程安全
-
如果方法内局部变量没有逃离方法的作用范围,也就是没有形参也没有返回,则线程安全
-
如果局部变量引用了对象,并逃离了,则线程不安全
分代回收
-
新生代:堆内存中的一块区域,用于存储新创建的对象,新生代通常只占用堆内存的一小部分。当新生代中的对象经过数次垃圾回收后仍然存活,就会移动到老年代中。
-
老年代:也是堆内存中的一块区域,用于存储长时间存活的对象。占用堆内存的大部分空间。
-
持久代(jdk8被元数据区替代):也称为方法区,用于存储已被虚拟机加载的类信息、常量、静态变量以及即时编译器优化后的代码等数据。
堆和栈的区别
-
功能不同:栈用于存储局部变量、对象的引用、方法调用;堆存储java的对象。
-
共享性不同:栈线程私有,堆线程共享
-
异常错误不同:java.lang.StackOverFlowError和java.lang.OutOfMemoryError
-
空间大小不同:栈的空间远小于堆
垃圾回收的过程
-
Minor GC:也称为Young GC,只对新生代进行垃圾回收,由于java对象大多存活时间不长,所以Minor GC发生频繁,回收速度也快。
-
Full GC: 也称为 Major GC ,全局性的垃圾回收方式,通常会在老年代空间不足时触发,在执行Full GC时会暂停所有应用线程,所以会导致较长时间的停顿。
垃圾回收器的不同类型
-
针对新生代:
-
PS GC:Parallel Scavenge GC,基于分代收集算法的垃圾回收器。在新生代使用复制算法,老年代采用标记-清除或标记-压缩算法,是Server模式下默认的垃圾回收器。
-
Serial GC:单线程收集器,垃圾回收时必须暂停其他线程
-
NewPar GC:是基于Serial收集器的多线程版本,是Server模式下首选的垃圾回收器。
-
-
针对老年代:
-
CMS GC:Concurrent Mark Sweep Garbage Collector,分布标记清除垃圾回收期,能够在线程运行的同时进行垃圾回收,减少停顿时间。
-
-
针对整个堆空间:
-
G1 GC:在jdk9成为默认垃圾回收器,G1 GC在采用了多线程并行的同时,将堆内存分成了多个区域,每个区域都可以充当eden,survivor,old,humongous,其中humongous专为大对象准备,通过智能地选择性回收来保证效率
-
ZGC:一种面向大堆,低延迟的垃圾回收期,它将堆空间分成了多个碎片来管理,能够在非常段的停顿时间处理大量垃圾,避免传统GC的长时间停顿。
tips: G1 GC和ZGC不需要和其他垃圾收集器搭配使用
-
垃圾回收算法
-
标记清除算法:首先标记出所有需要回收的对象,标记完成后统一回收掉被标记的对象。
-
标记压缩算法:首先标记出所有需要回收的对象,然后让所有存活的对象往一端移动,然后清理端边界以外的对象。
-
复制算法:将可用内存按容量分成大小相等的两块,每次只是用其中的一块。当这块空间用完后,就将还存活的对象复制到另一块,然后再把使用过的内存一次清理掉
-
分代收集算法:把堆分成新生代和老年代。新生代中,每次垃圾回收都有大量对象死亡,只有少量存活,所以新生代一般采用 复制算法 ;老年代中对象存活率高,又没有额外的空间担保,所以采用 清除-标记 或 清除-压缩 。
什么时候会触发FullGC
-
调用System.gc()
-
老年代空间不足
老年代只有当新生代对象转入或创建过大的对象、数组时才会出现不足的现象,当执行FullGC后空间仍然不足便会抛出OOM异常。
解决: 尽量使对象在Minor GC阶段(针对新生代的垃圾回收动作)被回收,尽量减少老年代对象的数量,以及不要创建过大的对象和数组。
-
持久代空间满
当系统中要加载的类、调用的方法过多时,持久带可能会满,执行 FULL GC 时仍然回收不了,则会抛出java.lang.OutOfMemoryError: PermGen space
解决: 增大持久带空间或配置 CMS GC (与Full GC相比,CMS GC具有更高的回收效率和更短的停顿时间,因此可以更好地解决PermGen空间不足的问题。)。
什么是JVM,为什么称为平台无关性
-
JVM是一个可以执行java字节码的虚拟机系统。
-
java源文件被编译成字节码文件,JVM负责将字节码转换成特定平台的机器语言,从而实现了平台无关性。
什么是直接内存
-
直接内存不属于jvm的内存结构,不由jvm管理,由操作系统管理的内存区域
-
一般用于NIO操作,用于数据缓冲区,读写性能高,不受jvm垃圾回收控制
对象分配规则
-
对象优先分配在Eden区,当Eden区空间不足时会执行 Minor GC ,幸存的对象会被分配到survivor。
-
大对象(需要大量连续内存空间的对象)直接放入老年代。
-
在survivor长期幸存的对象放入老年代。(虚拟机为每个对象定义了一个年龄计数器,如果Eden区的对象经过了一次Minor GC,对象就会进入survivor区,往后每次GC时对象的计数器都会+1,达到一定阈值则会进入老年代。
-
空间分配担保:新生代在发生 Minor GC 前,会先检查老年代最大可用的连续空间是否大于新生代中所有对象之和(或历次晋升老年代的对象的平均大小),若不成立,则进行Full GC;若成立,则会进行Minor GC,不过这次Minor GC是有风险的,可能出现大量对象在 Minor GC 后仍然存活
解决方法:
-
调整堆内存的大小和新老生代
-
选择更合适的垃圾回收器
-
优化应用程序代码
-
java对象创建过程
-
jvm在执行new指令时,会先去检查常量池中是否已经定义了这个对象的符号引用,如果没有则进行类加载过程(解析),如果有则直接通过符号引用定位到类的加载信息。
-
类加载完成后,需要为新对象分配空间,有多种方法,一种是“指针碰撞”,一种是“空闲列表”,不过最常用的是 "TLAB"(本地线程缓冲分配)。
-
分配空间后,jvm会将对象的内存空间初始化为0
-
设置对象头,包括对象的类型指针、GC 相关的标志位等
jvm对象分配空间的方法:
-
指针碰撞:用于java堆中内存是绝对规整的情况
-
空闲列表:用于并不是规整的情况
-
TLAB:TLAB技术会在堆中预先分配一小块内存,称为本地线程分配缓冲区。当线程需要分配内存时,会先在自己的缓冲区中分配,这样每个线程都可以在自己的缓冲区快速分配内存,而不需要与其他线程竞争。
java对象的结构
包括三部分:
-
对象头:由两部分组成,第一部分存储对象自身的运行时数据,第二部分是指针类型,指向对象代表的是哪个类。
-
实例数据:存储对象真正的有效信息。
-
对齐填充:jvm要求对象起始地址必须是8字节的整数倍
如何判断对象可以被回收
-
引用计数:每个对象有一个引用计数属性,新增一个引用时计数+1,释放时计数-1,为0时表示可以回收,此方法简单但无法解决对象相互循环引用的问题。
-
可达性分析:以GC Roots为起点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则该对象可以被回收。
哪些对象可以作为GC Root
-
栈中引用的对象,比如new的一个对象
-
被静态属性引用的对象
-
常量引用的对象
jvm监控分析命令
-
jps(JVM Process Status Tool):用于查看当前运行的java进程及其信息,,可以用来获取进程id。
-
jinfo:用于查看或修改当前运行的java进程的参数和属性,可以用来动态调整部分参数
-
jstack:用于生成当前java进程的所有线程快照,可以用来分析线程状态和死锁问题
-
jstat:用于监视jvm运行时状态信息,它可以显示jvm进程中的内存,垃圾回收等信息。
jstat -gcutil pid 毫秒值
-
jmap:查看各个区域的的使用情况
jvm调优在哪设置参数
-
war包部署的话在tomcat的配置文件
catalina.sh
中配置 -
jar包部署的话在启动时设置参数,比如 java -Xms512m -Xmx1024m -jar xxx.jar
如何jvm调优
对于还在正常运行的系统
-
可以使用jmap查看各个区域的使用情况
-
通过jstack查看线程的运行情况
-
通过jstat查看垃圾回收的情况,特别是FullGC,如果FullGC频繁就需要jvm调优
-
通过各个命令的结果,或者jconsole或jvisualvm工具来分析
-
首先,如果频繁发生fullGC又没有出现内存溢出,说明FullGC回收了很多对象,出现这种情况可能是因为对象太大所以直接放入了老年代,所以这些对象最好能在minorGC就回收掉,避免这些对象进入老年代,可以修改年轻代大小或者调整年轻代和老年代的比例
-
还可以通过jstack找到占用cpu最多的线程,定位到具体的方法,优化这个方法
对于已经发生OOM
-
通过设置参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof
,使系统发生OOM时生成dump文件 -
或者通过命令
jmap -dump:format=b,file=heap.hprof pid
获取dump文件 -
可以利用jvisualvm来分析dump文件
-
根据dump文件找到异常的实例对象和异常的线程,定位到具体代码
第一种方法需要OOM才会生成dump文件,第二种则是可以在运行时手动获取,不需要等到异常
如何排查cpu飚高
-
使用top命令查看进程占用cpu的情况
-
通过ps命令查看占用cpu最高的进程
-
使用jstack查看进程中是哪些线程有问题
调优工具
jdk自带的:
-
jconsole:是jdk自带的图形化性能监控工具,能够用于Jvm中内存、线程和类等的监控
-
jvisualvm:jconsole的升级版,整合了jstack,jmap,jinfo等调试工具的功能
jvm性能调优
-
-Xmx:堆内存最大限制,通常可以设置为系统可用内存的70%
-
-Xms: 堆内存初始大小,通常这个参数的值与 -Xmx 一致
-
-Xmn: 新生代大小,可以设置为整个堆内存的1/4到1/3
-
-XX:NewSize : 新生代初始大小
-
-XX:NewRatio :新生代老生代的占比
-
-XX:SurvivorRatio :Eden和Survivor的占比
-
-Xss: 栈的大小,一般设置在256k到512k
-
-XX:PermSize: 永久代初始大小
-
-XX:MaxPermSize: 永久代最大限制
-
设定垃圾回收器:
-
年轻代用 -XX:+UseParNewGC
-
老年代用 -XX:+UseConcMarkSweepGC
-
简述垃圾回收机制
程序员不需要显式的去释放对象,而是由jvm自行执行,在jvm中,有一个垃圾回收线程,是低优先级的,在正常情况下不会被执行,只有当虚拟机空闲或堆内存不足时才会触发执行。
你有没有遇到过OutOfMemory问题?你是怎么来处理这个问 题的?
创建了过大的数组,导致OOM异常;
根据实际需要创建适当长度的数组,或者通过 -Xmx 调整堆空间的大小。
OOM的常见原因
-
一次性从数据库取太多数据,一般一次取10w条就可能导致OOM
-
递归循环调用方法
-
启动参数设置的堆内存太小
-
创建过多的对象,过大的数组
JDK 1.8之后Perm Space有哪些变动?
jdk8开始元数据区取代了持久代,同时字符串常量池移到了堆内存中(8之前放在方法区中)。
new String("abc")到底创建了几个对象
首先因为new这个关键字,会在堆内存中实例化一个String对象,然后在String的构造函数传了一个"abc"字符串,因为String是final修饰的,所以"abc"是一个字符串常量,因此jvm会去字符串常量池中查找是否存在“abc”的对象引用,如果没有,就会在堆内存中创建一个"abc"的字符串常量,并且将引用保存在字符串常量池中,如果后续再有字面量"abc"的定义的话就直接从常量池中取对应的引用。
所以本题的答案应该是:
-
如果常量池中没有"abc"的引用,则2个
-
如果有则1个
MetaSpace⼤⼩默认是无限的么?
大小默认没有限制,一般根据系统的大小,jvm会动态改变此值。
可以指定元数据区的大小:
-
-XX:MetaspaceSize:分配给元数据区的初始大小
-
-XX:MaxMetaspaceSize:分配给元数据区的最大值,超过此值会触发Full GC。
线程
JMM(java内存模型)
-
JMM定义了共享内存中多线程程序读写操作的行为规范
-
JMM把内存分为2部分,一块是私有线程的工作区域(工作内存),一块是所有线程的共享区域(主内存)
-
线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存
实现多线程的方法
-
继承 Thread 类:创建一个类,让它继承 Thread 类,然后重写 run() 方法。创建该类的实例对象,然后调用它的 start() 方法来启动线程。
-
实现 Runnable 接口:创建一个类,让它实现 Runnable 接口,然后重写 run() 方法。创建该类的实例对象,并将其作为参数传递给 Thread 类的构造函数来创建一个线程对象。然后调用线程对象的 start() 方法来启动线程
-
实现 Callable 接口:创建一个类,让它实现 Callable 接口,然后重写 call() 方法。创建该类的实例对象,并将其作为参数传递给 FutureTask 类的构造函数来创建一个 FutureTask 对象。然后将 FutureTask 对象作为参数传递给 Thread 类的构造函数来创建一个线程对象。最后调用线程对象的 start() 方法来启动线程。
如何退出线程
-
使用标志位
-
采用interrupt方法,通过while(thread.isInterrupted)方法来退出
-
采用stop方法,不推荐,会强制终止线程,可能导致线程问题,比如不知道任务到哪一步了,锁释放了没
notify和notifyAll
-
notify只能唤醒等待池中的一个线程,notifyAll可以唤醒等待池中所有线程
-
notify可能造成死锁(因为无法确定哪个线程被唤醒,所以被唤醒的线程可能不是所期望的,那么它会再次进入等待状态,而其他线程仍然是等待状态,就会造成死锁。比如两个线程,一个生产者线程,一个消费者线程,产品充足时生产者线程会进入等待,产品不足时消费者线程会进入等待,假设当前生产者和消费者都处于等待状态,此时需要唤醒消费者线程进行消费,调用notify()会随机唤醒一个线程,可能唤醒的是生产者,生产者被唤醒后发现产品充足不需要生产,则进入等待,而此时消费者依旧是等待状态没有被唤醒,就产生了死锁),notifyAll不会
为什么 wait() 和 notify() 必须在同步块中使用呢?
因为它们涉及到线程间的协作和状态的改变,需要依赖于同步机制来实现线程之间的互斥和同步。
如果不在同步块会抛出异常,是因为lost wake-up problem,假设两个线程,生产者消费者,生产者count+1,消费者count-1,当count=0时消费者会唤醒生产者,当count>0时生产者会唤醒消费者,假设此时消费者检查count=0,唤醒生产者后发生了上下文切换没有立即进入等待状态,生产者唤醒后执行count+1并准备唤醒消费者,但此时生产者还未进入等待状态,所以生产者的notify没有效果,执行唤醒操作后消费者才进入等待状态也就一直陷入等待状态
sleep和wait的区别
-
sleep是Thread类的方法,wait是Object类的方法
-
sleep不会释放锁,wait会释放锁
-
wait需要依赖synchronized关键字
Thread类的start()和run()区别
start()用来启动新创建的线程,并且start()内部是调用的run方法,但这跟直接调用run方法不同,run方法是在原本的线程中运行,而start是启动新线程
start只能被调用一次,run可以多次
java中interrupted 和 isInterrupted方法的区别?
调用interrupt方法中断一个线程时会设置终端标识为true。
-
isInterrupted是一个实例方法,用于检查线程对象的中断状态。
守护线程
线程分为用户线程和守护线程,用户线程就是普通线程,守护线程就是jvm的后台线程,比如垃圾回收线程就是一个守护线程,守护线程会在其他普通线程停止运行后自动关闭。可以设置 thread.setDaemon(true) 来把一个线程设置为守护线程
volatile是什么
volatile是一种轻量级的同步机制,相比于synchronized,volatile不会造成阻塞,不会引起线程上下文切换和调度,volatile有三大特性:
-
有序性:保证执行顺序跟代码顺序一致,避免了jvm的指令重排序问题,被volatile修饰的变量会在读写时加入不同的屏障,阻止其他读写操作越过屏障。
-
原子性:只能保证单个共享变量的原子性,若是多个共享变量需要与synchronized一起使用。
-
可见性:可以防止即时编译器的优化,比如while(!flag)会被优化成while(true),使得修改了被volatile修饰的变量,其他线程可以立即看到修改后的值
synchronized关键字
synchronized是java语言的关键字,是原生语法层面的互斥锁。属于阻塞式同步
synchronized可以作用于方法,代码块,类:
-
修饰非静态方法的话是对调用该方法的对象加锁,
-
修饰静态方法其实和修饰类的作用是一样的,锁的是该类所有对象的方法,
-
修饰代码块:可以指定加锁对象,进入同步代码块前要先获取指定对象的锁
synchronized修饰代码块时,底层是由monitor实现的,会在同步块的前后生成 monitorenter 和 monitorexit 字节码指令。在执行 monitorenter 时会尝试获取对象锁,如果该对象没被锁,或当前线程已经拥有了这个锁,则锁的计数器+1,当执行 monitorexit 时,则计数器-1,当计数器为0时,锁就被释放。若获取锁失败,则进入阻塞。monitor内部有三个属性:
-
owner:关联获得锁的线程
-
entrylist:关联处于阻塞状态的线程
-
waitset:关联处于waiting状态的线程
修饰方法或类时,并没有 monitorenter 和 monitorexit ,而是 ACC_SYNCHRONIZED 标识,jvm通过这个标识来判断一个方法或类是否是同步方法或同步类。
早期版本的synchronized属于重量级锁,效率低下,是因为 monitor 是依赖于底层的操作系统实现的,而在java6之后,java官方从jvm层面进行了优化,实现了锁升级机制,所以现在的效率还可以。
synchronized锁升级机制
锁升级过程:无锁->偏向锁->轻量级锁->重量级锁
-
无锁:初始状态,还没有线程竞争锁
-
偏向锁:当只有一条线程执行到同步代码块时,jvm会使用cas操作获取锁,升级为偏向锁,进入偏向锁状态后,如果没有其他线程竞争,该线程后续再次访问同步代码块时,jvm不会在进行cas操作,直接运行代码块,犹如没有锁一样
-
轻量级锁:当有多条线程竞争同一把锁时,偏向锁会升级为轻量级锁,本质是通过cas自旋实现的,不会阻塞线程,但是会持续消耗cpu,因为其他线程会不断自旋尝试获取锁,
-
重量级锁:如果自旋次数过多仍未获取到锁,则会升级为重量级锁,会阻塞线程
Lock和synchronized
-
Lock是一个接口,syncronized是原生语法层面的关键字
-
Lock需要手动的加锁释放锁,syncronized 的加锁释放锁是通过jvm自动控制的,在进行代码块时自动获取锁,退出时自动释放
-
Lock和syncronized都支持可重入锁
-
Lock支持公平锁和非公平锁,支持等待可中断这些新特性
-
Lock的加锁粒度更细,可以作用到代码块中的某部分,syncronized通常作用域整个方法或某个代码块
JAVA锁
-
乐观锁:乐观锁是一种乐观思想,认为读多写少,认为并发情况下一定不会被其他线程修改,java中通常通过cas来实现乐观锁,CAS(Compare And Swap)是一种乐观锁技术,它认为多个线程不对共享同一份数据,所以在实行cas操作时,首先会读取该数据的当前值,并预期该数据的值不会被其他线程修改。如果后面读取到的数据与预期值相同,则表示没有被其他线程修改,则可以更新该数据的值。若不同则表示被其他线程修改,则返回失败。cas只能保证单个共享变量的原子性,若要保证多个共享变量(多个键值对)的原子性需要加锁
-
悲观锁:悲观锁就是悲观思想,认为并发情况下一定会被其他线程修改,所以每次读写数据时都会加锁,java中使用synchronized实现悲观锁
-
自旋锁:如果持有锁的线程能在很短时间内释放锁,则等待线程不会进入阻塞状态,而是等一等也就是自旋,等持有锁的线程释放后立刻就能获取到锁。为了不让线程一直占用cpu自旋,需要设置一个自旋的最大时间。通过AtomicReference类实现基于cas的自旋锁
-
可重入锁:可重入锁支持线程重复获取锁,在同一个线程可以多次获取同一个锁而不会发生死锁,在java中用 ReentrantLock 实现
-
Synchronized 同步锁:属于独占式的悲观锁,也属于可重入锁
-
公平锁与非公平锁:公平锁遵循先到先得的原则,按照线程请求锁的顺序分配锁资源,非公平锁则无序
java中synchronized 和 ReentrantLock 有什么不同?
-
ReentrantLock是一个接口,syncronized是原生语法层面的关键字
-
ReentrantLock需要手动的加锁释放锁,syncronized 的加锁释放锁是通过jvm自动控制的,在进行代码块时自动获取锁,退出时自动释放
-
ReentrantLock和syncronized都支持可重入锁
-
ReentrantLock支持公平锁和非公平锁,支持等待可打断( 通过
lock.lockInterrupitibly
获取锁,如果一直获取不到锁,可通过interrupt
来提前终止线程, ),可超时(lock.tryLock(2,second)
),多条件变量(lock.newCondition
)这些新特性 -
ReentrantLock的加锁粒度更细,可以作用到代码块中的某部分,syncronized通常作用域整个方法或某个代码块
-
synchronized有一个锁升级机制
AQS
AQS是多线程中的队列同步器,是一种锁机制,像ReentrantLock就是基于AQS实现的。
AQS内部维护了一个先进先出的双向队列,存储排队的线程
在AQS内部还有一个属性state,这个state默认是0,表示无锁状态,如果有线程将state修改成了1就表示该线程获取了锁。同时在修改state的时候采用的是cas操作,保证多个线程修改下的原子性
ReentrantLock 分为公平锁和非公平锁,底层如何实现
他们底层实现都是由AQS来进行排队,也就是 AbstractQueuedSynchronizer (ReentrantLock的Sync继承了AbstractQueuedSynchronizer,也是加锁释放锁的核心,NonfairSync继承了Sync,FairSync也继承了Sync,分别对应公平锁和非公平锁),两把锁区别在于线程在使用lock()加锁时:
-
如果是公平锁,会先检查AQS队列中是否存在线程在排队,如果有线程在排队,则当前线程也进行排队
-
如果是非公平锁,则不会检查是否有线程在排队,而是直接竞争锁。
当锁释放时,都是唤醒排在最前面的线程,所以非公平锁只是体现在了线程加锁阶段,而没有体现在线程被唤醒阶段。
java如何避免死锁
避免死锁最主要就是避免若干线程循环等待资源
-
注意加锁顺序
-
注意加锁时限,设置过期时间
-
注意检查死锁,可通过编写脚本实现周期执行jstack来检查,一但发生死锁则发送通知
有三个线程T1,T2,T3,如何保证顺序执行?
使用join()方法,join方法会阻塞当前线程,直到目标线程执行完毕才会执行当前线程。
比如三个线程T1,T2,T3,在T3的run方法中写 T2.join() ,在T2的run方法中写 T1.join() ,这样当启动三个线程时,启动顺序可以随意,但是执行结果会是T1->T2->T3。
Thread类中的yield方法有什么作用?
yield方法可以暂停当前正在执行的线程,但它只是让当前线程让出cpu时间片,以便其他线程有机会执行,若没有其他线程处于就绪状态,则当前线程仍然可以继续执行。
线程池中submit() 和 execute()方法
两个方法都可以向线程池提交任务,execute方法的返回值类型是void,而submit方法的返回值是Future类型,通过Future的get方法可以获取线程执行的返回值。
常用的线程池有哪些
-
newSingleThreadExecutor:创建一个单线程的线程池,保证所有任务的执行顺序按照任务的提交顺序执行
-
newFixedThreadPool:创建一个固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待
-
newCachedThreadPool:创建一个可缓存的线程池,缓存线程池会根据需要自动创建新的线程,并且在线程空闲一定时间后自动回收线程
-
newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行
简述一下你对线程池的理解
线程池其实就是一个可以容纳多个线程的容器,里面的线程可以反复使用,省去了频繁创建销毁线程对象的操作。
创建线程池的方法:
-
使用Executors创建(不推荐):
Java中为什么不建议使用Executors的四种线程池呢?_Mc0的博客-CSDN博客
newSingleThreadExecutor和newFixedThreadPool:
允许的请求队列长度为lntegerMAX VALUE,可能会堆积大量的请求,从而导致OOM。
newCachedThreadPool:
允许的创建线程数量为integer.MAX VALUE,可能会创建大量的线程,从而导致OOM。
-
使用ThreadPoolExecutor的构造方法自定义线程池(推荐)
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, // 核心线程数10, // 最大线程数60, // 线程空闲时间,超过这个时间没有任务执行就会被回收TimeUnit.SECONDS, // 空闲时间单位new ArrayBlockingQueue<>(20), // 用于存储待执行的任务的阻塞队列,容量为20Executors.defaultThreadFactory(), // 线程工厂,用于创建线程new ThreadPoolExecutor.AbortPolicy() );
合理利用线程池的好处:
-
避免线程频繁创建和销毁的损耗
-
提高响应速度,当任务到来时,任务可以不需要等到线程创建就能立即执行
-
统一分配、管理线程
线程池常见的阻塞队列
LinkedBlockingQueue | ArrayBlockingQueue |
---|---|
默认无界,支持有界,最好设置参数让他有界 | 强制有界 |
底层链表 | 底层数组 |
懒加载,在新增节点时才添加数据 | 提前初始化数组 |
两把锁(头尾) | 一把锁 |
线程池常见的拒绝策略
-
AbortPolicy拒绝策略,当线程池满了且任务队列也满了时,抛出RejectedExecutionException异常
-
CallerRunsPolicy:用调用者所在的线程来执行任务
-
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务
-
DiscardPolicy:直接丢弃任务
线程池提交任务的流程
-
当执行execute()或submit()方法提交一个Runable对象时
-
会先判断当前线程池中的线程数是否小于核心线程数
-
如果小于:则创建新线程并执行
-
如果大于等于:则放入待执行队列
-
如果队列没满,则正常入队
-
如果队列满了,则入队失败,会判断当前线程池中的线程数是否小于最大线程数
-
如果小于,则创建新线程并执行任务
-
如果大于等于,则执行拒绝策略
如何控制某个方法允许并发访问线程的数量
使用 semaphore
,在并发的情况下,可以控制方法的访问量
-
创建 semaphore 对象,设置一个容量,表示最多只能这么多容量的线程执行
-
acquire() 表示请求一个信号量,这是信号量数量-1。
-
release() 表示释放一个,信号量+1。
-
为0就不能
ThreadLocal
1、基本原理
threadlocal会为每个线程开辟一个独立的线程副本,可以利用threadlocal将数据缓存在某个线程内部,该线程可以在任意时刻,任意方法中获取缓存的数据
ThreadLocal通过操作每个线程内部的ThreadLocalMap来实现,也就是说每个线程内部都有一个ThreadLocalMap,ThreadLocalMap的底层实现是entry数组,每个entry的key存储的key为 ThreadLocal,value对应线程的局部变量值
因为ThreadLocal操作的都是当前线程中的变量,和其他线程无关,也就没有线程安全的问题
2、为什么会发生内存泄漏?
因为 ThreadLocalMap中的entry的key是弱引用对象,弱引用是只要被垃圾回收器发现就会被回收,被回收后entry中的key就是null了,这样就无法找到对应的value,而entry中的value是强引用,只有线程结束才会被回收,所以会造成内存泄露,正确的使用方法是在使用完之后调用threadlocal的remove方法,手动清除entry对象。
JavaWeb
JDBC
原生jdbc操作数据库流程
-
class.forname() 加载驱动
-
DriverManager.getConnection() 获取数据连接对象
-
根据SQL获取sql会话对象:Statement.PreparedStatement
-
执行SQL处理结果集,执行SQL前有参数则通过 setXXX()进行设置
-
关闭连接
jdbc处理事务的过程
-
设置为手动提交: conn.setAutoComit(false);
-
提交事务:conn.commit()
-
出现异常则回滚:conn.rollback()
网络通讯部分
TCP与UDP区别
-
连接性:TCP是面向连接的协议,UDP是无连接的协议
-
可靠性:TCp提供可靠的数据传输,确保数据按顺序到达目标地址;UDP因无连接,是不可靠协议,数据包可能丢失或乱序
-
速度:UDP比TCP更快,
-
传输大小:TCP没有数据包大小限制,可以传输任意大小的数据;UDP每个数据包的大小限制在64k内。
-
适用场景:TCP适用于对数据完整性和顺序性要求较高的场景,如电子邮件、文件传输;UDP适用于实时性要求高,可接受部分数据丢失的场景,如音视频童话,实时游戏。
什么是HTTP协议
客户端和服务器端之间数据传输的格式规范,格式简称为“超文本传输协议”,是一个基于请求与响应模式的,无状态的,应用层的协议,基于TCp的连接
get和post
-
get重点在获取数据,post发送数据
-
get通过url请求,通过field=value的形式拼接在url?后,多个请求数据用&连接,用户是可以直接看到的,不安全。post将数据封装在请求体中,用户无法直接看到,相对安全。
-
get传输的数据量小,因为受到url长度限制,但效率高。post可以传输大量数据
重定向和转发
-
重定向是客户端行为,转发是服务器端行为
-
重定向:两次请求(第一次请求发送到原始地址,服务器接收后会返回一个重定向响应,响应中包含302状态码和“Location"字段,该字段指向了重定向的目标url,浏览器再根据这个目标url发起第二次请求)。浏览器地址会发生变化,可以访问自己web之外的资源,传输的数据会丢失。
-
转发:一次请求,浏览器地址不变,访问自己本身的web资源,传输的数据不会丢失。
Ajax
-
Ajax是一种创建交互式网页应用的网页开发技术。
-
通过异步模式,提升了用户体验。
-
可以实现局部刷新
-
优化了浏览器与服务器之间的传输,减少不必要的数据往返,减少了带宽占用。
OSI七层模型和TCP/IP模型
从上到下
OSI:
应用层 :负责给应用程序提供接口,HTTP协议在此层
表示层 :负责编解码,加解密,压缩与解压缩
会话层 :协调系统间的通信
传输层 :负责端到端的连接,TCP和UDP在此层
网络层 :为网络设备提供逻辑地址
数据链路层 :提供可靠的点对点数据传输,这一层通常处理帧的封装、错误校验
物理层:定义物理设备相关标准和模式
TCP/IP: 应用层 -> 传输层 -> 网络层 -> 数据链路层
HTTP建立连接过程
HTTP协议基于TCP的连接,所以建立http连接前要先建立Tcp连接
-
浏览器解析用户输入的URL,生成一个HTTP格式的请求
-
先根据URL域名从本地hosts文件查找是否有映射IP,如果没有就进行DNS解析,得到IP地址
-
通过三步握手建立tcp连接
-
客户端发送http请求
-
服务端处理请求并响应
-
客户端接收响应并渲染
-
客户端或服务端断开TCP连接
在http1.0的时候默认短连接,也就是每一次请求都会按上面的步骤重复,在http1.1开始默认是长连接,通过请求头connection字段申明keep-alive,作用就是减少tcp握手次数,在使用长连接的情况下,一开始建立了一条连接,后续机会继续沿用这条连接
TCP三次握手四次挥手
【通俗易懂】三次握手与四次挥手_三次握手和四次挥手_小玄ks的博客-CSDN博客
三次握手
第一次握手:客户端向服务器端发送一个syn报文,并置标志位syn=1(请求建立连接),初始化序号seq=x,此时客户端状态为 SYN_SENT。
第二次握手:服务器端收到syn报文后,会发送 syn-ack 报文,并置 syn=1,初始序号seq=y,确认号字段 ack=x+1(表明我收到了初始序号seq=x的报文),此时服务器状态为 SYN_RCVD。
第三次握手:客户端收到 syn-ack 报文后,会发送一个 ack 报文,初始序号seq=x+1,确认号字段 ack=y+1,表明我收到了你的确认,此时客户端状态为 ESTABLISHED。
服务器接收到ack报文后,也处于ESTABLISHED状态,双方就建立了连接
四次挥手
初始时客户端和服务器都是 ESTABLISHED 状态,服务器和客户端都可以发起断开连接的请求,假设客户端发起请求
第一次挥手:客户端发送一个 FIN 报文,序号 seq=u,此时客户端状态为 FIN_WAIT_1
第二次挥手:服务器收到 FIN 报文后,立即发送 ack报文,确认号字段 ack=u+1,序号 seq=v。表明已经收到了客户端的报文。此时服务器状态为 CLOSE_WAIT 状态
第三次挥手:服务器发送 Fin 报文,置序号 seq=w,确认号 ack=u+1,表明可以断开连接。这里第三次挥手和第二次挥手不能合并在一起是因为:ack和fin的触发时机不同,服务器收到客户端的 fin 报文可以立即发送 ack 报文,但服务器想发送fin报文需要处理完缓冲区中的数据才可以。
第四次挥手:客户端收到 Fin 报文,发出 ack 报文,序号 seq=u+1,确认号ack=w+1,此时客户端状态为 TIME_WAIT,需要等待一段时间确保服务器收到自己的报文后才会进入CLOSED状态,如果服务器没收到,就会触发超时重传,服务器会再次发送 FIN 报文,也就是第三次握手的过程。
常见的HTTP相应状态码
返回的状态 1xx:指示信息--表示请求已接收,继续处理 2xx:成功--表示请求已被成功接收、理解、接受 3xx:重定向--要完成请求必须进行更进一步的操作 4xx:客户端错误--请求有语法错误或请求无法实现 5xx:服务器端错误--服务器未能实现合法的请求
HTTP 协议与 TCP/IP 协议的关系
HTTP 的长连接和短连接本质上是 TCP 长连接和短连接。HTTP 属于应用层协议,在传输层使用 TCP 协议,在网络层使用 IP 协议。IP 协议主要解决网络路由和寻址问题,TCP 协议主要解决如何在 IP 层之上可靠的传递数据包,使在网络上的另一端收到发端发出的所有包,并且顺序与发出顺序一致。TCP 有可靠,面向连接的特点。TCP/IP协议是底层的网络协议,负责将数据传输到目标地址。而HTTP协议是应用层协议,通过TCP/IP协议进行通信,实现了客户端和服务器的交互。HTTP协议依赖于TCP/IP协议的可靠性和连接管理,使得Web浏览器能够访问和获取Web服务器上的资源。
跨域是什么,怎么解决
跨域是指浏览器在发起网络请求时,会检查该请求所对应的协议,域名,端口和当前网页是否一致,如不一致则浏览器会进行限制,比如原本是在www.baidu.com,如果使用ajax去访问www.jd.com是不可行的。
解决方法:
-
设置响应头,resp.setHeader("Access-Control-Allow-Origin","*"),表示可以访问所有网站,不受同源策略的影响
-
使用jsonp,jsonp是基于script标签来实现的,因为script标签是可以跨域的
-
后台控制,先访问同域名下的接口,然后在接口中通过HttpClient去调用目标接口
-
网关,比如springcloud的gateway
数据库
Sql注入
举例: select admin from user where username='admin' or 'a'='a' and passwd='pwd' or 'a'='a'
使用预编译的方式预防:select admin from user where username=?And passwor=?
mybatis中使用 # 而不是 $ 来预防
select查询语句完整执行顺序
select -> from -> where -> group by -> having -> order by
group by :将查到的数据分组
having:对分组的数据过滤
order by:按照指定顺序
存储引擎
-
MyIASM:Mysql5.5之前默认的存储引擎,不支持行级锁和外键,使用的是表级锁,所以写操作需要锁住整张表,效率低,读操作则很快。存储总行数,不支持事务。适用于大部分是读操作的场景。
-
InnoDb:支持事务和外键,支持表级锁,默认使用行级锁,并发性能较高,不存储总行数,提供了崩溃恢复的能力,通过 redo log 和 undo log 保证数据的持久性和原子性。适用于经常写操作的场景。
-
binlog:
-
MySQL的server层日志。
-
用于实现数据库的复制和恢复。记录了所有的DDL语句和DML语句
-
可以用于主从复制和基于时间点还原数据库;
redo log:
-
InnoDB存储引擎核心日志。
-
确保事务的持久性。
-
记录的是事务提交时数据页的物理变化,并在后台异步地将这些更改应用到磁盘上的数据文件,从而确保数据的持久性,宕机时可用来回复数据
undo log:
-
InnoDB存储引擎日志。
-
确保事务的原子性。
-
undo log记录了事务执行过程中对数据的的反向操作,以便在事务回滚或崩溃恢复时可以撤销这些修改,将数据恢复到原始状态,确保数据的原子性。
-
-
-
Memory:使用存在内存中的内容来创建表,每个Memory表对应一个磁盘文件,因为是存放在内存的,所以访问速度很快,一但mysql服务关闭数据就会丢失
Innodb是如何实现事务的
事务特性分为ACID:
-
其中redolog通过记录事务提交时的数据页的物理修改,宕机时可用于回复数据来确保持久性
-
undolog记录事务执行过程中的反向操作,用于事务回滚或发生故障时可以撤销这些操作,恢复原始数据,确保原子性
-
隔离性则通过锁(拍他锁)和MVCC来实现
-
一致性需要通过另外三个特性来保证
解释一下MVCC(如何解决隔离性)
多版本并发控制,维护一个数据的多个版本,使读写没有冲突,底层实现分为3个部分,第一个是隐藏字段,第二个是undolog,第三个是readView读视图
隐藏字段是指mysql给每个表都设置了隐藏字段,有一个是trx_id(事务id),记录每一次操作的事务id,是自增的,另个是roll_pointer,指向上一个事务版本
undolog的作用是记录回滚日志,存储老版本数据,内部会形成一个版本链,通过roll_pointer指针连接各个事务版本
readView解决查询选择哪一个版本的事务,在内部定义了一些匹配规则和当前的一些事务id来判断该访问哪个版本的数据,不同隔离级别的快照读是不同的,最终的访问结果也不同,如果是RC则每次执行快照读都会生成readView,保证读取的是事务开始之前已经提交的数据;如果是RR则仅在事务中第一次执行快照读时生成readView,后续复用,确保了在事务执行期间看到的数据是一致的
-
当前读:读取的是记录的最新版本,通常会加锁,共享锁和排它锁都是当前读
-
快照读:简单的select就是快照读,读取的可能是历史版本,不加锁,非阻塞
主从同步的原理
为了减轻数据库的压力,以及避免数据丢失和主系统宕机
核心是binlog,binlog记录所有DDL和DML语句,可以用于数据库的复制和恢复
具体流程是:
-
master提交事务时,会将数据变更记录在binlog中
-
slave读取master的binlog,记录在自己的中继日志中 relay log
-
slave再读取中继日志中的数据,写入自己的数据库中
了解分库分表嘛
具体可以拆分成4种策略:
-
水平分库:将一个库的数据拆分到多个库中,解决海量数据存储和高并发的问题
-
水平分表:相对用的少,解决单表存储和性能的问题
-
垂直分库:根据业务拆分,提高高并发下的磁盘IO(一些业务可能更加频繁地进行写操作,而另一些业务则更加偏向读取。将它们分别存储在不同的数据库中,可以根据其特性进行针对性的磁盘IO优化,对写多的数据库可以采用高速磁盘或内存缓存,对于读多的可以用SSD来提高性能)和数据隔离
-
垂直分表:根据字段拆分,可以实现冷热数据分离,多个表之间互不影响
进行分库后可能会出现一些问题,比如:分布式事务问题,跨节点关联查询,跨节点分页排序查询,主键避免重复问题,所以需要引入中间件如mycat来解决,分布式事务可以用本地消息表或seata解决
了解过索引嘛
-
索引是帮助mysql高效获取数据的数据结构,是有序的
-
通过索引列对数据进行排序,提高数据的检索效率
-
避免全表扫描
索引的底层数据结构了解过吗
mysql中innodb采用B+树的数据结构
-
B+树是个矮胖树,层级更少,性能更高,
-
非叶子结点只存储指针,叶子结点存储数据
-
叶子结点是一个双向链表,更加利于范围查询
什么是聚集索引什么是非聚集索引
聚集索引指数据和索引放在一起,叶子结点保存了整行数据,聚集索引有且只有一个,通常,聚集索引选取规则:
-
如果存在主键,主键索引就是聚集索引
-
如果不存在主键,则使用第一个唯一索引作为聚集索引
-
如果都没有,innodb会自动生成一个rowid作为隐式的聚集索引
非聚集索引指数据和索引分开存储,叶子结点保存对应的主键,非聚集索引可以有多个,一般我们自定义的索引都是非聚集索引
什么是回表查询
通过二级索引找到对应的主键,再通过主键在聚集索引找到对应的行数据,这个过程就是回表
是什么叫覆盖索引
指查询使用了索引,返回的列必须在索引中能够全部找到
-
使用id查询,走聚集索引,能够一次查到整行数据,只用一次索引扫描,所以也属于覆盖索引,性能高
-
如果查询返回的列中没有全部包含索引列,会触发回表查询,就不是覆盖查询,所以要避免select *
mysql超大分页处理
数据量较大时,进行limit分页处理时,在查询时越往后,分页查询效率越低
比如 select * from table limit 9000000,10;
解决方法: 通过覆盖索引和子查询来优化
select * from table t, (select id from table order by id limit 9000000,10) a where t.id = a.id;
子查询中根据id排序并分页,确定了id后,只查询匹配子查询返回的id列表中的数据,因为查询id时走的是覆盖索引,所以性能相对更高
创建索引原则
-
针对数据量较大,访问频繁的表创建索引(单表超过10w)
-
针对经常where, order by, group by 的字段创建索引
-
尽量创建唯一索引,选择区分度较高的列,区分度越高,索引效率越高
-
尽量使用联合索引,更大可能可以覆盖索引,避免了回表查询
-
控制索引的数量,写操作的时候需要维护索引
-
如果索引列不能存储NULL值,则设置为NOT NULL
什么情况下索引会失效
-
违反最左前缀法则,整个联合索引都失效,;如果符合最左前缀,但是联合索引跳过中间某一列,则只有最左列的索引生效
-
对于联合索引,中间的列使用了范围查询也就是age>18,右边的列不能使用索引
-
在索引列上进行运算操纵和函数操作
-
字符串不加单引号,会被自动类型转换,造成索引失效
-
%放在开头的模糊查询
-
避免in 和 not in也可能会导致全表扫描,对于连续的数值,能用between就别用in
常用sql优化
表的设计优化:参考《阿里开发手册》,比如设置合适的数值(int, tinyint, bigint),设置合适的字符串类型(char是定长,varchar是变长),根据实际情况使用
sql优化:
-
避免使用select *
-
避免索引失效
-
尽量用union all代替 union,union会多一次过滤,效率低
-
索引并不是越多越好,索引可以提高查询的效率,但会降低insert和update的效率,一个表中的索引最好不要超过6个
-
Join优化,能用innerjoin就别用左右连接,如果必须用左右连接,也要以小表为驱动,内连接会对两个表进行优化,优先吧小表放在外边,把大表放在里边,左右连接则不会重新调整顺序
-
避免in 和 not in也可能会导致全表扫描,对于连续的数值,能用between就别用in
-
%放在开头的模糊查询
索引
索引就是加快检索表中数据的方法。按分类可以分成:
-
普通索引:最基本的索引,没有任何限制。
-
唯一索引:与普通索引类似,不同在于索引列的值必须唯一,可以为null,如果是组合索引,则列值的组合必须唯一
-
主键索引:特殊的唯一索引,一个表只能有一个主键,不允许有空值。
-
组合索引:由多个列组成,只有在查询条件中使用到了创建索引时的第一个字段,索引才会被使用。
-
全文索引:主要用来查找文中的关键字,只有char, varchar, text列可以创建全文索引。
按结构可以分成:
-
B+Tree索引:这是最常见的索引类型。B-Tree索引适用于查找某个范围内的记录,例如根据名字或者日期范围来查询记录。B-Tree索引可以优化等值查询和范围查询,并且支持多列索引。常见的包括:PRIMARY KEY、UNIQUE、INDEX等
-
Hash索引:这种索引适用于等值查询,例如根据ID来查询某个记录。Hash索引将索引列的值作为输入,通过Hash算法计算出一个指针,指向对应的记录。Hash索引无法优化范围查询,也无法支持排序,因此Hash索引在实际应用中使用较少
-
Full-text索引:这种索引适用于全文搜索,例如在文章中查找包含某个关键词的记录。Full-text索引可以提高全文搜索的效率,并且支持模糊匹配和相关度排序。
-
R-Tree索引: 这种索引适用于地理信息系统和空间数据存储。R-Tree索引可以高效地查询某个范围内的记录,例如查询某个点是否在某个多边形内;
优点:
-
创建唯一性索引,保证某些列的唯一性
-
大大加快检索速度,这也是主要的原因
-
加速排序和分组的过程
缺点:
-
增加索引维护成本:当表中的数据发生变化(如插入、更新、删除)时,索引需要进行维护
-
额外占用物理空间,
B树和B+树的区别,为什么mysql采用B+树
B树的特点是:
-
节点排序
-
一个节点可以存储多个元素,并且是排好序的
B+树的特点是:拥有B树的特点,同时:
-
B+树是个矮胖树,层级更少,性能更高,
-
非叶子结点只存储指针,叶子结点存储数据
-
叶子结点是一个双向链表,更加利于范围查询
三大范式
第一范式:每个属性都是不可再分的最小数据单元。
第二范式:满足第一范式,并且每一行的非主属性,必须完全依赖于主键,每张表只能描述一件事情
第三范式:满足第二范式,并且表中的列不存在对非主键列的传递依赖,比如订单表中,除了主键订单编号外,顾客姓名依赖于非主键顾客编号。
BC范式:BC范式是指Boyce-Codd范式,它是在第三范式的基础上定义的。它的定义是:在关系模式中每一个决定因素都包含候选键,也就是说,只要属性或属性组A能够决定任何一个属性B,则A的子集中必须有候选键
存储过程
为了完成特定功能使用sql语句编写的程序,存储过程在第一次执行时进行编译,然后将编译好的代码保存在数据库中,供以后调用,提高执行效率。
存储过程优缺点
优点 1)存储过程是预编译过的,执行效率高。 2)存储过程的代码直接存放于数据库中,通过存储过程名直接调用,减少网络通讯。 3)安全性高,执行存储过程需要有一定权限的用户。 4)存储过程可以重复使用,减少数据库开发人员的工作量。 缺点 1)调试相对麻烦 2)移植问题,数据库端代码当然是与数据库相关的。但是如果是做工程型项目,基本不存在移植问题。
存储过程写法
创建
CREATE PROCEDURE p1()
BEGIN SELECT count(*) FROM account; END;
调用
call p1();
查看
SHOW CREATE PROCEDURE 存储过程名称 ; -- 查询某个存储过程的定义
删除
DROP PROCEDURE [ IF EXISTS ] 存储过程名称 ;
触发器是什么
触发器是特殊的存储过程。触发器是指一段代码,当触发某个事件时,自动执行这些代码。 使用场景 • 可以通过数据库中的相关表实现级联更改。 • 实时监控某张表中的某个字段的更改而需要做出相应的处理。 注意不要滥用,否则会造成数据库及应用程序的维护困难。
MySQL中都有哪些触发器?
在MySQL数据库中有如下六种触发器: • Before Insert • After Insert • Before Update • After Update • Before Delete • After Delete
创建触发器
只有一个执行语句
create trigger 触发器名 before | after 触发事件
on 表名 for each row 执行语句
有多个执行语句
create trigger 触发器名 before | after 触发事件 on 表名 for each row begin 执行语句列表 end
char varchar
-
varchar 类型的长度是可变的,而 char 类型的长度是固定的。char 类型是一个定长的字段,以 char(10) 为例,不管真实的存储内容多大或者是占了多少空间,都会消耗掉 10 个字符的空间
-
char 长度最大为 255 个字符,varchar 长度最大为 65535 个字符
-
varchar 类型的查找效率比较低,而 char 类型的查找效率比较高
事务
将一组操作作为一个整体,向系统提交,要么都执行成功,要么都执行失败(回滚)。是一个不可分割的工作逻辑单元。事务必须具备以下四个属性ACID:
-
原子性:事务是一个完整的操作,事务中的操作要么都执行成功,要么都执行失败。
-
一致性:事务完成时,必须使所有的数据都保持一致状态
-
隔离性:事务之间是彼此隔离的,相互独立的
-
持久性:事务完成后,对数据库的修改是永久存在的
举个例子:
-
A向B转账,转账成功,A扣除500,B增加500,原子性体现在扣除和增加是一个整体,要么都成功要么都失败
-
一致性就是数据要一致,A扣除了500,B相应也增加了500,整体数据不变
-
隔离性就是A向B转账的过程中,不被其他事务干扰
-
持久性就是事务提交后,这个修改时永久性的
事务处理的两种方式
-
用BEGIN ROLLBACK COMMIT来显式实现事务
-
SET AUTOCOMMIT=0/1 禁止自动提交/开启自动提交
四种隔离级别
mysql默认可重复读
-
读未提交:一个事务可以读到另一个事务未提交的数据(脏读)
-
读已提交:一个事务只能读取到已经提交的数据,避免了脏读,可能出现不可重复读
-
可重复读:一个事务中,对同一数据的多次读取得到的结果是一致的,避免了脏读和不可重复读,可能出现幻读
-
序列化:强制事务串行执行,会严重影响性能
脏读、不可重复读、幻读
-
脏读:事务读取到了另一个事务未提交的数据
-
不可重复读:在一个事务中,多次读取同一份数据,得到的结果不一致
-
幻读:在一个事务中,多次查询会出现新增或消失的行,跟出现幻觉一样
数据库并发策略
乐观锁、悲观锁、时间戳
-
乐观锁:认为用户在读数据的时候,别人不会去写自己正在读的数据,适合多读场景
乐观锁常见的2种实现方式:
-
版本号机制:在数据表中加入version字段,当数据被修改,version+1,在读取数据的时候也会将version读出来并保存,当修改时判断表中的version和前面保存的version是否一致,一致才可以更新,不一致则说明被其他线程修改过,需要放弃提交更新操作。
-
cas操作:在实行cas操作时,首先会读取该数据的当前值,并预期该数据的值不会被其他线程修改。如果后面读取到的数据与预期值相同,则表示没有被其他线程修改,则可以更新该数据的值。若不同则表示被其他线程修改,则返回失败。cas只能保证单个共享变量的原子性,若要保证多个共享变量(多个键值对)的原子性需要加锁(syncronize)
缺点:
-
ABA问题:如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然是 A 值,那我们就能说明它的值没有被其他线程修改过了吗 ? 很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回 A,那 CAS 操作就会误认为它从来没有被修改过。这个问题被称为 CAS 操作的“ABA"问题。在 Java 中,可以使用
AtomicStampedReference
类来解决 ABA 问题,该类可以记录变量值和版本号的信息,确保 CAS 操作的正确性。 -
循环时间长开销大:若是自旋cas(也就是不成功就一直循环执行到成功),如果长时间不成功,会给cpu带来非常大的执行开销。
-
只能保证单个共享变量
-
-
悲观锁:跟乐观锁相反,认为在读数据时,别人一定会写自己在读的数据,所以在读数据时,会对读取的数据加锁,适合多写场景
-
时间戳:不加锁,通过时间戳来控制并发,在表中添加一列时间戳TimeStamp,每次读出来的时候,把该字段也读出来,要修改时更新为最新时间,并在提交修改时与前面读取保存的字段判断,如果大于数据库中的该字段,则允许修改,否则不行。
数据库锁
-
行级锁:是一种排它锁,防止其他事务修改此行。
-
表级锁:对当前操作的整张表加锁,他实现简单,资源消耗少,表级锁分为表共享读锁(共享锁)和表独占写锁(排它锁)
-
页级锁:介于行级锁和表级锁的一种,表级锁速度快,但冲突多;行级锁冲突少但速度慢。页级锁一次锁定相邻的一组记录。
也可以划分为
-
共享锁(读锁):共享锁允许多个事务同时读取同一份数据,但不允许对数据进行修改。
-
排它锁(写锁):排它锁只允许一个事务独占地进行数据读取和修改,其他事务无法同时获取共享锁或排它锁。
也可以划分为
-
乐观锁
-
悲观锁
如何定位慢查询
可以通过运维工具skywalking,可以直观的看到那个接口比较慢,并且可以分析接口哪部分比较慢,如果是sql执行慢还能看到sql的执行时间。
mysql也提供了慢日志功能(slow_query_log = 1 ),需要开启,设置指定时间,当sql执行超过这个时间就会记录到这个日志中,不过一般只用于调试阶段,因为会有一定损耗
慢查询优化
通过explain语句
-
通过key和key_len检查是否走了索引
-
通过type字段查看sql有无优化空间,是否存在全索引扫描或全表扫描
-
通过extra判断是否出现了回表的情况,如果有可以尝试添加索引或修改返回字段来修复
possible_key:可能用到的索引
key:实际命中的索引
key_len:索引占用的大小
exter:如果是 Using where
或 using index
,表示走了索引,需要的数据在索引列内都能找到,不需要回表查询。如果是 Using index condition
,表示走了索引,需要回表查询
type:这条sql连接的类型,性能由好到坏是 Null(查询没有用到表), System(查询mysql自带的表), const(根据主键查询), eq_ref(主键索引或唯一索引:返回一条数据), ref(索引查询:可能返回一或多条数据), range(范围查询), index(遍历整个索引查询), all(全表扫描)
分布式缓存
缓存击穿
一个热点数据失效,导致大量请求涌入数据库
解决方案:
-
加锁,优点:强一致性,缺点:并发性能
-
逻辑过期:不加过期时间,而是将过期时间放在缓存的value中,通过代码来判断逻辑:
-
假设线程1查缓存,从value中判断出来当前的数据已经过期了,然后会获取锁
-
接着创建一个新线程2,此时的线程1可以直接返回结果,不过返回的是脏数据
-
线程2查询数据库并将最新的数据写入缓存,重置过期时间,然后释放锁
-
在线程2释放锁前,其他线程访问但无法获取锁,也会直接返回脏数据。
-
只有等线程2完成重置逻辑后释放锁后,其他线程才能返回正确的数据
优点:高可用,性能好
缺点:不能保证数据一致
-
缓存穿透
指请求一个数据库中没有的数据,数据库中没有缓存自然也没有,如果频繁访问的话就会对数据库造成很大压力。
解决方案:
-
缓存空结果,并设置它的过期时间
-
设置布隆过滤器(布隆过滤器由一个位数组和多个哈希函数组成。当要插入一个元素时,首先通过多个哈希函数生成对应的多个哈希值,然后将位数组中对应位置的位设置为1。当要查询一个元素是否存在时,使用相同的多个哈希函数得到多个哈希值,然后检查位数组中对应的位是否都为1。如果有任何一位为0,就可以确定元素不在集合中;如果所有位都为1,元素可能在集合中),数组越大误判率越小,可以通过redisson或guava实现布隆过滤器,可以操控误判率
缓存雪崩
缓存中大量数据同时失效,导致大量请求直接访问数据库
解决方案:设置不同的缓存失效时间,搭建redis集群,添加多级缓存如guava,添加降级限流策略
缓存预热
系统上线前,先将相关的数据加载到缓存中,避免用户首次访问直接去数据库。
SpringMVC
什么是SpringMVC
SpringMVC是一个基于java的实现了MVC设计模式的请求驱动类型的web框架,通过把Model,View,Comtroller层分离,实现了多个组件将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分
SpringMVC的流程
-
用户发送请求至 DispatcherServlet 前端控制器
-
DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器
-
HandlerMapping根据请求的URL将请求映射到相应的controller并返回给DispatcherServlet
-
DispatcherServlet 调用 HandlerAdapter 处理器适配器。
-
HandlerAdapter 经过适配调用具体的 controller 并最终返回 ModelAndView 给 DispatcherServlet
-
DispatcherServlet 将 ModelAndView 传给 ViewResolver 视图解析器
-
ViewResolver 解析后返回具体的view
-
DispatcherServlet 对view进行渲染并响应给用户
优点
-
支持各种视图技术、模板引擎,不局限于jsp
-
与spring集成
-
清晰的模块组件
主要组件
-
DispatcherServlet 前端控制器:接收请求响应结果,相当于转发器,减少了其他组件之间的耦合
-
HandlerMapping 处理器映射器:根据请求URL来找controller
-
HandlerAdapter 处理器适配器:执行控制器并处理参数、返回值
-
ViewResolver 视图解析器:进行视图解析
SpringMvc常用注解
-
@RequestMapping:处理请求url映射的注解,可用于类和方法上,用在类上表示该类中所有响应请求的方法都是以该地址作为父路径
-
@RequestBody:接收http请求的json数据,将json转为java对象
-
@ResponseBody:将java对象转为json数据并返回
如何拦截get请求的方法
可以在 @RequestMapping 注解中加 Method=RequestMethod.GET
注解
注解的工作原理主要是通过Java的反射机制来实现的。当编译器或运行时系统遇到注解时,会通过反射获取注解的信息,并根据注解的定义来进行相应的处理。
Spring
Spring是什么
Spring是一个轻量级的IOC和AOP容器框架,提供容器来装载具体的bean对象,没有spring钱,通常使用new关键字来创建对象,有了spring后,只需要告诉spring容器有哪些对象,他会帮我们创建和维护整个对象的生命周期。
IOC表示控制反转,也就是把原来new对象的控制权交给了容器,Ioc的一个重点就是在系统运行中,动态的向某个对象提供它所需要的资源。这一点是通过DI实现的,DI就是依赖注入:和IOC是同一个概念的不同角度的描述,即依赖IOC容器来动态注入对象需要的资源。
AOP面向切面:作为面向对象的一种补充,用于将那些与业务无关,却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”,可以减少系统的重复代码,降低模块间耦合度,可用于权限,日志,事务处理。在需要进行增强的方法上采用AOP的五大通知:前置,环绕,后置,最终,异常来添加与业务无关的逻辑,以实现增强。
在后台记录请求接口使用AOP:使用环绕通知+切点表达式(找到记录请求接口的方法),通过环绕通知方法的参数来获取请求接口的信息
Spring主要由以下几个模块组成:
-
Spring Core:核心类库,提供IOC服务
-
Spring Context:提供框架式的Bean访问方式,以及企业级功能如定时任务。
-
Spring AOP:提供AOP服务
-
Spring DAO:对jdbc的抽象
-
Spring ORM:对现有ORM框架的支持
-
Spring Web:提供了面向Web的特性,如文件上传
-
Spring MVC:提供面向web应用的MVC实现
Spring的优点
-
spring属于低侵入式设计,代码污染度低
-
spring的DI机制将对象之间的依赖关系交由框架处理降低组件之间的耦合
-
提供了aop技术,支持将一些通用任务如日志,事务,权限进行集中管理,提供复用
-
对主流框架提供很好的集成
OOP和AOP
OOP面向对象,具有继承,封装,多态三大特性。允许开发者定义纵向的关系(如通过继承来构建层次关系),但并不适用于定义横向的关系:会导致大量代码重复,不利于各个模块的重用
AOP面向切面:作为面向对象的一种补充,用于将那些与业务无关,却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”,可以减少系统的重复代码,降低模块间耦合度,可用于权限,日志,事务处理。在需要进行增强的方法上才用哪个AOP的五大通知:前置,环绕,后置,最终,异常来添加与业务无关的逻辑,以实现增强。
AOP实现的关键是代理模式,分为静态代理和动态代理。
-
静态代理(AspectJ)就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,会在编译阶段将切面织入到java字节码中,运行的时候就是增强后的AOP对象。
-
动态代理分为 JDK动态代理 和CGLib动态代理,动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理
Spring的IOC理解
IOC表示控制反转,也就是把原来new对象的控制权交给了容器,Ioc的一个重点就是在系统运行中,动态的向某个对象提供它所需要的资源。这一点是通过DI实现的,DI就是依赖注入:和IOC是同一个概念的不同角度的描述,即依赖IOC容器来动态注入对象需要的资源。
IOC的注入方式有3种:setter注入,构造器注入,注解注入
IOC使得各个组件保持松散的耦合,AOP使得可以将与业务无关的遍布应用各层的功能分离出来形成可重用的功能组件
BeanFactory 和 ApplicationContext 有什么区别
-
BeanFactory是Spring里面最底层的接口,ApplicationContext是BeanFactory的派生类,除了提供BeanFactory的功能,还继承了诸如EnvironmentCapable、MessageSource、ApplicationEventPublisher接口,提供获取系统环境变量,国际化,事件发布,资源加载等功能。
-
BeanFactory是延迟加载注入Bean,在调用getBean()才实例化,ApplicationContext是在容器启动时,一次性创建所有的Bean
Spring Bean的生命周期
首先说一下servlet的生命周期:实例化,初始int,接受请求service,销毁destroy
Spring Bean的生命周期:
-
实例化bean:对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,容器会调用 getbean进行实例化。对于ApplicationContext容器,容器启动时一次性创建所有的bean实例
-
设置对象属性(依赖注入):实例化后的对象被封装在BeanWrapper对象中,SPring根据BeanDefinition中的信息以及BeanWrapper提供的接口完成依赖注入
-
处理Aware接口:Spring会检测该对象是否实现了xxxAware接口
-
如果这个Bean实现了 BeanNameAware 接口,就要重写 setBeanName(String beanId) 方法,可以拿到beanname
-
如果实现了 BeanFactoryAware 接口,要重写它实现的setBeanFactory()方法,拿到spring工厂
-
如果实现了 ApplicationContextAware 接口,重写setApplicationContext(ApplicationContext)方法,可以拿到Spring上下文
-
-
postProcessBeforeInitialization接口:如果Bean实现了 BeanPostProcessor接口,将会调用 postProcessBeforeInitialization(Object obj, String s) 方法,
-
init-method:如果Bean在Spring配置文件中配置了 init-method 属性会自动调用其配置的初始化方法
-
postProcessAfterInitialization:如果Bean实现了 BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法
-
DisposableBean:如果Bean实现了 DisposableBean 接口,则会调用其实现的destory()方法
-
destroy-method:最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会
自动调用其配置的销毁方法。(如果既实现了DisposableBean接口,又配置了自定义销毁方法(destroy-method),则会先执行自定义销毁方法,再执行 DisposableBean 的 destory()方法
spring循环依赖问题
循环依赖就是两个或两个以上的bean互相持有对方,比如A依赖B,B依赖A。
spring允许循环依赖,依据三级缓存解决大部分的循环依赖的问题
-
一级缓存:缓存初始化完成的Bean对象
-
二级缓存:缓存早期的Bean对象,就是生命周期没走完的Bean
-
三级缓存:缓存ObjectFactory,表示对象工厂,用来创建某个代理对象
一二级缓存能解决一般对象的循环依赖,代理对象需要加上三级缓存。
如果是构造方法出现了循环依赖的问题,由于构造函数在bean生命周期中是第一个执行的,所以spring无法解决,可以通过@Lazy注解加在构造函数上来解决
Spring容器启动流程
-
首先会进行扫描,扫描得到所有的BeanDefinition对象,并存在一个Map中
-
然后筛选出非懒加载的单例Bean并创建,对于多例Bean不需要在启动过程中去创建,而是会在每次获取Bean时利用BeanDefinition去创建
-
创建Bean的过程也就是Bean的生命周期
-
Bean创建完了后,Spring会发布一个容器启动事件
-
此刻spring启动结束
Spring Bean的作用域
-
singleton:单例模式,默认,Spring IOC 容器中只会存在一个共享的Bean实例,在多线程下是不安全的。
-
prototype:多例,为每一个bean提供一个实例
-
request:为每一个请求创建一个实例,请求完成后会失效并被垃圾回收
-
session:与request类似,确保每个session中有一个bean实例,session过期后,bean将被销毁
-
global session:在一个全局的Http Session中,容器会返回该 Bean 的同一个实例,仅在使用 portlet context 时有效。(是用于在基于 Portlet 的 Web 应用程序中管理和访问各个 Portlet 的上下文信息的对象。)
Spring如何处理线程并发问题
Spring中,Bean默认是Singleton作用域,同时大部分bean是无状态的,所以不会有线程安全问题,但如果bean是有状态的,那么在并发情况下会有线程安全问题,可以通过设置bean为prototype作用域,或者是采用 ThreadLocal ,ThreadLocal 为每个线程提供独立的变量副本,将非线程安全的变量放入ThreadLocal中来保证线程安全
无状态:表示bean属性不会发生变化,不能保存数据,是不变的类。比如: controller、service、dao。 有状态: 表示bean的属性是可以发生变化的,可以保存数据,是线程不安全的,比如: pojo。
Spring的自动装配,
xml配置共有5种:
-
no:默认的方式,不进行自动装配,通过手工设置ref属性来进行装配bean
-
byName:通过bean的名称进行自动装配
-
byType:根据参数类型
-
constructor:利用构造函数进行装配,并且构造函数的参数通过byType自动装配
-
autodetect:自动探测,如果有构造方法则通过constructor自动装配,否则通过byType自动装配
基于注解的方式:
-
@Autowire:可用于构造函数.成员变量.Setter 方法,按照类型自动装配,默认情况下它要求依赖对象必须存在(可以设置它 required 属性为 false)。
-
@Resource:跟@Autowire类似,默认按照名称自动装配,也可以设置为按照类型
-
@Inject:与@Autowire类似,默认按照类型自动装配,
-
@Qualifier:如果有多个相同类型的 bean,就需要通过
@Qualifier
注解来指定具体要使用的 bean。必须搭配上面三个一起使用
Spring框架中用到了哪些设计模式
(1)工厂模式:BeanFactory 就是简单工厂模式的体现,用来创建对象的实例;
(2)单例模式:Bean 默认为单例模式。
(3)代理模式:Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成技术;
(4)观察者模式:ApplicationListener事件监听机制
(5)责任链模式:BeanPostProcessor
Spring事务的实现方式
-
编程式事务:通过代码显示的commit, rollback
-
声明式事务:常用的是声明式事务,就是用注解@Transactional,声明式事务本质上是通过Aop功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始前加入事务,在执行完方法后根据执行情况提交或回滚。声明式事务优于编程式事务,因为不需要在业务代码中参杂事务的代码,但声明式事务最细粒度只能做到方法级别,无法像编程式事务做到代码块级别
spring事务什么时候会失效
Spring事务的原理是AOP,进行了切面增强,事务失效就说明是AOP不起作用了,常见情况有:
-
发生自调用,同一个类中使用this调用本类的方法(this通常省略),此时这个this指向的对象不是代理对象,而是目标对象本身,也就是绕过了代理对象,解决方法:
-
将被调用的事务方法放到另一个类,通过依赖注入的方式使用代理对象进行方法调用
-
[事务] Transactional注解在同一个类中调用的失效问题_同一个类中方法调用@transactional失效_fastjson_的博客-CSDN博客
-
-
方法不是public,如果要用在非public方法上,可以开启AspectJ代理模式
-
数据库不支持事务
-
没有被spring管理(比如由new创建的对象)
-
捕获异常但没有抛出(throw new RuntimeException)
-
没有设置事务的回滚策略:spring默认只会回滚运行时异常,如果是抛出的是被检查异常,需要设置回滚策略
Spring的事务传播行为
spring的事务传播行为说的是,当多个事务同时存在时,spring如何处理这些事务的行为
-
REQUIRED:默认,如果当前没有事务,就创建一个新的事务,若当前存在事务,则加入该事务
-
SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果不存在,则以非事务执行
-
MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在,就抛出异常
-
REQUIRES_NEW:无论当前有无事务,都创建新事务
-
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起当前事务
-
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
-
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按 REQUIRED 属性执行
隔离级别
① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
② ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。
③ ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。
④ ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。
⑤ ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。
AOP的几个名词
-
切面(Aspect):被抽取的公共模块
-
连接点(joint point):指方法,一个连接点代表一个方法的执行
-
通知(advice):在某个特点的连接点上执行的动作。通知有各种类型,包括“around”,"before",“after”等
-
切入点(pointcut):指我们要对哪些连接点进行拦截,通过切入点表达式,指定拦截的方法,比如指定拦截 add*,search*
-
织入(Weaving):通过织入切面逻辑到目标对象的方法中,创建一个新的代理对象。这个代理对象包含了目标对象的原始功能,并且织入了额外的切面逻辑,以实现对目标对象的增强或修改。
AOP通知的类型
-
前置通知(Before advice):在目标连接点执行之前执行的通知
-
后置通知(After advice):在目标连接点执行之后执行的通知。
-
返回后通知(After returning advice):在目标连接点成功执行并返回结果后执行的通知。
-
异常通知(After throwing advice):在目标连接点抛出异常时执行的通知。
-
环绕通知(Around advice):在目标方法执行前后都执行的通知,是最常用的通知
当方法符合切点规则不符合环绕通知的规则时候
-
执行的顺序如下:@Before-@After-@AfterRunning(如果有异常一@AfterThrowing)
-
当方法符合切点规则并且符合环绕通知的规则时候,执行的顺序如下:@Around-@Before-@Around-@After执行 ProceedinaJoinPointproceed) 之后的操作-@AfterRunning(如果有异常@AfterThrowing)
Mybatis
什么是Mybatis
Mybatis是一个半ORM(对象关系映射)框架,它内部封装了jdbc,开发者只需关注sql语句本身,sql语句写在xml文件中,与业务代码解耦,程序员直接编写原生sql,可以严格控制sql执行性能,还可以使用XML或注解来配置和映射pojo,灵活度高。
Mybatis的优点
-
基于SQL语句编程,Sql语句写在xml中,与业务代码解耦,便于统一管理;提供标签,支持编写动态Sql语句,并且可以重用
-
减少jdbc的大量繁杂代码
-
很好的与各种数据库兼容,因为Mybatis使用jdbc连接数据库,所以只要是jdbc支持的数据库mybatis也支持
-
与spring很好的集成
-
提供映射标签,支持对象与数据库的ORM字段关系映射
缺点:
-
sql语句编写工作量大,当字段多,关联多时很考验sql功底
-
Sql语句依赖于数据库,所以不能随意切换数据库
Mybatis的执行流程
-
通过核心配置文件
mybatis-config.xml
加载环境配置和映射文件 -
构建会话工厂,全局单例
-
通过工厂创建
SqlSession
,每次操作都会创建一个会话,包含了执行Sql语句的所有方法 -
操作Executor执行器(封装了jdbc的操作),这是真正操作数据库的接口,也负责缓存的维护
-
Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息
-
输入参数映射
-
输出结果映射
Mybatis和Hibernate有哪些不同
-
mybatis是半orm框架,需要自己编写sql语句,hibernate是个全orm框架,基本不需要自己编写sql语句,开发人员只需定义实体类和相应的映射关系即可。
-
mybatis直接编写sql语句,SQL语句依赖数据库,不能随意切换数据库,hibernate对象关系映射能力强,数据库无关性好
#{}和${}
#{}是预编译处理,${}是字符串替换
mybatis在处理#{}时,会将sql中的#{}替换为?号,调用preparedStatement的set方法赋值
最好使用#{},可以有效预防sql注入问题
Mybatis是如何将 SQL 执行结果封装为目标对象并返回的?都有哪些映射形式?
-
resultMap标签,需要手动逐一定义数据库列名和对象属性名之间的映射
-
resultType标签,指定返回结果类型,一般是一个java对象,如果列名和属性名不一致,可以在sql语句中用as关键字将数据库列名写成对象属性名
Mybatis 动态 SQL
可以在xml映射文件中,用标签的形式编写动态sql,常用的动态sql标签有9种:trim、where、set、foreach、if、choose、when、otherwise、bind
if:满足条件才会执行
choose:相当于java中switch语句中的switch
when:相当于switch语句中的case
otherwise:相当于switch语句中的default
where:在至少满足一个if条件时才会插入WHERE关键字,而且,where标签会根据语法决定是否保留AND和OR
set:用于解决update语句存在的符号问题,会消除无关的逗号
trim:用于处理字符串的拼接,可以去除多余的逗号、AND 和 OR 等冗余字符。
foreach:遍历集合,它包含这些属性:collection, item, index, separator(在迭代后加上指定字符)
bind:可以用于模糊查询
Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?
不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复;
MyBatis 实现一对一和一对多
一对一:在 <resultMap> 标签内配置 <association> 标签
一对多:在 <resultMap> 标签内配置 <collection> 标签
Mybatis 是否支持延迟加载?
Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在Mybatis 核心配置文件 mybatis-config.xml 中,可以配置是否启用全局延迟加载 lazyLoadingEnabled=true|false,
也可以在mapper文件中开启局部延迟加载 fetchType=lazy
延迟加载的原理
-
当执行查询语句时,MyBatis并不立即加载关联对象的数据,而是生成一个代理对象,代理对象中包含了关联对象的引用和相关的属性。
-
当访问代理对象中的关联对象属性时,MyBatis会判断是否需要进行延迟加载。
-
如果需要延迟加载,则会执行查询语句,获取关联对象的数据,并将数据填充到代理对象中的属性上。
-
通过代理对象,可以访问到关联对象的数据。
Mybatis一二级缓存
一级缓存:SqlSession级别的缓存,不同的 SqlSession 之间的缓存互相独立,默认开启且不能关闭
二级缓存:namespaces和mapper级别的缓存,需要手动开启,二级缓存被多个SqlSession共享,同时属性类需要实现序列化接口,用于将对象转换为字节流以便于存储或传输
当一级缓存/二级缓存进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
SOA 微服务 分布式
SOA:面向服务的体系结构(Service-Oriented Architecture)
SOA架构是一种面向服务的架构,系统的所有服务都注册在总线上,当调用服务时,从总线上查找服务信息,然后调用
微服务架构
微服务是一种更彻底的面向服务的架构,将系统中各个功能个体抽成一个个小的应用程序,基本保持一个应用对应一个服务的架构
分布式架构
分布式架构是指将单体架构中的各个部分拆分,然后部署不同的机器或进程中去,SOA和微服务基本上都是分布式架构的
SOA 与微服务的关系
SOA 即面向服务的架构,SOA 是根据企业服务总线(ESB)模式来整合集成大量单一庞大的系统。微服务可以说是 SOA 的一种实现,将复杂的业务组件化,但它比 ESB 实现的 SOA 更加的轻便敏捷和简单。
CAP和BASE
CAP:(一致性,可用性,分区容错性)
-
分布式系统节点通过网络连接,一定会出现分区问题(P)
-
当分区出现时,系统的一致性(C) 和可用性(A)就无法同时满足
BASE理论:
-
基本可用 BA
-
软状态 S:在一定时间内,允许出现中间状态,比如临时的不一致
-
最终一致 E
Springboot
Springboot使用“习惯优于配置”的理念,整合了很多框架,不需要手动写一大堆xml配置,可以快速的完成spring和其他框架的构建整合,进一步提升开发效率
内嵌了tomcat,不需要额外配置
可以通过mvn clean package打包成一个jar包后,直接通过java -jar 运行springboot项目
springboot常用配置
@SpringbootApplication注解:标识这是springboot工程,这个注解包含了三个注解
-
@SpringbootConfiguration:跟@Configuration一样,表示这也是个配置类
-
@EnableAutoConfiguration:开启自动配置
这个注解内有个 @import 注解,会读取META-INF/spring.factories 中springboot提供的多个自动配置类,根据条件注解决定是否导入到容器中,比如RedisAutoConfiguration,RedisAutoConfiguration类上方标识了一些注解有:
// 标识配置类 @Configuration(proxyBeanMethods = false ) // 判断是否有redis字节码,也就是当导入redis启动器依赖后,就会加载RedisAutoConfiguration并将redis配置类中的bean全部放入springboot容器中 @ConditionalOnClass({RedisOperations.class})
又因为RedisAutoConfiguration类内部注入了 RedisTemplate ,所以当我们导入redis相关依赖后就可以直接使用RedisTemplate
同时redistemplate这个Bean上还有个注解 @ConditionalOnMissingBean,用于判断容器中是否存在这个bean,如果有则不用再加载。
-
@ComponentScan:标识扫描路径
springboot如何启动tomcat
-
首先springboot在启动时会创建一个spring容器
-
在创建spring容器过程中,利用@ConditionalOnClass注解判断是否存在tomcat依赖,因为springboot内嵌了tomcat,所以存在并会生成一个启动tomcat的bean
-
spring容器创建完成后,会获取启动tomcat的bean,并创建bean对象,绑定端口等,然后启动tomcat
springboot配置文件加载顺序
优先级从高到低,高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置 。
-
命令行参数。
java -jar springbootdemo-0.0.1-SNAPSHOT.jar --server.port=8081 --server.servlet.context-path=/bcb
-
Java系统属性 System.setProperty();
-
操作系统环境变量 System.setEnv();
-
jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
-
jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件,再来加载不带profile
-
jar包外部的application.properties或application.yml(不带spring.profile)配置文件
-
jar包内部的application.properties或application.yml(不带spring.profile)配置文件
-
@Configuration注解类上的@PropertySource:指定外部属性文件的位置
SpringCloud
spring Cloud是基于Spring框架的一套用于构建分布式系统和微服务架构的开发工具集合。它提供了一系列的组件和模块,比如Eureka,Ribbon, Feign, Hystrix, Zuul, nacos
SpringCloud和Dubbo的区别
Springcloud是一个微服务框架,提供了很多功能组件,Dubbo是一个RPC的分布式框架,核心是解决服务间调用的问题,SpringCloud更全面,Dubbo则更侧重于服务调用
RPC是什么
远程过程调用, 是一种用于实现分布式系统中不同节点之间通信的协议,它使得在网络上的不同计算机之间可以像调用本地方法一样进行方法调用,可以基于http协议,也可以直接基于TCP的连接,Dubbo是一个RPC框架
分布式事务
分布式事务就是一次业务处理可能需要不同服务来实现,比如用户发送一次下单请求,会设计到库存服务,如果没有分布式事务,就可能出现订单创建成功,库存却没有减少,解决方案有:
-
本地消息表:在数据库中创建一个表,创建订单时,将减库存消息加入本地消息表中,一起提交到数据库,然后调用库存服务,如果成功则修改本地消息表中的记录为成功,如果失败,则由定时任务从本地消息表中取出未成功的消息,重新调用库存系统
-
Seata:分布式事务框架
服务熔断、服务降级、服务雪崩、服务限流
-
服务雪崩:当服务器A调用服务器B,B调用C,此时大量请求访问A,假设A扛得住,但是C扛不住,就会导致B堆积了很多请求,最终导致A也扛不住,解决方法就是熔断和降级
-
服务降级:当发现系统压力过载时,为了确保服务不会受请求突增导致服务崩溃,通过histrix+feign实现,在feign接口编写fallback的降级逻辑来减轻服务压力。也就是如果该接口因压力过大无法访问,就会走fallback的逻辑。
-
服务熔断:默认关闭,需要手动打开,如果检测到 10 秒内请求的失败率超过 50%,就触发熔断机制。之后每隔5 秒重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常请求
-
服务限流:在高并发情况下,为了保护系统,对请求进行数量限制,防止系统不被大量请求压垮,在秒杀中,限流很重要
Nacos和Eureka的区别
共同点:都用作注册中心
-
都支持服务注册和服务发现
-
都支持服务提供者心跳检测
区别:
-
nacos可以设置是否为临时实例模式(通过ephemeral:true/false),默认是临时实例模式,临时实例采用心跳检测,非临时模式采用服务端主动检测模式
-
临时实例心跳不正常会被剔除,非临时不会。
-
当服务提供者列表更新,nacos会及时推送更新消息给消费者
-
nacos集群默认是AP(高可用)模式,当集群中存在非临时实例时,采用CP(强一致)模式。eureka只支持AP模式
-
nacos还有配置中心的功能
Redis
Redis是什么
redis是一个key-value类型的非关系型数据库,redis可以存储String, hash,list,set,Sorted Set,redis的数据都是存在内存中的,所以读写速度非常快,但是如果电脑关机会导致数据丢失,所以redis也提供了持久化策略RDB和AOF。也经常用来做分布式锁
String:value可以是String也可以是数字
hash:value是一个结构化的对象,存储键值对,在做购物车时用到了,userid作为key,购物车id作为value的字段,商品作为value的值
list:既可以作栈也可以做队列,可以做简单的消息队列的功能。
set:存放不重复值的元素,可以进行交集并集差集等操作。可以实现去重,共同关注,朋友圈点赞的功能
sorted set:多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用
为什么是单线程的
-
代码更清晰,处理逻辑更方便
-
避免不必要的上下文切换
-
不存在多线程导致的切换而消耗 CPU
-
不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
redis持久化方式
RDB(Redis DataBase):设置指定时间间隔将redis存储的数据生成快照写入磁盘,可以手动save执行RDB,也可以bgsave开启子进程执行RDB,RDB内部通过bgsave自动间隔执行RDB。适合可以容忍数分钟的数据丢失,追求更高的启动速度
AOF(Append Only File):默认关闭,将redis执行过的所有写操作记录到AOF文件,在下次redis重启时,将这些写操作从头到尾执行一遍,就可以恢复数据了。通过配置redis.conf可以设置AOF记录命令的频率:always(同步刷新),everysec(每秒),no(操作系统控制),适合对数据完整性要求高
数据过期策略
-
惰性删除:访问key的时候判断是否删除,如果过期则删除
-
定期删除:定期检查一定量的key是否过期,有SLOW模式和FAST模式,SLOW模式是个定时任务,默认10hz(1秒10次);FAST模式执行频率不固定,两次清理时间不超过2ms
Reids两种配合使用
缓存数据一致性
-
失效模式,修改数据库时顺带删除缓存,或者先删除再修改,下次直接从数据库获取最新的,不过会有问题脏数据
-
请求A进行写操作,删除缓存
-
请求B查询缓存发现没有就查数据库的旧数据
-
请求B将旧值写入缓存
-
请求A将新值写入数据库
-
-
延迟双删:先删除缓存,然后更新数据库,延迟比如100毫秒,再次删除缓存,但是依旧有脏数据的风险,因为延时的时间不好把控
-
读写锁,redissonClient.getReadWriteLock(),强一致,性能低
-
通过MQ异步通知:修改数据后异步发布消息给MQ,缓存服务通过监听MQ来更新缓存。这个方法的可靠性取决于MQ的可靠性
redis实现分布式锁
原生redis分布式锁:
-
通过setnx("lock",uuid,10s)来获取锁,
-
获取到锁
-
执行业务
-
因为要保证get和del操作的原子性,所以通过lua脚本删锁。
-
-
没获取到锁
-
重试
-
public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithRedisLock() {String uuid = UUID.randomUUID().toString();// 如果业务执行时间过长,可能导致锁的自动释放,所以使用uuid作为唯一标识防止被其他线程误删锁 Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);if (lock) {Map<String, List<Catelog2Vo>> dataFromDb = null;try {// 加锁成功 执行业务dataFromDb = getDataFromDb();} finally {// 因为要保证get和del操作的原子性,所以通过lua脚本删锁。// 原子删锁String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);}return dataFromDb;}else {// 如果当前已有锁,则调用自己,重新加锁,相当于synchronized()自旋的方式return getCatelogJson();} }
redisson分布式锁:
RLock rLock = redissonClient.getLock(lockName); try {boolean isLocked = rLock.tryLock(expireTime, TimeUnit.MILLISECONDS);if (isLocked) {// TODO} catch (Exception e) {rLock.unlock();} }
redisson实现分布式锁主要利用setnx命令,只需要一个RLock,RLock是redisson的最核心接口,通过 redissonclient.getLock() 获取一个RLock对象,然后通过RLock实现加锁和解锁。Redisson实现加锁的原子性也是依赖lua脚本,加锁解锁时设置了计数器,通过计数器来实现可重入锁特性。常用的加锁有lock()和trylock(),前者是阻塞式加锁,后者是非阻塞式加锁,有返回值,可以根据返回值做判断。
加锁可以设置过期时间,如果没设置则默认30s过期,主要是为了防止死锁。虽然设置了默认过期时间,但如果程序执行时间超过了30s,此时锁过期释放,其他线程就有可能加锁成功,这样就会有线程安全问题,所以redisson实现了一个看门狗机制,是用来自动延长加锁时间的。看门狗机制本质上是一个定时任务,当没有设置过期时间,客户端会创建一个定时任务来延长加锁时间,默认每10s执行一次。不过如果没有设置过期时间,并且你的程序没有主动释放锁,这样是会造成死锁的,因为看门狗会不断延长过期时间。
redis主从复制的核心原理
全量同步
-
从节点请求主节点同步数据(包括 replication id、offset)
-
主节点判断是否第一次请求,是的话就跟从节点同步版本信息(包括replication id、 offset)
-
主节点执行 bgsave,生成rdb文件后,发送给从节点去执行
-
在rdb生成执行期间,主节点会将在这期间的写请求以命令的方式记录到缓冲区
-
最后,rdb完成后,再把刚才记录的命令文件日志发送给从节点同步
增量同步
-
从节点请求主节点同步数据,主节点判断不是第一次请求,就获取从节点的offset值
-
主节点从命令文件日志中获取offset值之后的数据,发送数据给从节点同步
怎么保证redis的高并发高可用
通过哨兵机制,哨兵作用如下:
-
监控:哨兵会不断检查master和slave结点是否按预期进行
哨兵会每隔一秒向集群发送ping命令,如果某个哨兵发现某个节点没有及时响应,则认为该节点 主观下线 ,如果超过指定数量的哨兵都认为该节点 主观下线, 则判定为 客观下线
-
自动故障回复:如果master故障,哨兵会将一个slave提升为master,故障回复也以新的master为主,选主规则:
-
首先判断主从节点断开时间长短,排除断开时间超过指定值的slave
-
然后判断从节点的 slave-prority ,值越小优先级越高
-
如果 slave-prority 一样,则判断 offset 值, 值越大优先级越高,因为offset值越大表示跟主节点的数据越相似。
-
最后判断slave节点的运行id大小,越小优先级越高
-
-
通知:集群发生故障时,哨兵会将最新消息推送给客户端,告诉客户端连接哪一个redis
redis集群脑变,怎么解决。
由于主从节点和哨兵处于不同的网络分区,由于网络时延或者其他原因导致哨兵无法感知到主节点的心跳,导致选举推选一个从节点为master,这样就存在了两个master,这样会导致客户端依旧往旧的master写数据,当网络正常后,哨兵会将旧的master降为从节点,再从新的master同步数据,就会导致之前的数据丢失
解决方法:修改配置,设置最少存在的slave节点数量以及缩短主从同步的延迟时间
redis为什么单线程也这么快
-
基于内存操作
-
不需要线程上下文切换,也不需要担心线程安全问题造成锁的开销
-
采用io多路复用,非阻塞Io
redis网络模型
采用io多路复用+事件的处理器,比如连接应答处理器(接收请求),命令回复处理器(响应结果),命令请求处理器(转化命令+执行语句),在redis6中,命令回复处理器采用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,但是在命令执行的时候依旧是单线程
Linux
常用命令
ls: 列出文件和子目录
cd:切换到指定目录
pwd:显示当前所在目录的路径
cat:显示文件内容
touch:创建新文件或更新文件的时间戳
cp:复制
mv:移动,或重命名
rm:删除文件或目录
mkdir:用于创建新目录
rmdir:删除空目录
grep:在文件中查找匹配的字符串或模式
find:查找文件
top:显示当前运行的进程和系统资源的使用情况
ps:显示当前运行的进程信息
ping:测试网络连通
nginx
常见配置项
http块:
-
配置server块:
1.1 listen字段:监听端口
1.2 server_name字段:服务名
1.3 location字段:定义url匹配规则和相应的处理方式如
1.3.1 proxy_pass:反向代理到指定的服务器
1.3.2 rewrite:重写url
1.3.3 expires:设置静态资源的过期时间
-
upstream块:用于负载均衡,可以将location块中的proxy_pass代理的服务器负载均衡,分流缓解压力
-
proxy_cache_path :设置图片缓存路径,然后需要在location块中配置
proxy_cache image_cache;
负载均衡策略
-
轮询法:按请求顺序轮流分配
-
随机法:随机选取一台机器
-
哈希法:根据客户端的ip地址,通过哈希算法得到一个数值,用该数值对服务器列表大小进行取模运行,得到的结果就是选取的服务器的序号
-
加权轮询法:为机器配置权重,根据权重和请求顺序分配
-
加权随机法:按照权重随机分配
-
最小连接数法:根据后端服务器当前的连接情况,动态地选取其中积压连接数最少的一台服务器
git
什么是git?为什么要使用git?
git是一个免费的开源的分布式版本控制系统;为了保留之前开发的版本,方便回滚和修改
集中化版本控制系统和分布式版本控制系统的区别
集中化的有svn,当中央服务器发生故障,故障期间谁都无法提交更新,git是分布式,git是把代码仓库完整地镜像到本地,即使中央服务器故障,也可以将代码先提交到本地仓库,等恢复了再提交中央仓库
git命令
-
git init 初始化
-
git add ./filename 添加所有/文件名
-
git commit -m 提交,-m可以添加提交信息
-
git status 查看工作区状况
-
git log 查看提交历史
-
git reset 回滚
-
git branch 查看分支
-
git branch 分支名称 :创建分支
-
git checkout 分支名称:切换分支
-
git merge 要合并的分支名称:(注意要先切换到目标分支再合并)
-
git branch -d 分支名称:删除分支
pull和fetch的区别
fetch只是把最新版本下载到本地,不会merge。
git pull origin dev=git fetch origin dev+git merge origin/dev; git fetch更保险一些,git pull操作更简单
git冲突
当多个开发者同时对同一文件的同一部分修改时,就可能产生git冲突
产生的情况:
-
合并分支时
-
拉取远程更新时
解决方法:
-
打开冲突文件
-
修改文件,同时删除冲突标记
-
git add 标记冲突解决
-
git commit
fastdfs
fastdfs是一个轻量级分布式文件系统
使用步骤:
-
安装和配置
安装好后需要配置tracker和storage的配置文件
-
启动tracker和storage
fastdfs分为tracker和storage两个角色,tracker负责文件上传和下载,storage负责文件存储,需要先启动tracker再启动storate
-
配置项目代码
在代码中配置fastdfs配置如tracker地址,端口等
-
实现操作
docker
常用命令
-
创建镜像:docker build -f xxx -t xx
-f:指定DockerFile文件的路径名
-t:镜像的名字及标签
-
查看镜像:docker images | grep 镜像名
-
创建一个新容器并运行:docker run -p 8089:8089 --name 镜像名 -d 镜像名:v0.1
-p:指定端口号 主机端口:容器端口
-d:后台运行
-
查看运行中的容器:docker ps | grep 镜像名
-
查看所有容器:docker ps -a
-
进入容器内部:docker exec -it 容器id /bin/bash
-
删除容器:docker rm -f 容器id
-f:强制删除
-
删除镜像:docker rmi -f 镜像名:版本号
相关文章:
【2023面试题大全,都是常问面试题】
JAVA基础 面向对象和面向过程的区别 面向过程:基于步骤的编程方式,用函数把这些步骤一步一步地实现,然后在使用的时候一一调用则可 面向对象:基于对象的编程方式,通过定义类来描述对象的属性和行为,面向对…...

Bun 1.0 正式发布,爆火的前端运行时,速度遥遥领先!
文章目录 一、包子1.0二、Bun 是一个一体化工具包为什么包子存在 二、Bun 是一个 JavaScript 运行时Node.js 兼容性速度TypeScript 和 JSX 支持ESM 和 CommonJS 兼容性网络 API热重载插件 一、包子1.0 Bun 1.0终于来了。 Bun 是一个快速、一体化的工具包,用于运行…...
getchar函数设置为非阻塞
一. 前言 我们在学习C语言的时候,getchar都是阻塞的,等待用户输入字符并且输入回车后才返回。但是有时候我们希望把getchar设置为非阻塞,或者说,当我们遇到getchar函数变成非阻塞的了,我们应该怎么解决这个问题&#x…...
【超算作业调度系统--LSF】
集群服务器--LSF作业调度系统使用 0 Introdutction1 命令1.1 bsub--作业提交命令1.1.1 $ bqueues --查看现有队列信息;1.1.2 $lsload --查看各节点运行情况1.1.3 $bhosts --查看各节点空闲情况1.1.4 $busers --查看用户信息1.2 bsub --提交作业1.2.1 bsub OMP_NUM_T…...
L1-011 A-B分数 20
本题要求你计算A−B。不过麻烦的是,A和B都是字符串 —— 即从字符串A中把字符串B所包含的字符全删掉,剩下的字符组成的就是字符串A−B。 输入格式: 输入在2行中先后给出字符串A和B。两字符串的长度都不超过104,并且保证每个字符…...
PHPword解析内容支撑
因有些功能不支持,所以新增了某些功能,以防后期变动不好变更,手动做个记录 将公式替换成指定的符号,读取到 html 后读取 xml 解析公式,根据标记符号进行替换 文件名PhpOffice\PhpWord\Shared\XMLReader.php public fun…...

回归预测 | MATLAB实现RUN-XGBoost龙格库塔优化极限梯度提升树多输入回归预测
回归预测 | MATLAB实现RUN-XGBoost多输入回归预测 目录 回归预测 | MATLAB实现RUN-XGBoost多输入回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现RUN-XGBoost多输入回归预测(完整源码和数据) 1.龙格库塔优化XGBoost,…...

LLM-TAP随笔——语言模型训练数据【深度学习】【PyTorch】【LLM】
文章目录 3、语言模型训练数据3.1、词元切分3.2、词元分析算法 3、语言模型训练数据 数据质量对模型影响非常大。 典型数据处理:质量过滤、冗余去除、隐私消除、词元切分等。 训练数据的构建时间、噪音或有害信息情况、数据重复率等因素都对模型性能有较大影响。训…...
Linux- open() lseek()
文件描述符 文件描述符(File Descriptor,简称 FD)是 UNIX 和 UNIX-like 系统中用于代表和识别打开的文件或其他I/O资源的一种抽象标识。它是一个非负整数,内部由操作系统进行管理和分配。文件描述符可以代表文件、套接字、管道等…...
Halcon Tuple相关算子(一)
(1) tuple_length( : : Tuple : Length) 功能:返回输入元组中元素的个数。 控制输入参数: Tuple:输入元组; 控制输出参数:length:输入元组中元素的个数。 (2) tuple_find( : : Tuple, ToFind : Indices…...

基于图像形态学处理的路面裂缝检测算法matlab仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ...................................................... %1:从文件夹中读取多个…...

PY32F003F18之窗口看门狗
一、PY32F003F18窗口看门狗特点: 即使窗口看门狗被禁止,窗口看门狗的"递减计数器"也会继续递减计数。 二、窗口看门狗复位的条件: 1、将"控制寄存器WWDG_CR"中的WDGA1,激活"窗口看门狗计数器等于0x3F"时,则产…...

SpingBoot:整合Mybatis-plus+Druid+mysql
SpingBoot:整合Mybatis-plusDruid 一、特别说明二、创建springboot新工程三、配置3.1 配置pom.xml文件3.2 配置数据源和durid连接池3.2.1 修改application.yml3.2.2 新增mybatis-config.xml 3.3 编写拦截器配置类 四、自动生成代码五、测试六、编写mapper.xml&#…...

计算机视觉与深度学习-经典网络解析-VGG-[北邮鲁鹏]
目录标题 VGG参考VGG网络贡献使用尺寸更小的$3 \times 3$卷积串联来获得更大的感受野放弃使用$11 \times 11$和$5 \times 5$这样的大尺寸卷积核深度更深、非线性更强,网络的参数也更少;去掉了AlexNet中的局部响应归一化层(LRN)层。 网络结构主要改进输入…...

入门级制作电子期刊的网站推荐
随着数字化时代的到来,越来越多的人开始尝试制作自己的电子期刊。如果你也是其中的一员,那么这篇文章可以帮助你制作电子期刊。无论是初学者还是有一定经验的制作者,都能快速完成高质量的电子期刊制作 小编经常使用的工具是-----FLBOOK在线制…...

软件测试内容整理
1. 软件测试 1.1. 定义 软件测试(英语:Software Testing),描述一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。换句话说,软件测试是一种实际输出与预期输出之间的审核或者比较过程。 软件测试的经典定…...

UniAccess Agent卸载
异常场景: UniAccess Agent导致系统中的好多设置打不开 例如:ipv4的协议,注册表,host等等 需要进行删除,亲测有效,及多家答案平凑的 借鉴了这位大神及他里面引用的大神的内容 https://blog.csdn.net/weixin_44476410/article/details/121605455 问题描述 这个进…...

【C++】C++11——构造、赋值使用条件和生成条件
移动构造和移动赋值生成条件移动构造和移动赋值调用逻辑强制生成默认函数的关键字default禁止生成默认函数的关键字delete 移动构造和移动赋值生成条件 C11中新增的移动构造函数和移动赋值函数的生成条件为: 移动构造函数的生成条件:没有自己实现的移动…...

【LeetCode热题100】--56.合并区间
56.合并区间 排序: 如果按照区间的左端点排序,那么在排完序的列表中,可以合并的区间一定是连续的,如下图所示,标记为蓝色、黄色和绿色的区间分别可以合并为一个大区间,它们在排完序的列表中是连续的 算法&a…...

opencv dnn模块 示例(17) 目标检测 object_detection 之 yolo v5
在前文【opencv dnn模块 示例(16) 目标检测 object_detection 之 yolov4】介绍的yolo v4后的2个月,Ultralytics发布了YOLOV5 的第一个正式版本,其性能与YOLO V4不相伯仲。 文章目录 1、Yolo v5 和 Yolo v4 的区别说明1.1、Data Augmentation - 数据增强1…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...

Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...

毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...
stm32进入Infinite_Loop原因(因为有系统中断函数未自定义实现)
这是系统中断服务程序的默认处理汇编函数,如果我们没有定义实现某个中断函数,那么当stm32产生了该中断时,就会默认跑这里来了,所以我们打开了什么中断,一定要记得实现对应的系统中断函数,否则会进来一直循环…...

C# WPF 左右布局实现学习笔记(1)
开发流程视频: https://www.youtube.com/watch?vCkHyDYeImjY&ab_channelC%23DesignPro Git源码: GitHub - CSharpDesignPro/Page-Navigation-using-MVVM: WPF - Page Navigation using MVVM 1. 新建工程 新建WPF应用(.NET Framework) 2.…...