Java后端面试
2023-02-06 20:31:08 0 举报
AI智能生成
Java后端面试:Java基础、Spring框架、Mybatis框架、分布式组件、微服务、数据库等,后续持续更新中。
作者其他创作
大纲/内容
Dubbo
Zookeeper
缓存
分布式
数据库
微服务架构
微服务架构和单系统架构的优劣势
单系统:整个系统是一个项目,所有的服务在一个项目中
微服务架构:将系统拆分为多个子服务独立运行
1. 非核心的单个子服务down掉不影响其他服务的运行
2. 项目管理更便利,代码提交更方便
3. 数据库也被相应的拆分,减轻数据库的压力,且易于扩展
4. 资源利用率更高,针对高压的服务分配更多的资源
5. 缺点:事务一致性问题(分布式事务)
6. 缺点2:增加运维成本
Java
名词对比
JDK、JRE和JVM的区别
JVM: Java虚拟机,实现了Java的跨平台
JRE: 包括JVM和Java的核心类库,例如java.lang等
JDK: 包括JRE和java开发工具,例如编译工具(javac.exe)、打包工具(jar.exe)、监控JVM的工具等
Java和c++的对比
1. 都是面向对象的语言,都支持封装、继承和多态
2. Java不提供指针直接访问内存,程序内存更加安全
3. Java的类是单继承,c++支持多重继承
4. Java有垃圾回收机制自动管理内存
Oracle JDK和OpenJDK的对比
1. OpenJDK完全开源,版本每三个月更新一次;OracleJDK是OpenJDK的一个实现,更加企业化
2. OracleJDK比OpenJDK更加稳定,提供更多的类和一些bug的修复
3. 在响应性和性能方面,OracleJDK相比于OpenJDK提供更好的性能
基础理论及语法
理论概念
面向对象的特性
抽象
将一类对象的公用特征总结出来构造类的过程,包括数据抽象和行为抽象两方面
封装
将一个对象的属性和方法私有化,同时提供一些可以被外界访问的属性和方法
继承
在已存在的类的基础上创建新的类,新类的定义可以增加新的属性和方法
多态
运行时多态
父类或接口的引用变量可以指向子类或具体实现类的实例对象,在程序运行时才确定该引用变量到底指向哪个类的实例对象
提高了程序的拓展性
编译时多态
主要指方法的重载,根据参数列表的不同来区分不同的函数,通过编译之后会变成不同的函数
面向对象五大基本原则
单一职责原则
类的功能要单一
开放封闭原则
一个模块对拓展开发,对修改封闭
里氏替换原则
子类可以替换父类出现在父类能够出现的任何地方
依赖倒置原则
具体实现应该依赖抽象,而抽象不应该依赖具体实现
接口分离原则
不同的功能拆分在不同的接口中
抽象类和接口的对比
都包含抽象方法,且都不能被实例化
抽象类是对类的抽象,接口是对行为的抽象
抽象类由子类extends继承,接口由实现类implements实现
一个类只能继承一个抽象类,可以实现多个接口
抽象类的方法可以用任意访问修饰符,接口方法默认public,并且不允许定义为private或protected
Java中定义一个不做事且没有参数的构造方法的作用
Java在执行子类的构造方法时,默认会调用父类中“没有参数的构造方法”。因此如果父类只定义了有参数的构造方法且在子类的构造方法里没有用super指定调用,则编译时会发生错误。
内部类
内部类的分类有哪些
静态内部类
定义在类内部的静态类,static关键字修饰
静态内部类可以访问外部类的静态变量,但不可以访问外部类的非静态变量
创建方式:new 外部类.静态内部类()
成员内部类
定义在类内部成员位置上的非静态类,无关键字修饰
可以访问外部类的所有数据
创建方式:外部类实例.new 内部类()
局部内部类
定义在类方法中的内部类
创建方式:在对应的方法中,new 内部类()
匿名内部类
没有名字的内部类,日常开发中使用的比较多
创建方式:new 类/接口 { //... }
创建规则
1. 必须继承一个抽象类或实现一个接口
2. 不能定义任何静态成员和方法
3. 当外部的变量需要被匿名内部类使用时,必须声明为final
扩展:为什么变量必须加上final
因为生命周期不一致,局部变量存在栈中,方法结束后非final的局部变量会被销毁,而匿名内部类对局部变量的引用仍然存在。如果匿名内部类调用非final的局部变量就会出错。加了final之后,局部变量存在于内存常量池中,不会随方法结束而被销毁。
4. 匿名内部类必须实现继承的抽象类或接口中的所有抽象方法
内部类的优点
可以访问创建它的外部类的所有内容,包括私有数据
内部类不为其他类所见,具有很好的封装性
内部类可以变相做到“多重继承”
重载(overload)和重写(override)的区别
重载:发生在同一个类中,方法名相同参数列表不同(参数类型或个数不同)
重写:发生在父子类中,方法名、参数列表全部相同
==和equals的区别
基本数据类型比较时,两者作用一样,都是比较其值是否相同
引用数据类型比较时,==比较的是其在栈内存的引用地址是否相同,equals比较的是其在堆内存的具体数值是否相同
hashCode与equals
hashCode介绍
获取哈希码(散列码),返回一个int整数,用于确定该对象在哈希表中的索引位置。
存储的是键值对,可以根据“键”快速检索出对应的值,快速找到所有需要的对象
为什么要有hashCode
以HashSet检查重复为例,会首先计算新对象的hashCode值,只有hashCode值相同时才会调用equals方法检查对象是否相同,这样可以大大减少equals的次数,提高执行速度。
为什么重写equals时必须重写hashCode方法
相关规定:如果两个对象相等,则其hashCode一定也是相同的
而如果不重写hashCode方法,该Class的两个对象的hashCode都不会相等。也就是说会出两个对象相等但其hashCode不同的情况
修饰符相关问题
访问修饰符public、protected、private及不写时(默认)的区别
private:仅同一个类可访问
默认(不写): 同一个包内可访问
protected:同一个包或所有子类可访问
public:所有类可访问
final的作用
用户修饰类、方法和变量
被修饰的类不可以被继承
被修饰的方法不可以被重写
被修饰的变量不可以被改变(不可变的是变量的引用,即栈内存地址)
final、finally及finalize的区别
final用于修饰类、方法和变量。修饰的类不可继承,修饰的方法不可重写,修饰的变量不可重新赋值
finally一般在处理异常时出现在try-catch代码块中,finally表示不管是否出现异常,该代码块的内容都会执行,一般用于关闭资源。
finalize是一个方法,由垃圾回收器调用。当我们调用System.gc()方法时,垃圾回收器会调用finalize()回收垃圾
this与super的区别
super引用当前对象的直接父类中的成员
this引用当前对象的成员
关键字static
作用
创建独立于具体对象的域变量或方法,以致于即使没有创建对象也能使用其属性和调用方法
其次,static代码块可以形成静态代码块以优化程序性能。因为static代码块只在类加载的时候执行一次,多用于初始化操作
应用场景
修饰成员变量、方法
修饰类(只能修饰内部类)
静态代码块
静态导包
注意事项
静态只能访问静态
非静态可以访问非静态,也可以访问静态的
break、continue及return
break结束当前循环体
扩展:Java中如何跳出当前的多重嵌套循环
continue结束正在执行的循环,进入下一个循环条件
return结束当前方法直接返回
IO流
Java中IO流分为几种
按照流的流向分
输入流
输出流
按照操作单元划分
字节流
字符流
按照流的角色分
节点流
处理流
IO流的主要基类
InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流
OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流
BIO、NIO、AIO的区别
BIO
Block IO同步阻塞式IO,即传统的IO,模式简单实用方便,并发处理能力低。
数据的读取写入必须阻塞在一个线程内等待其完成
NIO
Non IO同步非阻塞IO,传统IO的升级,客户端和服务端通过Channel通讯,实现多路复用
提供了Channel、Selector、Buffer等抽象,支持阻塞和非阻塞两种模式
AIO
Asynchronous IO是NIO的升级,实现异步非阻塞IO,操作基于事件和回调机制
基于事件和回调机制,应用操作后会直接返回,不会阻塞在那里,当后台处理完成,操作系统会通知相应的线程做后续的操作,目前应用还不是很广泛。
Files的常用方法
exists
createFile
createDirectory
delete
copy
move
read
write
反射机制
什么反射机制
在运行状态下,对于任意一个类,都能知道其所有属性和方法;对于任意一个对象,都能调用其任意属性和方法。
优缺点
优点:运行时可以动态 操作类,提高代码灵活性
缺点:反射相当于一些列的解释操作,性能比直接的java代码要慢很多
反射机制的应用场景
动态代理模式
Spring/Mybatis/Hibernate框架大量使用发射机制,通过配置/注解操作类
获取发射的三种方法
1. 通过new对象实现反射机制: obj.getClass()
2. 通过路径实现反射机制: Class.forName("com.reflect.Student")
3. 通过类名实现反射机制: Student.class
String相关
什么是字符串常量池
字符串常量池专门存储字符串,可以提高内存的使用率,避免开辟多块空间存储相同的字符串
String是基本数据类型吗
不是
String的特性
对象不可变
为什么是不可变的?
String类使用final修饰char类型数组存储字符
如何实现可变
反射机制直接修改String对象的value
常量池优化
String str = “i”与String str = new String("i")一样吗
不一样,前者分配到常量池中,后者分配在堆内存中
类不可被继承
在使用HashMap时,用String做key有什么好处
HashMap内部实现是通过key的hashCode值来确定value的存储位置,而字符串是不可变的,所以创建字符串时,它的hashCode被缓存下来不需要重新计算,所以相比于其他对象更快。
String、StringBuffer和StringBuilder的区别
String不可变
StringBuilder可变,非线程安全
StringBuffer可变,线程安全
包装类相关
自动装箱与拆箱
装箱:将基本类型用对应的引用类型包装起来
拆箱:将包装类转换成基本数据类型
int和Integer的区别
Integer是int的包装类
常量池中存储-128~127的整型,因此在此范围内装箱的等值Integer对象==返回true
异常相关
java异常架构
Throwable
Error
StackOverFlowError
OutOfMemoryError
NoClassDefFoundError
Exception
RuntimeException
NullPointException
IndexOfBoundsException
ArithmeticException
ClassCastEException
IOException
FileNotFoundException
Error和Exception的区别
Error通常是虚拟机相关错误,无法通过程序本身恢复
Exception是可以在程序运行时捕获并处理,使程序继续正常运行
检查异常和非检查异常
非检查异常:运行时异常,RuntimeException及其子类,编译器不会检查并不要求必须处理
检查异常:编译时异常,编译器会检查并要求代码必须处理或throws显性抛出
JVM如何处理异常
1. 方法中发生异常,创建异常对象交于JVM,该对象包含异常名称、描述及发生时程序的状态
2. JVM顺着调用栈查找是否有处理异常的代码,如果有则将异常传递给它,如果没有则由JVM默认的异常处理器处理,一般是打印异常信息并终止程序。
throw与throws
throw出现在方法内,用于抛出异常,只能一个
throws出现在方法声明上,用于声明该方法可能抛出的异常,可以多个
try-catch-finally,如果catch中return了,finally还会执行吗
会执行,在return之前执行
常见的异常
RuntimeException/非检查异常
NullPointException空指针异常
出现NPE的情况
数据库查询结果可能为null
远程调用返回的对象可能为null
遍历集合元素时可能为null
级联调用方法时可能为null,如obj.getA().getB().getC()
基本数据类型自动拆箱可能为null
ClassCastException类转换异常
IndexOutOfBoundsException数组越界
检查异常
IOException
FileNotFoundException
ClassNotFoundException
异常处理的最佳实践/规则手册
1. 清理资源操作在finally中处理或使用try with resource语句
2. 抛出throw或声明throws的异常要尽量明确
3. 优先捕获更具体的异常:捕获顺序从小到大
4. 异常会影响性能,因此
不要for循环内try-catch
通过if语句避免NPE
不要使用异常控制流程
5. 对异常进行文档说明-javadoc添加@throws声明
6. 不要捕获Throwable类,因为其包含程序无法处理的Error
7. 不要忽略异常:catch中记录异常的信息,即使很小的概率会走到catch
8. 不要在finally使用return
NoClassDefFoundError与ClassNotFoundException
NoClassDefFoundError由JVM引起,一般发生的情况是编译时该类存在,运行时却被删除了
ClassNotFoundException是一个检查异常,可以捕获并处理,一般发生在动态加载类时通过类路径未找到该类,或者该类已经有一个类加载器加载了而此时另一个加载器又尝试去加载它。
集合相关
Collection相关
集合架构
Collection
List
ArrayList
LinkedList
Vector
Set
HashSet
LinkedHashSet
TreeSet
Map
HashTable
Properties
HashMap
LinkedHashMap
TreeMap
集合的快速失败机制fail-fast
一种错误检查机制,当遍历集合时集合的结构发生变化,则会抛出CocurrentModificationException异常终止遍历
原因:迭代器在遍历过程中使用一个modCount变量,每次遍历前都会检查modCount的值,如果是期望值则继续遍历,否则终止遍历
解决方法:使用CopyOnWriteArrayList代替ArrayList
如何创建一个不被修改的集合
Collections.unmodifiableCollection,改变集合时会报错
如何边遍历边移除Collection中的元素
使用迭代器的remove()
List和Set的区别
List:有序,可重复,允许多null元素,检索效率高,插入删除效率低
Set:无序,不可重复,只允许一个null元素,检索效率低,插入删除效率高
List相关
如何实现数组和List之间的转换
数组转List
Arrays.asList(array)
List转数组
list.toArray()
ArrayList、LinkedList、Vector的区别
ArrayList:底层数据结构是数组,查询快插入慢
LinkedList:底层数据结构是链表,插入快查询慢
Vector:和ArrayList相似,区别是Vector线程安全而ArrayList线程不安全
多线程场景下如何使用ArrayList
Collections.synchronizedList将其转成线程安全的容器再使用
Set相关
HashSet的实现原理
基于HashMap实现,HashSet的值存放在HashMap的key上,value统一为present,因此HashSet的操作底层都是调用HashMap的相关方法来完成。
HashSet如何检查重复
add方法底层调用HashMap的put方法,先比较对象的hashCode值,如果相同在结合equals方法比较,都相同是用新的V覆盖旧的V而key不变。
HashSet、LinkedHashSet、TreeSet区别
HashSet:无序不可重复
LinkedSet:继承自HashSet,多维护一个字段记录插入顺序实现有序
TreeSet:有序的二叉树结构,检索的时间复杂度为O(logN)
Map相关
HashMap的实现原理
数据结构:HashMap是由数组+链表/红黑树组成,数组是HashMap的主题,链表则是主要味蕾解决哈希冲突而存在的,1.8之后引入红黑树结构,在链表大于8的时候转化成红黑树以减少搜索时间 O(N) --> O(logN)。
集合操作基于Hash算法实现
1. 当往HashMap中put元素时,利用key的hashCode计算出当前对象元素在数组中的下标
2. 存储数组元素时,如果出现hash值相同的key时,相同key会覆盖原始值,不同key(出现哈希冲突)则将当前对象元素放入链表/红黑树
3. get获取元素时,直接找到hash值对应的下标,再进一步判断key是否相同,从而找到对应的值
HashMap的put方法的具体流程
1. 根据key计算hash值得到插入的数组索引
hash函数的算法:高16位不变,低16位和高16位做异或操作,以减少碰撞
2. 如果数组索引位置的值为null,直接新建节点添加;如果有值,遍历检查key的值是否存在,已存在则覆盖原始值,不存在则执行链表插入或红黑树插入
3. 插入成功后,判断实际存在的键值对数量是否超过了最大容量threshold,如果超过执行扩容操作。
HashMap的扩容操作是怎么实现的
1. 每次扩容,都是原始容量的2倍
2. 根据元素的hash值重新分发位置
1.7扩容时需要重新计算元素hash值进行分发
1.8根据位置判断(e.hash & oldCap是否为0),该元素的位置要么停留在原始位置,要么移动到原偏移量两倍的位置
HashMap如何解决哈希冲突的
拉链法
HashMap在JDK1.7和1.8中有哪些不同
引入红黑树,链表长度大于8时转成红黑树,避免链表过长而影响查询效率
优化resize扩容算法
1.7扩容时需要重新计算元素hash值进行分发
1.8根据位置判断(e.hash & oldCap是否为0),该元素的位置要么停留在原始位置,要么移动到原偏移量两倍的位置
Map的key类型选择最佳实践
最好使用String、Integer这样的包装类作为key,因为其不可变,不可变的类可以确保hashCode和equals在未来不会改变,这样不会存在获取hash值不同的问题。其次不可变的对象hashCode值可以被缓存起来(JVM常量池具有缓存功能),拥有更好的性能。
如果使用Object作为HashMap的Key,应该怎么办
1. 重写hashCode因为需要计算数据的存储位置。注意不要试图在计算中排除掉对象的某些字段来提高性能,这样虽快但是会导致更多的哈希碰撞
2. 重写equals方法,需要遵守自反性、对称性、传递性、一致性以及equals(null)必须返回false的几个原则,目的是为了保证key的唯一性。
3. hashCode/equals不可被再次改变
HashMap为什么不直接使用hashCode直接作为table的下标
hashCode返回的是int整型,范围很大,而HashMap的容量范围很小(16~2^30),一般取不到int的最大值。
HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap区别
HashMap底层数据结构是数据+链表,put时节点位置下标是根据key的hash值计算的,所以遍历时的顺序和插入顺序是不一样的。
LinkedHashMap继承自HashMap,但是它会记录节点插入的顺序,使得遍历顺序和插入顺序是一样的
TreeMap是将节点按照其key值的顺序插入的。
ConcurrentHashMap分段锁实现线程安全,相比于HashTable的synchronized锁粒度更加精细,并发性能更好
ConcurrentHashMap底层是怎么实现线程安全的
1.7:将数据分成一段一段的,每一段数据分配一个Segment锁,数据修改时需要首先获取对应的Segment锁。当一个线程占有其中一段数据时,其他段的数据仍可被其他线程访问
1.8:放弃Segment的设计,采用Node+CAS+Synchronized保证并发,synchronized只锁定当前链表或红黑树的首节点,这样只要hash不冲突,就不会产生并发,效率更高。
TreeMap和TreeSet在排序中如何比较元素
TreeSet要求存放的对象所属类必须实现Comparable接口,该接口提供比较元素的compareTo方法,当插入元素时会回调该方法比较元素的大小。
TreeMap要求存放的键值必须实现Comparable接口,从而可以根据键值进行排序
工具类相关
Collection和Collections区别
Collection是集合类的顶级接口
Collections是集合类的工具类,提供一系列的静态方法,常用的如sort()
Comparable和Comparator区别
Comparable出自java.lang包,有一个compareTo(Object obj)的方法用于排序
Comparator出自java.util包,有compare(Object obj1, Object obj2)方法用于排序
Collections的sort方法如何比较元素
Collections的sort方法提供了两种重载的方式
Collections.sort(list):一种要求待排序容器中传入的对象需要实现Comparable接口
Collections.sort(list, comparator):另一种除了传入待排序容器外,需要额外传入比较器Comparator的实现类
并发多线程相关
基础知识
并发编程的三个必要因素
原子性:一个或多个操作要么全部执行成功要么全部执行失败
可见性:一个线程对共享变量的修改,另一个线程立即可见
有序性:程序按照代码的先后顺序执行
Java如何保证多线程的运行安全
线程切换带来的原子性问题:线程间使用同步锁(synchronized或者lock)
缓存导致的可见性问题:volatile解决可见性问题
编译优化带来的有序性问题:Happens-Before规则可以解决有序性问题
守护线程和用户线程
用户(User)线程:主线程,运行在前台执行具体的任务
守护(Dawmon)线程:运行在后台,为主线程服务,一旦所有用户线程结束运行守护线程会随之结束工作
线程死锁
线程1拥有锁A尝试获取锁B,而线程2拥有锁B尝试获取锁A
如何避免死锁?
避免一个线程同时获得多个锁
避免一个线程在每个锁内同时占用多个资源
尝试使用定时锁lock.tryLock(timeout)来代替内部锁
线程周期
新建状态
Thread t = new Thread(...)
就绪状态
t.start()
运行状态
当线程获取cpu时间片时,执行run方法
阻塞状态
等待阻塞:运行状态的线程执行wait方法,线程进入等待池中不竞争锁,等待notify/notifyAll之后进入锁池,重新尝试获取cpu时间片进入运行状态
同步阻塞:现成获取同步锁失败时,JVM会把线程放入锁池
其他阻塞:通过调用sleep或join或发出IO请求时进入阻塞,当sleep超时、join等待线程终止或超时、IO处理完毕时重新进入就绪状态
终止状态
现成run执行结束或因异常退出run方法时,线程结束生命周期
Java创建线程的四种方式
继承Thread类
实现Runnable接口
实现Callable接口
使用匿名内部类
Runnable和Callable的区别
都是线程的接口,通过thread.start()启动线程
Runnable中run()方法无返回值;Callable中call()有返回值,和Future、FutureTask配合可以用来获取异步执行的结果
Runnable的run方法只能抛出运行时异常且无法捕获处理;Callable的call允许抛出异常,可以获取异常信息
Callable支持返回执行结果,需要调用Future.get()得到,此方法会阻塞主进程继续执行,如果不调用不会阻塞
run()和start()
start用于启动线程使线程进入就绪状态,run是线程的一个普通方法调用,用于执行线程运行时的代码
sleep()和wait()
两者都可以暂停线程的执行
调用类不同:sleep是Thread的静态方法,wait是Object类的方法
是否释放锁:sleep()不释放锁,wait释放锁
用途不同:sleep通常用于暂停执行,wait通常用于线程间的交互/通信
用法不同:wait方法调用后,线程不会自动苏醒,需要其他线程调用同一个对象上的notify或notifyAll方法;sleep执行完成后,线程自动苏醒
为什么线程的wait、notify、notifyAll被定义在Object类里
因为Java所有的类都继承Object,java想让任何对象都可以成为锁,而在java的线程中没有可供所有对象使用的锁,所有任意对象都可以调用的方法需要定义在Object类中
sleep()和yield()
执行sleep线程进入阻塞状态,执行yield线程进入就绪状态
sleep方法申明抛出InterruptedException,yield方法没有申明异常
sleep方法比yield方法具有更好的移植性
sleep方法给其他线程运行机会时不考虑线程的优先级;yield方法只会给相同优先级或更高优先级的线程运行机会
为什么Thread类的sleep和yield方法是静态的
这两个方法只在当前正在运行的线程上运行,所以在其他处于等待线程上调用你这个方法没有意义
如何停止一个正在运行的线程
1. run方法完成时线程终止
2. stop方法强行终止,不推荐,stop/suspend/resume已经是过期的方法
3. interrupt方法中断线程
Java中怎么保证多线程的运行安全
1. 使用安全类,比如java.util.concurrent下的类,原子类AtomicInteger
2. 使用自动锁 synchronized
3. 使用手动锁 Lock
线程类的构造方法、静态块是被哪个线程调用的
线程类的构造方法、静态块是被new这个线程对象所在的线程调用,而run方法是被线程本身调用
并发理论
Java中线程之间如何通信
共享内存模型(Java内存模型JMM)
抽象的说,JMM定义了线程和主内存之间的关系,线程之间的共享变量存在主内存中,各个线程都有一个私有的本地内存存着该线程用到的共享变量副本。
如果线程A、B要通信的话,A把本地内存中更新的共享变量刷新到主内存里,B到主内存中读取更新过的共享变量
当多个线程同时访问一个数据时,可能本地内存没有及时刷新到主内存导致线程安全问题
重排序
什么是重排序?
程序按照代码的先后顺序执行
处理器为了提高程序执行效率,可能会对输入的代码进行优化重排序,不保证每个语句的执行先后顺序,但是保证最终执行结果是一致的。
重排序遵守的规则 as-if-serial
1. 不管怎么排序,结果不能改变
2. 不存在数据依赖的代码可以被编译器和处理器重排序
重排序实际执行的指令步骤
编译器优化重排序
指令级并行重排序
内存系统重排序
最终执行的指令序列
自旋和忙循环
自旋:很多synchronized里的代码执行时间非常快,此时等待的线程加锁可能不太值得,可以让等待锁的线程不被阻塞,而是在synchronized的边界做忙循环,也就是自旋。如果做了多次仍未获得锁,在进入阻塞。
忙循环:不放弃CPU运行空循环
synchronized
synchronized的底层实现原理
synchronized语义底层是通过一个monitor(监视器锁)的对象来实现
每个对象有一个monitor,每个synchronized修饰过的代码在它的monitor被占用时会处于锁定状态并且尝试获取monitor的所有权
1. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程成为monitor的所有者
2. 如果线程已经占用monitor,只是重新进入,则monitor的进入数加1
3. 如果其他线程已经占用monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权
synchronized可重入的原理
重入锁是指一个线程获取该锁之后,该线程可以继续获得该锁。底层维护一个计数器,线程获取锁时计数器加一,释放锁时计数器减一。计数器为0表示未被任何线程所占用。
synchronized三种使用方式
修饰实例方法
给当前对象加锁
修饰静态方法
给当前类加锁
修饰代码块
给指定对象加锁
synchronized锁升级的原理是什么
锁升级的目的是为了降低锁带来的性能消耗,1.6之后优化synchronized的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式。
在锁对象的对象头里有一个threadid字段,当第一次访问的时候threadid为空,jvm让其持有偏向锁并将threadid设置为当前线程id。再次进入时会判断threadid是否一致,如果一致直接使用该对象,如果不一致则升级为轻量级锁,通过自旋循环一定次数获取锁,执行到一定次数仍未获取到锁时,此时升级为重量级锁。
线程B如何知道线程A修改了变量
volatile修饰变量
synchronized修饰修改变量的方法
synchronized、volatile、CAS
synchronized是悲观锁,属于抢占式,会引起其他线程阻塞
volatile
实现机制: 基于`内存屏障`的特性实现,JVM会在volatile变量的写指令之后插入写屏障(`write-barrier`),读指令之前插入读屏障(`read-barrier`)
特性/作用
可见性:共享变量的变更对所有线程可见
禁止重排序:volatile变量的指令不参与重排序
happen-before原则:写指令happen-before读指令,确保变量的变更对所有线程有效
不保证变量修改的原子性,常用于多线程下的单次操作
CAS
Compare And Swap:内存值V,旧期望值A,新值B:通过比较V和A判断可不可以替换成B,A=V替换,A!=V不替换,最终返回V。
底层实现:通过反射获取unsafe实例,直接用unsafe获取变量在内存的偏移地址,从偏移地址获取值,比较并替换
缺陷
ABA问题:A变成B,再变成A,CAS认为A从未被修改过。
可增加版本标记通过控制变量的版本保证正确性。
自旋时间过长的话,会造成CPU浪费,长时间做无用功
只能针对一个共享变量
volatile + CAS可实现原子性操作,可以参见AtomicInteger
Lock接口和synchronized对比有什么优势
提供更具扩展性的锁操作,更灵活。
1. 可以使锁更公平
2. 可以使线程在等待锁的时候响应中断
3. 可以让线程尝试获取锁,并在无法获取时立即返回或等待一段时间
4. 可以在不同范围,以不同顺序获取和释放锁
整体上说Lock是synchronized的扩展版,更灵活,支持公平锁和非公平锁(默认),synchronized只支持非公平锁。但大部分情况下,非公平锁是高效的选择。
悲观锁和乐观锁
悲观锁:每次拿数据的时候都认为别人会修改,所以每次都会上锁,这样别人想拿这个数据就会阻塞。
乐观锁:每次拿数据时都认为别人不会修改,所以不上锁。但是在更新时会判断是否有其他人也在更新,可以使用版本号等机制判断。
线程池相关
线程池的优点
降低资源消耗:重用存在的线程,减少对象创建销毁的开销
提高响应速度:当任务到达时,不需要等线程创建就立即执行
提高线程可管理性:线程池统一分配、优化和监控线程
附件功能:提供定时执行、定期执行、单线程、并发数控制的功能
线程池的执行原理
0. 提交任务到线程池
1. 判断线程池里的核心线程是否都在执行任务,如果不是则创建新的工作线程执行任务,如果都在执行任务,进入下一步
2. 判断工作队列是否已满,如果没满则将提交的任务存入工作队列,如果已满,进入下一步
3. 判断线程池里的线程是否都处于工作状态,如果没有则创建一个新的工作线程执行任务,如果满了,更具饱和策略处理这个任务
如何合理的分配线程池大小
根据实际情况来定,简单来说根据任务的CPU密集和IO密集分配
CPU密集型任务:该任务需要大量的运算,且没有阻塞,CPU一直运行
可以少配置线程数,大概和机器CPU核数相当,这样每个线程都在执行任务
IO密集型任务:该任务不需要大量的运算,需要大量的阻塞
多配置线程数,2*CPU核数
总的来说:
线程等待时间比CPU执行时间比例高,需要越多线程
线程CPU执行时间比等待时间比例高,需要越少线程
介绍下Executors
Executors是实现线程池功能的工厂类,提供newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor等方法构建适用于不同场景的线程池。
线程池的四种创建方式
newCachedThreadPool
创建一个可缓存线程池,根据所需对线程池的长度可以增加或回收
因为无限制,容易造成堆内存溢出
newFixedThreadPool
创建一个定长的线程池,超出的线程会在队列等待,最好根据系统资源来设置长度。
Runtime.getRuntime().availableProcessors()方法查看电脑CPU数量
线程固定,如果有很多线程容易造成阻塞队列越来越长,最终导致OOM
newScheduledThreadPool
创建一个固定长度的线程池,而且支持定时以及周期性的任务执行,类似于Timer(Java的一个定时类)
newScheduledThreadPool.schedule(new Runnable(){...}, 3, TimeUnit.SECONDS) //延迟3秒执行
newSingleThreadPool
创建一个单线程话的线程池,只用唯一的工作线程来执行任务。如果唯一的线程因为异常终止,那么会有一个新的线程来替代它。
Executor和Executors的区别
Executors工具类,提供功能创建不同类型的线程池
Executor接口对象执行线程任务,ExecutorService接口继承Executor接口并进行扩展提供更多的方法,能获取任务执行的状态并可以获取任务的返回值
使用ThreadPoolExecutor可以创建自定义线程池
线程池有哪些状态
RUNNING
接受新的任务,处理等待队列的任务
SHUTDOWN
不接受新的任务,但会继续处理等待队列的任务
STOP
不接受新的任务,不在处理等待队列的任务,且中断正在执行任务的线程
TIDYING
所有任务都销毁时,线程池状态转为TIDYING状态,会执行terminated()方法
TERMINATED
terminate()方法结束后,进入该状态
线程池中的submit()和execute()区别
相同点:都可以开启线程执行池中的任务
不同点:
接收参数:execute()只接收Runnable,submit()接收Runnable或Callable
返回值:submit()方法可以返回持有计算结果的Future对象
异常处理:submit()方便Exception处理
ThreadPoolExecutor饱和策略有哪些
如果当前同时运行的线程数量达到最大线程数并且队列也放满了任务时,ThreadPoolExecutor定义了一些策略
1. ThreadPoolExecutor.AbortPolicy:抛出RejectedExecutionException来拒绝新任务的处理
2. ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务
3. ThreadPoolExecutor.DiscardPolocy:不处理新任务,直接丢弃
4. ThreadPoolExecutor.DiscardOldestPolicy:将最早的未处理任务丢弃
如何自定义线程池
new ThreeadPoolExecutor()
构造参数
corePoolSize:核心线程数量
maximumPoolSize:最大线程数量
keepAliveTime:线程保持时间
unit:时间单位
workQueue:阻塞队列
threadFactory:线程工厂,可默认
handler:线程池拒绝策略,可默认
JVM相关
JVM由哪些部分组成,运行流程是什么
JVM包含两个子系统和两个组件
子系统Class Loader 类加载
根据类名加载class文件到运行时数据区中的方法区内
子系统Execution Engine 执行引擎
将java字节码翻译成底层的系统指令
运行时数据区
JVM内存区域
本地接口
本地库的接口,其他语言(C/C++)编写
运行流程
1. 编译器将Java代码转换成字节码
2. 由类加载器把字节码加载到内存中
3. 而字节码知识JVM的指令集规范,不能被底层系统执行。需要执行引擎将其翻译成底层系统指令,交由CPU去执行
4. CPU执行过程中有可能需要调用其他语言的本地库接口来实现整个程序的功能
说一下JVM运行时数据区
JVM运行时数据区主要分为两大类
线程共享的内存:数据区
方法区:主要存储类信息、常量、静态变量、JIT
堆内存:主要存储引用数据的值
线程独享的内存:指令区
程序计数器:存储当前线程执行指令的地址,也叫线程计数器,作用于多线程之间的切换
虚拟机栈:每个方法的执行都会在虚拟机栈中创建一个栈帧(也就是一个方法一个栈帧),用于存储局部变量、操作数栈、动态链接、方法出口等信息
栈帧存储的信息解析
局部变量表:用于存储临时的变量
操作数栈:用于计算或操作数据,并将计算结果放入局部变量表。
动态链接:当方法中调用其他方法时,需要存储链接到其他方法的地方
出口:出口正常的话是return,不正常的话是抛出异常。
一个方法调用另一个方法会创建很多栈帧吗?
会的,并且根据顺序栈帧一直从上往下排。
递归调用的方法,自己会创建很多栈帧吗?
会的
本地方法栈
1. 首先,他和虚拟机栈很相似,只不过方法上带有native关键字的。
2. 其次,native关键字的方法内容是看不到的。需要去官网下下载源码,而且大部分源码是c和c++的
深拷贝和浅拷贝
浅拷贝
只是增加一个指针,指向你存在的内存地址。
深拷贝
不仅增加一个指针,而且申请一个新的内存。使这个增加的指针指向这个新的内存。
垃圾回收相关
java会存在内存泄漏吗?
内存泄漏是指不再使用的对象或者变量一直占据着内存空间
java也会出现,如长声明周期的对象持有短生命周期对象的引用时
简述java垃圾回收机制
在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行,只有在虚拟机空闲或者当前堆内存不足时才会触发执行,扫描那些没有被任何引用的对象,并将内存回收。
垃圾回收器是如何判断对象是否可以被回收的?
垃圾回收器从GC Root开始向下搜索,搜索的路径称之为引用链,当一个对象到GC Roots没有任何引用链路径时,则这个对象是可以被回收的。
GC Roots的引用一般有哪些?
1. 虚拟机栈中引用的对象,如方法里的变量和参数
2. 本地方法栈引用的对象
3. 方法区中类的常量/静态变量引用的对象
4. 活着的线程Alive Thread
5. 用于同步的监控对象Monitor
引用计数法为什么会被淘汰?
无法判断循环引用的情况。
如何手动进行垃圾回收?
执行System.gc()
JVM的垃圾回收算法有哪些?
标记-清除算法
分为标记和清除两个阶段。标记阶段从根引用开始遍历,深度优先搜索,标记被引用的对象。清除阶段再次遍历,未标记的对象加入空闲链表,标记的对象去除标记。
* 空闲链表表示该块区域可以被分配。
* 空闲链表表示该块区域可以被分配。
缺点:产生大量不连续的内存碎片
标记-整理算法
分三次遍历,第一次遍历标记活动对象,并根据活动情况设置对象的forwarding字段;第二次遍历根据forwarding值变更相关引用指向地址;第三次遍历移动对象,移动到forwarding的地址
优点:解决标记-清除算法的内存碎片问题
缺点:需要进行局部移动,效率更低
复制算法
将内存空间划分为两个相等的区域,每次只使用其中一块。需要回收时,将该区域存活的对象复制到另一个区域中,然后清空当前区域。
优点:实现简单,运行高效,不产生内存碎片
缺点:可用空间减半,对象存活率高时复制操作会特别频繁。
讲一下新生代、老年代和永久代
新生代
新生代一般只保存新出现的对象,每次垃圾回收时会发现只有少量的对象存活,一般采用复制算法进行回收。
因此,新生代又被划分为三个区域:Eden、From Survior、To Survior,三者比例一般是8:1:1,对象复制回收的机制:
新对象一开始在Eden区,Eden空间不足时触发Minor GC,活动的对象从Eden区复制到From Survior区。
当Minor GC触发时From区空间也不足时,将活动对象从From区复制到To区,并清空From区
Survior区域的复制操作结束后,From区和To区的角色互换
新生代比例分配为什么是8:1:1?1:1:1行吗?直接9:1呢?
首先,Eden空间不足时就会触发一次minor gc,1:1:1会导致Eden区空间过小频繁触发minor gc。
其次,Survior区也是执行的复制算法,所以Survior区需要被划分为两个相等的区域,9:1无法满足
最后,8:1:1是经过统计计算出的最优比例,也是JVM的默认设置比例,可以通过参数 -XX:SurviorRatio 来设置
老年代
一般保存存活了很久的对象,在新生代存活超过默认值15次gc就会被移到老年代中。老年代的回收算法一般采用标记-清除或标记-整理算法
永久代
JVM的方法区,存放类信息、常量、静态变量等数据
1.7之前:空间大小固定,不足时抛异常
1.8之后:空间可变,不足时会扩展,可设置最大空间
新生代和老年代的默认比例
1:2,可以通过参数 -XX:NewRatio来指定
讲一下Minor GC、Major GC、Full GC的区别和触发条件
Minor GC
发生在新生代的垃圾回收,一般采用复制算法
触发条件:当eden区空间不足时触发
Major GC
发生在老年代的垃圾回收,一般采用标记-清除或标记-整理算法
通常是和Minor GC一起执行,所以一般和Full GC等价
触发条件:
1. 老年代剩余空间 小于 新生代每次晋升到老年代的对象平均大小时
2. 新生代晋升到老年代的对象大于老年代剩余空间时
3. 堆内存分配很大的对象时会直接放入老年代,如果大对象的大小 超过 老年代剩余空间时也会触发
4. System.gc()
5. CMS GC异常
Full GC
清理整个堆空间,等于Minor GC+Major GC
讲一下JVM有哪些垃圾回收器
回收新生代的垃圾回收器
Serial:单线程,复制算法
PraNew:多线程,复制算法
Parallel Scavenge:多线程,复制算法,高吞吐量追求高效率,适用于后台应用
回收老年代的垃圾回收器
Serial Old(标记-整理算法):单线程
Parallel Old(标记-整理算法):多线程,吞吐量优先,Parallel Scavenge的老年代版本
CMS(标记-清除算法):多线程,以获取最短回收停顿时间为目标,具有高并发、低停顿的特点
CMS的回收步骤
1. Initial Mark初始标记阶段:暂停程序执行,标记roots对象和第一层可达对象
2. Concurrent Mark并发标记阶段:不暂停应用程序,标记活动的对象
3. Remark重新标记阶段:暂停程序执行,标记步骤2遗漏的对象
4. Cleanup清理阶段:并发清理与应用线程并发执行,回收未被标记的对象
CMS的缺陷
1. Concurrent Mode Failure,由于浮动垃圾造成的老年代可用空间不足,新生代的对象晋升失败从而会触发Full GC
2. 碎片空间过多,导致可用的连续空间不足以存放大对象从而会触发Full GC
G1(Garbage First)
回收的范围是整个堆内存
垃圾回收器可以应用的组合有哪些
Serial + Serial Old
Serial + CMS
ParNew + Serial Old
ParNew + CMS
Parallel Scavenge + Serial Old
Parallel Scavenge + Parallel Old
G1
详细讲述下分代垃圾回收器是怎么工作的?
分代回收器有两个分区:新生代和老年代,其默认比例为1:2
新生代有三个分区:Eden、From Survior、To Survior,默认比例8:1:1,在新生代发生的回收机制机制使用复制算法。
新生代的回收流程如下:新对象创建之初会放置在Eden区,当Eden区空间不足时触发Minor GC,使用复制算法将活动的对象复制到From Survior区,如果触发Minor GC时 From Survior区空间也不足时,同样使用复制算法先将From Survior的活动对象复制到To Survior区,再清空From Survior区并将From Survior区和To Survior区的角色对换。每次从From Survior区复制到To Survior区的对象年龄会加一,当年龄达到15(JVM默认值)时,会晋升到老年代中。另外新创建的大对象也会直接进入到老年代。
老年代的空间占比达到一定比例时会触发Major GC,根据回收器的不同使用标记清除或标记整理算法执行回收
讲述下分区垃圾回收器(G1)是怎么工作的?
在JVM启动之初,收集器将整个堆内存分成大小相同的2000个区,区块的大小范围(1MB~32MB),因此老年代和新生代不再是物理连续的区域了
当一个分区空间不足时,触发Minor GC,执行复制算法将活动的对象移到另一个分区
当所有分区空间达到一定比例时触发Full GC,分为四个阶段
a) Initial Mark初始标记阶段:与Minor GC同时发生,不停顿的情况下标记roots对象和第一层可达对象。
b) Concurrent Mark并发标记阶段:不停顿的情况下标记所有活动的对象,并计算每个region的对象存活率,计算出的对象存活率用于后面cleanup阶段使用,如果region几乎没有对象存活,GC会在这个阶段就将其回收掉,这也是Garbage First名字的由来。
c) Remark重新标记阶段:程序停顿,标记步骤2遗漏的活动对象,采用的算法更高效SATB(snapshot-at-the-beginning)
d) Clean up/Copy阶段:挑选对象存活率低的region进行回收,这个阶段也是和minor gc一同发生的,Copy阶段伴随着STW事件发生
G1的优缺点
优点:
回收时有空间压缩的操作,减少空间碎片
Major GC很多阶段是和Monor GC同时进行,减少回收停顿时间
缺点:
不适合大对象多的场景
G1和CMS的比较
类型:CMS分代回收器,G1分区回收器
回收范围:CMS针对老年代,G1针对整个堆内存
回收过程:CMS在初始标记阶段和重新标记阶段都有程序停顿,G1的很多MajorGC回收阶段和MinorGC一同发生,只有在重新标记阶段有短暂程序停顿
类加载机制
描述一下JVM加载class文件的原理机制?
详细的加载过程:类加载器读取class文件,将类信息、常量、静态变量存储在方法区中,并在堆中生成一个代表该类的java.lang.Class对象,封装了类在方法区的数据结构并向用户提供访问该数据结构的接口,即反射的接口
java中类的加载并不是一次性将所有的类加载,而是保证程序运行的基础类加载到jvm中,至于其他类,只有在需要时才会被加载,节省内存开支
类加载器有哪些?
启动类加载器
扩展类加载器
系统类加载器:根据类路径加载类。一般来说,java的类都是有它来完成加载的,可以通过ClassLoader.getSystemClassLoader()获取它
自定义类加载器
什么是双亲委派模型
当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。
作用:避免类的重复加载
JVM调优
JVM的参数可以在哪里设置?
IDEA等编辑器的工具里设置
如果是jar包的话,通过命令java -jar 加上相应参数
如果是war包的话,可以在tomcat设置
JVM调优的工具有哪些?
jconsole:监控jvm中的内存、线程和类
jvisualvm:全能分析工具,可以分析内存快照、线程快照、程序死锁、监控内存的变化、gc变化等。
常用的JVM调优参数有哪些?
-Xms:初始堆的大小
-Xmx:最大堆的大小
-Xmn:堆中新生代大小
-XX:NewSize=n :新生代初始大小
-XX:MaxNewSize=n:新生代最大值
-XX:NewRatio=n:老年代和新生代的比值,默认为2
-XX:SurviorRatio=n:新生代中Eden与Survior的比值,默认为8
-XSS:每个线程的堆栈大小。1.5后每个线程为1M
-XX:PermSize=n:永久代初始值
-XX:MaxPermSize=n:永久代最大值
-XX:+Use**GC:指定垃圾回收器,1.9之后默认G1
-XX:MaxTenuringThreshold=n:新生代晋升老年代的年龄,默认15
-XX:LargePageSizeInBytes=n:堆内存的内存页大小
-XX:+UseFastAccessorMethods:优化原始类型的getter方法性能
-XX:+DisableExplicitGC:禁止System.gc(),默认启用
JIT知识点
JIT是什么?
一种动态编译器,全名Just In Time Compiler即时编译器。
JIT的作用?
随着程序运行的时间推移,一些运行频率高的代码(热点代码)会被JIT编译成本地机器指令,再进行一些优化后保存起来,下次执行时可以无需再翻译,提高代码执行效率。
Spring
Spring
为了简化java开发的轻量级开发框架,两大核心概念:依赖注入/控制反转(DI/IOC)和面向切面编程(AOP)
什么控制反转?
不会直接创建对象,而是将对象声明出来,在配置文件中描述对象的属性和之间的关联,由容器管理,等到需要使用时使用注解由容器自动将对象组合创建出来。
什么是面向切面编程?
一种编程模式,允许程序员通过自定义的横切点进行模块化,将各个行为封装到可重用的模块中。比如把日志输出、校验功能封装在某个可重用的模块,如方法调用前、方法调用后、抛异常时等
Spring框架用到了哪些设计模式?
工厂模式:BeanFactory就是简单工厂模式,用来创建对象的实例
单例模式:Bean默认的都是单例模式
代理模式:AOP的实现使用了动态代理模式
模板方法:用来解决代码重复的问题。如RestTemplate、JpsTemplate、JmsTemplate
观察者模式:定义对象间一对多的依赖关系,当一个对象发生改变时,所有依赖它的对象都会收到通知。如ApplicationListener
简述Spring如何设计容器的?
BeanFactory可以理解为是个HashMap,Key是BeanName,value是Bean实例,提供put和get两个功能。
put时加载配置文件,根据类名放入Map中
getBean时,根据类名实例化对象,如果有依赖关系,通过递归调用实例化其属性完成注入
Bean相关
Spring中bean的作用域有哪些?
singleton:bean在每个容器中只有一个实例
Spring中的单例bean是线程安全的吗?
不是
实际上大部分情况下bean都是无状态的(属性/字段),不保存数据,因此某种程度上来说bean是安全的
Spring如何处理线程并发问题?
ThreadLocal和线程同步机制可以解决多线程中相同变量的访问冲突问题。
同步机制:当不同的线程在访问前需要获取锁,没有锁则需要排队
ThreadLoad:为每一个线程提供一个独立的变量副本,从而隔离了多个线程同时访问数据的冲突
prototype:bean在每个容器中可以有多个实例
request:每次http请求都会创建一个bean
session:在一个http session中,一个bean对应一个实例
global-session:在一个全局的http session中,一个bean对应一个实例
Spring中bean默认的作用域是singleton。使用prototype需要慎重的考虑,因为频繁的创建和销毁bean会影响性能。
Spring中bean的生命周期
根据配置文件/注解将bean的类名放入容器中
需要使用该bean时,根据类名实例化对象,如果有依赖关系,通过递归调用实例化其属性完成注入
bean实例化之后会一直留在应用上下文中,直至上下文被销毁
注解相关
使用@Autowired注解自动装配的过程是怎样的?
当启动Spring IOC时,容器会自动装载一个AutowiredAnnotationBeanPostProcessor后置处理器。当容器扫描到@Autowired、@Resource或@Inject时,就会自动查找需要的bean,并装配给该对象的属性。
使用@Autowired时,首先在容器中查询对应类的bean,如果刚好查到一个直接装配给@Autowired指定的数据。如果查到的结果不止一个,就根据@Autowired的名称查找,如果为空则抛出异常,解决方法是使用required=false
@Component、@Controller、@Repository、@Service的区别?
@Component:Spring管理组件的通用构造型
@Controller:将此类标记为Web MVC的控制器
@Service:@Component的特化,无额外功能,指定具体的组件意图
@Repository:@Component的特化,是针对DAO的组件,可以使未经检查的异常转换成Spring DataAccessException
@Required注解有什么作用?
表明bean的该属性必须在配置时设置
@Autowired和@Resource之间的区别?
都可用于构造函数、成员变量、setter方法
@Autowired默认按照类型装配,默认要求依赖对象必须存在(也可以设置required属性为false改变此默认行为)
@Resource默认按照name来装配,name不存在时才会按照类型来装配
@Qualifier注解有什么作用?
当创建了多个相同类型的bean并希望使用属性装配其中一个bean时,可以使用@Qualifier来指定
Spring事务相关
说一下Spring的事务传播行为
事务传播行为指的是,当多个事务同时存在时,Spring如何处理这些事务的行为。
PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务。是最常见的设置
PROPAGATION_SUPPORTS:如果当前没有事务,就以非事务执行,如果当前存在事务,就加入该事务
PROPAGATION_MANDATORY:如果当前没有事务,就抛出异常,如果当前存在事务,就加入该事务
PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务
PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则判处异常
PROPAGATION_NESTED:如果当前没有事务,就创建一个新事务,如果当前存在事务,则嵌套事务执行
说一下Spring的事务隔离级别
ISOLATION_DEFAULT:用底层数据库设置的隔离级别,Spring默认值
ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别,事务未提交前就可以被其他事务读取(会出现脏读、不可重复读、幻读)
ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成不可重复读、幻读)SQL Server默认级别
ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时内容是一致的,禁止读取别的事务未提交的数据(会造成幻读),MySQL默认级别
ISOLATION_SERIALIZABLE:序列化,代价最好最可靠的隔离级别,该级别能防止脏读、不可重复读、幻读。
数据不一致的问题
脏读:表示一个事务能够读取另一个事务未提交的数据
不可重复读:表示一个事务内,多次读同一个数据结果(一般是数据的某个字段)不一致,是由于另一个事务对数据的更改所造成。
幻读:表示同一个事务内多次查询返回的结果(一般是数据的数量)不一致,是由于另一个事务插入或删除造成
AOP相关
Spring AOP的几个名词
切面(Aspect)
通知和切点的结合
连接点(Join Point)
目标方法
通知(Advice)
切面的工作内容
切点(Pointcut)
匹配通知要织入的一个或多个连接点,通常使用类和方法名称或正则表达式来指定这些切点
引入(Introduction)
允许我们向现有类添加新方法或属性
目标对象(Target Object)
被切面通知的对象
织入(Weaving)
把切面应用到目标对象并创建新的代理对象的过程
Spring通知有哪些类型
前置通知(Before)
目标方法被调用前调用通知
后置通知(After)
目标方法完成之后调用通知,不关心方法输出是什么
返回通知(After-returning)
目标方法成功执行之后调用通知
异常通知(After-throwing)
目标方法抛出异常后调用通知
环绕通知(Around)
被调用前和调用后都调用通知
SpringBoot
SpringCloud
Mybatis
锁
消息队列
设计模式
ES
0 条评论
下一页