Java
一.java基础
1.八大基础类型
数字型: 字节类型(byte)、短整型short、整型int、长整型Long、单精度浮点数float、双精度浮点数double
字符型: 字符类型char、
布尔型: 布尔类型boolean、>
2.java三大特性
封装: 使用private关键字,让对象私有,防止无关的程序去使用。
继承: 继承某个类,使子类可以使用父类的属性和方法。
多态: 同一个行为,不同的子类具有不同的表现形式。
3.重载和重写的区别
重载: 发生在同一类中,函数名必须一样,参数类型、参数个数、参数顺序、返回值、修饰符可以不一样。
重写: 发生在父子类中,函数名、参数、返回值必须一样,访问修饰符必须大于等于父类,异常要小于等于父类,父类方法是private不能重写。
4.pubilc、protected、(dafault)不写、private修饰符的作用范围
pubilc: 同类、同包、子类、不同包都可以使用。
protected: 同类、同包、子类可以使用,不同包不能。
(dafault)不写: 同类、同包可以使用,子类、不同包不能。
private: 只有同类可以。
5.==和equals的区别
==: 基础类型比较的值,引用类型比较的是地址值。
equals: 没有重写比较地址值是否相等,重写比较的内容是否相对。比如String类重写equals,源码首先比较是否都是String对象,然后再向下比较。
6.hashcode()值相同,equals就一定为true
不一定,因为 "重地"和"通话"的hashcode值就相同,但是equals()就为false。
但是equals()为true,那么hashcode一定相同。
7.为什么重写equals(),就要重写hashcode()?
保证同一对象,如果不重写hashcode,可能会出现equals比较一样,但是hashcode不一样的情况。
8.short s = 1;s = s + 1;(程序1)和 short s = 1; s += 1;(程序2)是否都能正常运行
程序1会编译报错,因为 s + 1的1是int类型,因为类型不兼容。强制转换失败。
程序2可以正常运行,因为java在复合赋值解释是 E1 += E2,等价于 E1 = (T)(E1 + E2),T是E1的类型,因此s += 1等价于 s = (short)(s + 1),所以进行了强制类型的转换,所以可以正常编译。
9.说出下面程序的运行结果,及原因
public static void main(String[] args) {
Integer a = 128, b = 128, c = 127, d = 127;
System.out.println(a == b);
System.out.println(c == d);
}
结果:false,true
因为Integer = a,相当于自动装箱(基础类型转为包装类),因为Integer引入了IntegerCache来缓存一定的值,IntegerCache默认是 -128~127,所以128超过了范围,a和b不是相同对象,c和d是相同对象。
可以通过jvm启动时,修改缓存的上限。
10.&和&&的区别
&&: 如果一边为假,就不比较另一边。具有短路行
&: 两边都为假,结果才为假,多用于位运算。
11.String、StringBuffer、StringBuilder的区别
String: 适用于少量字符串。创建之后不可更改,对String的修改会生成新的String对象。
StringBuilder: 适用于大量字符串,线程不安全,性能更快。单线程使用
StringBuffer: 适用于大量字符串,线程安全。多线程使用,用synchronized关键字修饰。
12.String rap = new String(“ctrl”);创建了几个对象?
一个或两个,如果常量池存在,那就在堆创建一个实例对象,否则常量池也需要创建一个。
13.什么是反射
在运行过程中,对于任何一个类都能获取它的属性和方法,任何一个对象都能调用其方法,动态获取信息和动态调用,就是反射。
14.浅拷贝和深拷贝的区别
浅拷贝: 基础数据类型复制值,引用类型复制引用地址,修改一个对象的值,另一个对象也随之改变。
深拷贝: 基础数据类型复制值,引用类型在新的内存空间复制值,新老对象不共享内存,修改一个值,不影响另一个。
深拷贝相对浅拷贝速度慢,开销大。
15.构造器能被重写吗
不能,可以被重载。
16.并发和并行
并发: 一个处理器同时处理多个任务。(一个人同时吃两个苹果)
并行: 多个处理器同时处理多个任务。(两个人同时吃两个苹果)
17.实例变量和类变量。
类变量是被static所修饰的,没有被static修饰的叫实例变量也叫成员变量。同理也存在类对象和实例对象,类方法和实例方法。
//类变量
public static String kunkun1 = "鸡你太美";
//实例变量(成员变量)
public String kunkun2 = "鸡你不美";
18.说出下面程序的运行结果,及原因
public class InitialTest {
public static void main(String[] args) {
A ab = new B();
ab = new B();
}
}
class A {
static { // 父类静态代码块
System.out.print("A");
}
public A() { // 父类构造器
System.out.print("a");
}
}
class B extends A {
static { // 子类静态代码块
System.out.print("B");
}
public B() { // 子类构造器
System.out.print("b");
}
}
结果:ABabab
原因:
①执行顺序是 父类静态代码块(父类静态变量) -> 子类静态代码块(子类静态变量) -> 父类非静态代码块 -> 父类构造方法 -> 子类非静态代码块 -> 子类构造方法
②静态代码块(静态变量)只执行一次。
19.抽象类和接口的区别
抽象类只能单继承,接口可以实现多个。
抽象类有构造方法,接口没有构造方法。
抽象类可以有实例变量,接口中没有实例变量,有常量。
抽象类可以包含非抽象方法,接口在java7之前所有方法都是抽象的,java8之后可以包含非抽象方法。
抽象类中方法可以是任意修饰符,接口中java8之前都是public,java9支持private。
扩展:普通类是亲爹,手把手教你怎么学,抽象类(多个类具有相同的东西,拿出来放抽象类)是师傅,教你一部分秘籍,然后告诉你怎么学。接口(规范了某些行为)是干爹,只给你秘籍,怎么学全靠你。
20.Error和Exception有什么区别
Error: 程序无法处理,比较严重的问题,程序会立即崩溃,jvm停止运行。
Exception: 程序本身可以处理(向上抛出或者捕获)。编译时异常和运行时异常
21.NoClassDefFoundError和ClassNotFoundException区别
NoClassDefFoundError: 在打包时漏掉了某些类或者打包时存在,然后你把target里的类删除,然后jvm运行时找不到报错。
ClassNotFoundException: 在编译的时候某些类找不到,然后报错。
22.如果try{} 里有一个 return 语句,那么finally{} 里的代码会不会被执行,什么时候被执行,在 return 前还是后?
会执行,在return之前执行,如果finally有return那么try的return就会失效。
23.看一面代码执行结果是啥
public class TryDemo {
public static void main(String[] args) {
System.out.println(test1());
}
public static int test1() {
int i = 0;
try {
i = 2;
return i;
} finally {
i = 3;
}
}
}
结果 2
因为在return前,jvm会把2暂存起来,所以当i改变了,回到try时,还是会返回暂存的值。
24.final关键字有哪些用法?
修饰类: 不能被继承。
修饰方法: 不能被重写。
修饰变量: 声明时给定初始值,只能读取不能修改。如果是对象引用不能改,但是对象的属性可以修改。
25.jdk1.8的新特性
①lambda 表达式
②方法引用
③加入了base64的编码器和解码器
④函数式接口
⑤接口允许定义非抽象方法,使用default关键字即可
⑥时间日期类改进
26.http中重定向和转发的区别
重定向发送两次请求,转发发送一次请求
重定向地址栏会变化,转发地址栏不会变化
重定向是浏览器跳转,转发是服务器跳转
重定向可以跳转任意网址,转发只能跳转当前项目
重定向会有数据丢失,转发不会数据丢失
27.get和post请求的区别 delete、put
get相对不安全,数据放在url中(请求行),post放在body中(请求体),相对安全。
get传送的数据量小,post传送的数据量大。
get效率比post高,是form的默认提交方法。
28.cookie和session的区别
存储位置不同:cookie放在客户端电脑,session放在服务器端内存的一个对象
存储容量不同:cookie <=4KB,一个站点最多保存20个cookie,session是没有上限的,但是性能考虑不要放太多,而且要设置session删除机制
存储数据类型不同:cookie只能存储ASCll字符串,session可以存储任何类型的数据
隐私策略不同:cookie放在本地,别人可以解析,进行cookie欺骗,session放在服务器,不存在敏感信息泄露
有效期不同:可以设置cookie的过期时间,session依赖于jsessionID的cookie,默认时间为-1,只需要关闭窗口就会失效
29.java中的数据结构
数组、链表、哈希表、栈、堆、队列、树、图
30.什么是跨域?跨域的三要素
跨域指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制
协议、域名、端口
注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域
31.tomcat三个默认端口及其作用
8005:这个端口负责监听关闭tomcat的请求。
8009:接受其他服务器的请求
8080:用于监听浏览器发送的请求
32.throw 和 throws 的区别?
throw:抛出一个异常。
throws:声明一个异常。
33.说一下你熟悉的设计模式
单例模式: 保证被创建一次,节省系统开销。
工厂模式: 解耦代码。
观察者模式: 定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有的依赖者都会收到通知并自动更新。
代理模式: 代理对象具备被代理对象的功能,并代替被代理对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理。
模板模式: 较少代码冗余。例如:redis模板。
34.实例化对象有哪几种方式
① new
② clone()
③ 反射
④先序列化在反序列化
35.java中什么样的类不能被实例化
抽象类: abstract关键字修饰的类。
36.序列化和反序列化
序列化: 把对象转为字节序列的过程,在传递和保存对象时,保证了对象的完整性和可传递性,便于在网络传输和保存在本地文件中。
反序列化: 把字节序列转为对象的过程,通过字节流的状态和信息描述,来重建对象。
37.序列化的优点
将对象转为字节流存储到硬盘上,当JVM噶了的话,字节流还会在硬盘上等待,等待下一次JVM的启动,把序列化的对象,通过反序列化为原来的对象,减少储存空间和方便网络传输(因为是二进制)。
38.你知道什么是单点登录吗?
单点登录(SSO:Single Sign On): 同一账号在多系统中,只登录一次,就可以访问其他系统。多个系统,统一登录。
列如:在一个公司下,有多个系统,比如淘宝和天猫,你登录上淘宝,就不用再去登录天猫了。
39.实现单点登录的方式
① Cookie: 用cookie为媒介,存放用户凭证。登录上父应用,返回一个加密的cookie,访问子应用的时候,会对cookie解密校验,通过就可以登录。不安全和不能跨域免登。
② 分布式session实现: 用户第一次登录,会把用户信息记录下来,写入session,再次登录查看session是否含有对应信息。session系统不共享,使用缓存等方式来解决。
③重定向: 父应用提供一个GET方式的登录接口A,用户通过子应用重定向连接的方式访问这个接口,如果用户还没有登录,则返回一个登录页面,用户输入账号密码进行登录,如果用户已经登录了,则生成加密的token,并且重定向到子应用提供的验证token的接口B,通过解密和校验之后,子应用登录当前用户,虽然解决了安全和跨域,但是没前两种简单。
40.sso(单点登录)与OAuth2.0(授权)的区别?
单点登录: 就是一个公司多个子系统登录问题。
OAuth2.0: 是授权问题,比如微信授权问题。是一种具体的协议。
41.如何防止表单提交
①js屏蔽提交按钮。
②给数据库添加唯一约束。
③利用Session防止表单重复提交。会有一个token标记,表单提交的时候拦截器会检查是否一致,不一致就不通过。
④使用AOP切入实现。自定义注解,然后新增切入点,然后每次都记录过期时间,然后做比较。
42.泛型是什么?有什么好处?
本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
好处:
①类型安全
②消除强制类型转换
③提高性能
④提高代码的复用性
43.值传递和引用传递
值传递: 函数调用时会把实际参数,复制一份到函数中,函数中对参数进行操作,并不会影响参数实际的值。
引用传递: 将实际参数的地址值传递到函数中,函数对参数进行操作,会影响到实际参数的值。
注意: java中不存在引用传递(即使传的是对象,那也只是传递了对象的引用地址的副本,也属于值传递)。
二.java集合
1.List、Set、Map的区别
List集合有序、可重复的单例集合。
Set集合无序、不可重复的单例集合。
Map集合有序、k不可重复,v可重复的双例集合。
2.List、Set、Map常用集合有哪些?
List
vector: 底层是数组,方法加了synchronized来保证线程安全,所以效率较慢,使用ArrayList替代。
ArrayList: 线程不安全,底层是数组,因为数组都是连续的地址,所以查询比较快。增删比较慢,增会生成一个新数组,把新增的元素和原有元素放到新数组中,删除会导致元素移动,所以增删速度较慢。
LinkedList: 线程不安全,底层是链表,因为地址不是连续的,都是一个节点和一个节点相连,每次查询都得重头开始查询,所以查询慢,增删只是断裂某个节点对整体影响不大,所以增删速度较快。
Set
HashSet: 底层是哈希表(数组+链表或数组+红黑树),在链表长度大于8时转为红黑树,在红黑树节点小于6时转为链表。其实就是实现了HashMap,值存入key,value是一个final修饰的对象。
TreeSet: 底层是红黑树结构,就是TreeMap实现,可以实现有序的集合。String和Integer可以根据值进行排序。如果是对象需要实现Comparator接口,重写compareTo()方法制定比较规则。
LinkedHashSet: 实现了HashSet,多一条链表来记录位置,所以是有序的。
Map<key,value>双例结构
TreeMap: 底层是红黑树,key可以按顺序排列。
HashMap: 底层是哈希表,可以很快的储存和检索,无序,大量迭代情况不佳。
LinkedHashMap: 底层是哈希表+链表,有序,大量迭代情况佳。
3.ArrayList的初始容量是多少?扩容机制是什么?扩容过程是怎样?
初始容量: 默认10,也可以通过构造方法传入大小。
扩容机制: 原数组长度 + 原数组长度/2(源码中是原数组右移一位,也就相当于除以2)
注意:扩容后的ArrayList底层数组不是原来的数组。
扩容过程: 因为ArrayList底层是数组,所以它的扩容机制和数组一样,首先新建一个新数组,长度是原数组的1.5倍,然后调用Arrays.copyof()复制原数组的值,然后赋值给新数组。
4.什么是哈希表
根据关键码值(Key value)而直接进行访问的数据结构,在一个表中,通过H(key)计算出key在表中的位置,H(key)就是哈希函数,表就是哈希表。
5.什么是哈希冲突
不同的key通过哈希函数计算出相同的储存地址,这就是哈希冲突。
6.解决哈希冲突
(1)开放地址法
如果发生哈希冲突,就会以当前地址为基准,再去寻找计算另一个位置,直到不发生哈希冲突。
寻找的方法有:① 线性探测 1,2,3,m
② 二次探测 1的平方,-1的平方,2的平方,-2的平方,k的平方,-k的平方,k<=m/2
③ 随机探测 生成一个随机数,然后从随机地址+随机数++。
(2)链地址法
冲突的哈希值,连到到同一个链表上。
(3)再哈希法(再散列方法)
多个哈希函数,发生冲突,就在用另一个算计,直到没有冲突。
(4)建立公共溢出区
哈希表分成基本表和溢出表,与基本表发生冲突的都填入溢出表。
7.HashMap的hash()算法,为什么不是h=key.hashcode(),而是key.hashcode()^ (h>>>16)
得到哈希值然后右移16位,然后进行异或运算,这样使哈希值的低16位也具有了一部分高16位的特性,增加更多的变化性,减少了哈希冲突。
8.为什么HashMap的初始容量和扩容都是2的次幂
因为计算元素存储的下标是(n-1)&哈希值,数组初始容量-1,得到的二进制都是1,这样可以减少哈希冲突,可以更好的均匀插入。
9.HashMap如果指定了不是2的次幂的容量会发生什么?
会获得一个大于指定的初始值的最接近2的次幂的值作为初始容量。
10.HashMap为什么线程不安全
jdk1.7中因为使用头插法,再扩容的时候,可能会造成闭环和数据丢失。
jdk1.8中使用尾插法,不会出现闭环和数据丢失,但是在多线程下,会发生数据覆盖。(put操作中,在putVal函数里) 值的覆盖还有长度的覆盖。
11.解决Hashmap的线程安全问题
(1)使用Hashtable解决,在方法加同步关键字,所以效率低下,已经被弃用。
(2)使用Collections.synchronizedMap(new HashMap<>()),不常用。
(3)ConcurrentHashMap(常用)
12.ConcurrentHashMap的原理
jdk1.7: 采用分段锁,是由Segment(继承ReentrantLock:可重入锁,默认是16,并发度是16)和HashEntry内部类组成,每一个Segment(锁)对应1个HashEntry(key,value)数组,数组之间互不影响,实现了并发访问。
jdk1.8: 抛弃分段锁,采用CAS(乐观锁)+synchronized实现更加细粒度的锁,Node数组+链表+红黑树结构。只要锁住链表的头节点(树的根节点),就不会影响其他数组的读写,提高了并发度。
13.为什么用synchronized代替ReentrantLock
①节省内存开销。ReentrantLock基于AQS来获得同步支持,但不是每个节点都需要同步支持,只有链表头节点或树的根节点需要同步,所以使用ReentrantLock会带来很大的内存开销。
②获得jvm支持,可重入锁只是api级别,而synchronized是jvm直接支持的,能够在jvm运行时做出相应的优化。
③在jdk1.6之后,对synchronized做了大量的优化,而且有多种锁状态,会从 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁一步步转换。
AQS (Abstract Queued Synchronizer): 一个抽象的队列同步器,通过维护一个共享资源状态( Volatile Int State )和一个先进先出( FIFO )的线程等待队列来实现一个多线程访问共享资源的同步框架。
14.HashMap为什么使用链表
减少和解决哈希冲突,把冲突的值放在同一链表下。
15.HashMap为什么使用红黑树
当数据过多,链表遍历较慢,所以引入红黑树。
16.HashMap为什么不一上来就使用红黑树
维护成本较大,红黑树在插入新的数据后,可能会进行变色、左旋、右旋来保持平衡,所以当数据少时,就不需要红黑树。
17.说说你对红黑树的理解
①根节点是黑色。
②节点是黑色或红色。
③叶子节点是黑色。
④红色节点的子节点都是黑色。
⑤从任意节点到其子节点的所有路径都包含相同数目的黑色节点。
红黑树从根到叶子节点的最长路径不会超过最短路径的2倍。保证了红黑树的高效。
18.为什么链表长度大于8,并且表的长度大于64的时候,链表会转换成红黑树?
因为链表长度越长,哈希冲突概率就越小,当链表等于8时,哈希冲突就非常低了,是千万分之一,我们的map也不会存那么多数据,如果真要存那么多数据,那就转为红黑树,提高查询和插入的效率。
19.为什么转成红黑树是8呢?而重新转为链表阈值是6呢?
因为如果都是8的话,那么会频繁转换,会浪费资源。
20.为什么负载因子是0.75?
加载因子越大,填满的元素越多,空间利用率越高,但发生冲突的机会变大了;
加载因子越小,填满的元素越少,冲突发生的机会减小,但空间浪费了更多了,而且还会提高扩容rehash操作的次数。
“冲突的机会”与“空间利用率”之间,寻找一种平衡与折中。
又因为根据泊松分布,当负载因子是0.75时,平均值时0.5,带入可得,当链表为8时,哈希冲突发生概率就很低了。
21.什么时候会扩容?
元素个数 > 数组长度 * 负载因子 例如 16 * 0.75 = 12,当元素超过12个时就会扩容。
链表长度大于8并且表长小于64,也会扩容
22.为什么不是满了扩容?
因为元素越多,空间利用率是高了,但是发生哈希冲突的几率也增加了。
23.扩容过程
jdk1.7: 会生成一个新table,重新计算每个节点放进新table,因为是头插法,在线程不安全的时候,可能会出现闭环和数据丢失。
jdk1.8: 会生成一个新table,新位置只需要看(e.hash & oldCap)结果是0还是1,0就放在旧下标,1就是旧下标+旧数组长度。避免了对每个节点进行hash计算,大大提高了效率。e.hash是数组的hash值,,oldCap是旧数组的长度。
24.HashMap和Hashtable的区别
①HashMap,运行key和value为null,Hashtable不允许为null。
②HashMap线程不安全,Hashtable线程安全。
25.集合为什么要用迭代器(Iterator)
更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
如果不用迭代器,只能for循环,还必须知道集合的数据结构,复用性不强。
三.多线程
1.线程是什么?多线程是什么?
线程: 是最小的调度单位,包含在进程中。
多线程: 多个线程并发执行的技术。
2.守护线程和用户线程
守护线程: jvm给的线程。比如:GC守护线程。
用户线程: 用户自己定义的线程。比如:main()线程。
拓展:
Thread.setDaemon(false)设置为用户线程
Thread.setDaemon(true)设置为守护线程
3.线程的各个状态
新建(New): 新建一个线程。
就绪(Runnable): 抢夺cpu的使用权。
运行(Running): 开始执行任务。
阻塞(Blocked): 让线程等待,等待结束进入就绪队列。
死亡(Dead): 线程正常结束或异常结束。
4.线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等
wait(): 线程等待,会释放锁,用于同步代码块或同步方法中,进入等待状态
sleep(): 线程睡眠,不会释放锁,进入超时等待状态
yield(): 线程让步,会使线程让出cpu使用权,进入就绪状态
join(): 指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
notify(): 随机唤醒一个在等待中的线程,进入就绪状态。
notifyAll(): 唤醒全部在等待中的线程,进入就绪状态。
5.wait()和sleep()的区别?
① wait() 来自Object,sleep()来自Thread。
② wait()会释放锁,sleep()不会释放锁。
③ wait()只能用在同步方法或代码块中,sleep()可以用在任何地方。
④ wait()不需要捕获异常,sleep()需要捕获异常。
6.为什么 wait()、notify()、notifyAll()方法定义在 Object 类里面,而不是 Thread 类?
① 锁可以是任何对象,如果在Thread类中,那只能是Thread类的对象才能调用上面的方法了。
② java中进入临界区(同步代码块或同步方法),线程只需要拿到锁就行,而并不关心锁被那个线程持有。
③ 上面方法是java两个线程之间的通信机制,如果不能通过类似synchronized这样的Java关键字来实现这种机制,那么Object类中就是定义它们最好的地方,以此来使任何Java对象都可以拥有实现线程通信机制的能力。
7.start()和run()的区别
start()方法: 是启动线程,调用了之后线程会进入就绪状态,一旦拿到cpu使用权就开始执行run()方法,不能重复调用start(),否则会报异常。
run()方法: 就相当于一个普通的方法而已。直接调用run()方法就还只有一个主线程,还是会顺序执行,也可以重复调用run()方法。
8.实现多线程的方式
①继承Thread类。
②实现Runnable接口
③实现Callable接口
④线程池
9.Runnable和Callable的区别
①Runnable没有返回值,Callable有返回值。
②Runnable只能抛出异常,不能捕获,Callable 能抛出异常,也能捕获。
10.线程池的好处
① 线程是稀缺资源,使用线程池可以减少线程的创建和销毁,每个线程都可重复使用。
② 可以根据系统的需求,调整线程池里面线程的个数,防止了因为消耗内存过多导致服务器崩溃。
11.线程池的七大参数
corePoolSize: 核心线程数,创建不能被回收,可以设置被回收。
maximumPoolSize: 最大线程数。
keepAliveTime: 空闲线程存活时间。
unit: 单位。
workQueue: 等待队列。
threadFactory: 线程工程,用于创建线程。
handler: 拒绝策略。
12.线程池的执行过程
①接到任务,判断核心线程池是否满了,没满执行任务,满了放入等待队列。
②等待队列没满,存入队列,等待执行,满了去查看最大线程数。
③最大线程数没满,执行任务,满了执行拒绝策略。
13.四大方法
①ExecutorService executor = Executors.newCachedThreadPool(): 创建一个缓存线程池,灵活回收线程,任务过多,会oom。
②ExecutorService executor = Executors.newFixedThreadPool(): 创建一个指定线程数量的线程池。提高了线程池的效率和线程的创建的开销,等待队列可能堆积大量请求,导致oom。
③ExecutorService executor = Executors.newSingleThreadPool(): 创建一个单线程,保证线程的有序,出现异常再次创建,速度没那么快。
④ExecutorService executor = Executors.newScheduleThreadPool(): 创建一个定长的线程池,支持定时及周期性任务执行。
14.四大拒绝策略
①new ThreadPoolExecutor.AbortPolicy(): 添加线程池被拒绝,会抛出异常(默认策略)。
②new ThreadPoolExecutor.CallerRunsPolicy(): 添加线程池被拒绝,不会放弃任务,也不会抛出异常,会让调用者线程去执行这个任务(就是不会使用线程池里的线程去执行任务,会让调用线程池的线程去执行)。
③new ThreadPoolExecutor.DiscardPolicy(): 添加线程池被拒绝,丢掉任务,不抛异常。
④new ThreadPoolExecutor.DiscardOldestPolicy(): 添加线程池被拒绝,会把线程池队列中等待最久的任务放弃,把拒绝任务放进去。
15.shutdown 和 shutdownNow 的区别?
① shutdown没有返回值,shutdownNow会返回没有执行完任务的集合。
②shutdown不会抛出异常,shutdownNow会抛出异常。
③shutdown会等待执行完线程池的任务在关闭,shutdownNow会给所以线程发送中断信号,然后中断任务,关闭线程池。
16.什么是死锁?
各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象。
17.造成死锁的四个必要条件
互斥: 当资源被一个线程占用时,别的线程不能使用。
不可抢占: 进程阻塞时,对占用的资源不释放。
不剥夺: 进程获得资源未使用完,不能被强行剥夺。
循环等待: 若干进程之间形成头尾相连的循环等待资源关系。
18.线程安全主要是三方面
原子性: 一个或多个操作,要么全部执行,要么全部不执行(执行的过程中是不会被任何因素打断的)。
可见性: 一个线程对主内存的修改可以及时的被其他线程观察到。
有序性: 程序执行的顺序按照代码的先后顺序执行。
保证原子性
使用锁 synchronized和 lock。
使用CAS (compareAndSet:比较并交换),CAS是cpu的并发原语)。
保证可见性
使用锁 synchronized和 lock。
使用volatile关键字 。
保证有序性
使用 volatile 关键字
使用 synchronized 关键字。
19.volatile和synchronized的区别
① volatile仅能使用在变量级别的,synchronized可以使用在变量、方法、类级别的
② volatile不具备原子性,具备可见性,synchronized有原子性和可见性。
③ volatile不会造成线程阻塞,synchronized会造成线程阻塞。
④ volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好。
20.synchronized和lock的区别
① synchronized是关键字,lock是java类,默认是不公平锁(源码)。
② synchronized适合少量同步代码,lock适合大量同步代码。
③ synchronized会自动释放锁,lock必须放在finally中手工unlock释放锁,不然容易死锁。
21.JMM(java内存模型)
java内存模型,一个抽象的概念,不是真是存在,描述的是一种规则或规范,和多线程相关的规则。需要每个JVM都遵循。
22.JMM的约定
①线程解锁前,必须把共享变量立即刷回主存。
②线程加锁前,必须读取主存中的最新值到工作内存中。
③加锁和解锁必须是同一把锁。
23.JMM的八个命令
为了支持JMM,定义了8条原子操作,用于主存和工作内存的交互。
lock(锁定): 作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock(解锁): 作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read(读取): 作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用。
load(载入): 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用): 作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎。
assign(赋值): 作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量。
store(存储): 作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以遍随后的write的操作。
write(写入): 作用于主内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中。
24.为什么要有JMM,用来解决什么问题?
解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。
四.jvm
垃圾回收机制详解
1.jvm是什么?
java虚拟机,是实现java跨平台的核心组件。
2.jvm的作用
java中所有的类,必须被装载到jvm中才能使用,装载由类加载器完成,.class这个类型可以在虚拟机运行,但不是直接和操作系统交互,需要jvm解释给操作系统,解释的时候需要java类库,这样就能和操作系统交互。
3.java文件的加载过程
.java -> .class -> 类加载器 -> jvm
4.jdk、jre、jvm的区别
jdk: 包含java运行环境和开发环境、jvm、java类库。
jre: 包含java运行环境和jvm、java类库。
jvm: java虚拟机,是跨平台的核心组件。
5.类加载器的作用
将.class文件装载到jvm中,实质就是把文件从硬盘写到内存。
6.类加载器的类型
引导类加载器(Bootstrap ClassLoader): c++编写,jvm自带的加载器,负责加载java核心类库,该加载器无法直接获取。
拓展类加载器(Extension ClassLoader): 加载jre/lib/etc目录下的jar包。
系统类加载器(Application ClassLoader): 加载当前项目目录下的类或jar包,最常用的加载器。
自定义加载器(Custom ClassLoader): 开发人员自定义的。需要继承ClassLoader
7.双亲委派机制的加载过程
①接到类加载的请求。
②向上委托给父类加载器,直到引导类加载器。
③引导类加载器检查能否加载当前这个类,如果能,使用当前加载器,请求结束,如果不能,抛出异常,通知子加载器进行加载。
④重复③。
8.双亲委派机制的优缺点
优点:保证类加载的安全性,不管那个类被加载,都会被委托给引导类加载器,只有类加载器不能加载,才会让子加载器加载,这样保证最后得到的对象都是同样的一个。
缺点:子加载器可以使用父加载器加载的类,而父加载器不能使用子加载器加载的类。
9.为什么要打破双亲委派机制
子加载器可以使用父加载器加载的类,而父加载器不能使用子加载器加载的类。
例如:使用JDBC连接数据库,需要用到 com.mysql.jdbc.Driver和DriverManager类。然而DriverManager被引导类加载器所加载,而com.mysql.jdbc.Driver被当前调用者的加载器加载,使用引导类加载器加载不到,所以要打破双亲委派机制。
10.打破双亲委派机制的方式
① 自定义类加载器,重写loadclass方法。
② 使用线程上下文类(ServiceLoader:使父加载器可以加载子加载器的类)。
11.jvm的每个部分储存的都是什么
方法区(线程共享): 常量池、静态(static)变量以及方法信息(方法名、返回值、参数、修饰符等)等。
堆(线程共享): 是虚拟机内存中最大的一块,储存的是实例对象和数组。
本地方法栈(线程不共享): 调用的本地方法,被native修饰的方法,java不能直接操作操作系统,所以需要native修饰的方法帮助。
虚拟机栈(线程不共享): 8大基本类型、对象引用、实例方法。
程序计数器(线程不共享): 每个线程启动是都会创建一个程序计数器,保存的是正在执行的jvm指令,程序计数器总是指向下一条将被执行指令的地址。
12.内存溢出(oom)和栈溢出
内存溢出的原因: (1)内存使用过多或者无法垃圾回收的内存过多,使运行需要的内存大于提供的内存。
(2)长期持有某些资源并且不释放,从而使资源不能及时释放,也称为内存泄漏。
解决: (1)进行jvm调优。-Xmx:jvm最大内存。-Xms:启动初始内存。-Xmn:新生代大小。 -Xss:每个虚拟机栈的大小。
(2)使用专业工具测试。
手动制造: 一直new对象就ok。
栈溢出原有: 线程请求的栈容量大于分配的栈容量。
解决: (1)修改代码 (2)调优 -Xss
手动制造: 一直调用实例方法。
13.垃圾回收的作用区域
作用在方法区和堆,主要实在堆中的伊甸园区。年轻代分为(伊甸园区和幸存区)
14.怎么判断对象是否可回收
可达性分析算法: 简单来说就是一个根对象通过引用链向下走,能走到的对象都是不可回收的。可作为根对象有: 虚拟机栈的引用的对象,本地栈的引用的对象,方法区引用的静态和常量对象。
引用计数算法: 每个对象都添加一个计数器,每多一个引用指向对象,计数器就加一,如果计数器为零,那么就是可回收的。
15.四种引用类型 强引用 软引用 弱引用 虚引用
强引用: 基于可达性分析算法,只有当对象不可达才能被回收,否则就算jvm满了,也不会被回收,会抛出oom。
软引用: 一些有用但是非必须的对象,当jvm即将满了,会将软引用关联对象回收,回收之后如果内存还是不够,会抛出oom。
弱引用: 不论内存是否够,只要开始垃圾回收,软引用的关联对象就会被回收。
虚引用: 最弱的引用和没有一样,随时可能被回收。
16.垃圾回收算法
(1)标记-清除算法(适用老年代): 先把可回收的对象进行标记,然后再进行清除。
优点: 算法简单。
缺点: 产生大量的内存碎片,效率低。
(2)复制算法(适用年轻代): 把内存分成两个相同的块,一个是from,一个是to,每次只使用一个块,当一个块满了,就把存活的对象放到另一个块中,然后清空当前块。主要用在年轻区中的幸存区。
优点: 效率较高,没有内存碎片。
缺点: 内存利用率低。
(3)标记-整理算法(适用老年代): 标记-清除算法的升级版,也叫标记-压缩算法,先进行标记,然后让存活对象向一端移动,然后清除掉边界以外的内存。
有点: 解决了内存利用率低和避免了内存碎片。
缺点: 增加了一个移动成本。
17.轻GC(Minor GC)和 重GC(Full GC)
轻GC: 普通GC,当新对象在伊甸园区申请内存失败时,进行轻GC,会回收可回收对象,没有被回收的对象进入幸存区,新对象分配内存极大部分都是在伊甸园区,所以这个区GC比较频繁。一个对象经历15次GC,会进入老年区,可以设置。
重GC: 全局GC,对整个堆进行回收,所以要比轻GC慢,因此要减少重GC,我们所说的jvm调优,大部分都是针对重GC。
18.什么时候会发生重GC
①当老年区满了会重GC:年轻区对象进入或创建大对象会满。
②永久代满了会重GC。
③方法区满了会重GC。
④system.gc()会重GC 。
⑤轻GC后,进入老年代的大小大于老年代的可用内存会,第一次轻GC进入老年代要2MB,第二次的时候会判断是否大于2MB,不满足就会重GC。
五.锁
1.悲观锁和乐观锁
悲观锁: 在修改数据时,一定有别的线程来使用,所以在获取数据的时候会加锁。java中的synchronized和Lock都是悲观锁。
乐观锁: 在修改数据时,一定没有别的线程来使用,所以不会添加锁。但是在更新数据的时候,会查看有没有线程修改数据。比如:版本号和CAS原理(无锁算法)。
2.悲观锁和乐观锁的场景
悲观锁: 更适合写操作多的场景,因为先加锁可以保证数据的正确。
乐观锁: 更适合读操作多的场景,因为不加锁会让读操作的性能提升。
3.自旋锁和自适应自旋锁
前言:因为线程竞争,会导致线程阻塞或者挂起,但是如果同步资源的锁定时间很短,那么阻塞和挂起的花费的资源就得不偿失。
自旋锁: 当竞争的同步资源锁定时间短,就让线程自旋,如果自旋完成后,资源释放了锁,那线程就不用阻塞,直接获取资源,减少了切换线程的开销。实现原理是CAS。
缺点:占用了处理器的时间,如果锁被占用的时间短还好,如果长那就白白浪费了处理器的时间。所以要限定自旋次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。
自适应自旋锁: 自旋次数不固定,是由上一个在同一个锁上的自旋时间和锁拥有者的状态决定。如果在同一个锁对象上,自旋刚刚获得锁,并且持有锁的线程在运行,那么虚拟机会认为这次自旋也可能成功,那么自旋的时间就会比较长,如果某个锁,自旋没成功获得过,那么可能就会直接省掉自旋,进入阻塞,避免浪费处理器时间。
4.无锁、偏向锁、轻量级锁、重量级锁
这四个锁是专门针对synchronized的,在 JDK1.6 中,对 synchronized 锁的实现引入了大量的优化,并且 synchronized 有多种锁状态。级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级。
无锁: 就是乐观锁。
偏向锁: 当只有一个线程访问加锁的资源,不存在多线程竞争的情况下,那么线程不需要重复获取锁,这时候就会给线程加一个偏向锁。(对比Mark Word解决加锁问题,避免CAS操作)
轻量级锁: 是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。(CAS+自旋)
重量级锁: 若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。(将除了拥有锁的线程以外的线程都阻塞)
5.公平锁和非公平锁
公平锁: 多个线程按照申请锁的顺序来获取锁。 Lock lock = new ReentrantLock(true); 默认是非公平锁,设置为true是公平锁。
优点:等待线程不会饿死。
缺点:CPU唤醒线程得开销比非公平锁要大。
非公平锁: 多个线程获取锁的顺序并不是按照申请锁的顺序。 sybchronized和lock都是非公平锁。
优点:减少唤醒线程得开销。
缺点:可能会出现线程饿死或者很久获得不了锁。
6.可重入锁
可重入锁: 也叫递归锁,同一个线程在外层方法获取了锁,在进入内层方法会自动获取锁。(前提:锁对象是同一个对象或者类)。ReentrantLock和synchronized都是可重入锁。
7.独享锁和共享锁
独享锁: 独占锁是指锁一次只能被一个线程所持有。如果一个线程对数据加上排他锁后,那么其他线程不能再对该数据加任何类型的锁。获得独占锁的线程即能读数据又能修改数据。synchronized和Lock的实现类就是独占锁。
共享锁: 共享锁是指锁可被多个线程所持有。如果一个线程对数据加上共享锁后,那么其他线程只能对数据再加共享锁,不能加独占锁。获得共享锁的线程只能读数据,不能修改数据。
8.互斥锁和读写锁
互斥锁: 是独享锁的实现,某一资源同时只允许一个访问者对其访问。具有唯一和排它性。
读写锁: 是共享锁的实现,读写锁管理一组锁,一个是只读的锁,一个是写锁。读锁可以在没有写锁的时候被多个线程同时持有,而写锁是独占的。写锁的优先级要高于读锁。并发度要比互斥锁高,因为可以拥有多个读锁。
9.分段锁
是锁的设计,不是具体的某一种锁,分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
ConcurrentHashMap的锁机制jdk1.7用的就是分段锁。
10.锁优化技术
锁粗化: 将多个同步块的数量减少,并将单个同步块的作用范围扩大,本质上就是将多次上锁、解锁的请求合并为一次同步请求。
锁消失: 锁消除是指虚拟机编译器在运行时检测到了共享数据没有竞争的锁,从而将这些锁进行消除。
六.Spring
1.spring是什么
轻量级开发框架,为java提供了基础架构支持,简化了企业级应用开发,让开发者只需要关注业务逻辑。
2.spring的设计核心是什么
IOC和AOP
3.IOC和AOP面试题
IOC:控制反转,将创建对象进行反转,因为正常都是程序员去创建对象,现在使用spring容器去创建,根据需求自动去创建对象。
对象实例化,通过spring容器进行创建和管理,spring通过DI(数据注入)实现IOC。
AOP:AOP也是以IOC为基础,面向切面编程,抽象化的面向对象,面向对象的补充和完善。把业务逻辑抽离出来,然后动态切入方法中,减少代码重复和解耦。简单来说:做到核心业务和非核心业务的耦合。
4.spring的优点和缺点
优点:
①解耦和方便开发:spring容器进行对象的创建和管理。
②AOP的支持:面向切面编程,实现日志和权限拦截等功能。
③声明事务的支持:通过配置进行事务管理,不需要手动编写。
④方便集成各种框架。
缺点:
①使用大量反射机制,占内存,不如直接调用效率高。
②没有做到依赖管理。
③集成的框架耦合度高,不易拆分。
5.spring中bean的作用域
①singleton(单例模式): 默认作用域,在spring容器中一个bean只创建一个实例,所有对bean的请求和引用都会返回这个实例。适用都是无状态的bean。
②prototype(原型模式): 每次请求都会为bean创建一个实例。适用都是有状态的bean。
③request(请求作用域): 为每一个HTTP请求创建一个实例,在请求完成以后,bean会失效,会被垃圾回收器回收。
④session(会话作用域): 为每一个HTTP会话创建一个实例,不同会话使用不同实例,session销毁,bean失效。
⑤global-session(全局作用域): Spring5 已经没有了。
有状态: 有数据存储功能。
无状态: 不会保存数据。
6.spring中bean的注入方式
①构造器注入
②Setter注入
③接口注入(灵活性和易用性差,Spring4已经废弃)
7.BeanFactory 和 ApplicationContext有什么区别?
是spring的核心接口,都可以作为容器,ApplicationContext是BeanFactory的子接口。
BeanFactory: 是spring最底层的接口,包含各种Bean的定义和Bean的管理。
ApplicationContext: 作为BeanFactory的派生,除了有BeanFactory的功能以外,还提供了更多的功能。
区别:
① BeanFactroy采用的是延迟加载形式来注入Bean的,使用到bean才会加载。ApplicationContext一次性加载所有bean。
② BeanFactory需要手动注册,而ApplicationContext则是自动注册。
③ BeanFactory不支持国际化,ApplicationContext支持国际化(实现MessageSource接口)。
8.循环依赖的情况,怎么解决?
循环依赖:A依赖B,B依赖C,C依赖A,形成了闭环。
①构造器的循环依赖: 这种依赖spring是处理不了的,直接抛出异常。
②单例模式下的setter循环依赖: 通过"三级缓存"处理循环依赖,能处理。
③多例模式下的setter循环依赖: 不能处理,会一直产生新的Bean,导致OOM。
9.spring中单例Bean是线程安全的吗?
不是,因为所有线程共享一个单例Bean,存在资源的竞争所以是线程不安全的,实际上大部分时间Bean是无状态的,所以说在某种程度上来说Bean其实是安全的。如果是有状态,那就需要开发人员修改bean的作用域。singleton改为prototype。
有状态: 有数据存储功能。
无状态: 不会保存数据。
10.spring如何处理线程的并发问题?
①把成员变量写在方法内。
②使用ThreadLocal,ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
③修改bean的作用域,singleton改为prototype。(@Scope(“prototype”))
④使用synchronized修饰。
11.spring中bean的生命周期
bean: 是由 Spring IoC 容器实例化、组装和管理的对象。
正常情况: 当bean不在被使用,就会被回收。
单例模式: spring中bean的生命周期分为:实例化Bean->Bean属性填充->初始化Bean->销毁Bean。
多例模式: spring无法进行管理,所以将生命周期交给用户控制。
12.spring Bean的扩展点
1.专用拓展点:用于单个 Bean 的扩展,定义 Bean 类时实现接口来扩展功能
(1) Aware 接口及子接口:属性填充的时候注入 Bean 信息或上下文等信息
使用 BeanNameAware接口 将当前 Bean 的名称注入到类中
public class Xinxin implements BeanNameAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
}
}
(2) InitializingBean 和 DisposableBean 接口
InitializingBean:在 Bean 初始化时添加自定义逻辑
DisposableBean :在 Bean 销毁时添加自定义逻辑
public class Xinxin implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
@Override
public void destroy() {
// do some destruction work
}
}
(3) 使用@Bean注解在Bean初始化时和Bean销毁时自定义逻辑
public class Xinxin{
public void init() {
// do some initialization work
}
public void destroy() {
// do some destruction work
}
}
@Configuration
public class AppConfig {
//initMethod 属性指定 Bean 初始化方法,destroyMethod 属性指定 Bean 销毁方法。
@Bean(initMethod = "init", destroyMethod = "destroy")
public Xinxin xinxin() {
return new Xinxin();
}
}
(4) 使用注解@PostConstruct(修饰的方法为Bean初始化时执行的方法)和@PreDestroy(修饰的方法为Bean销毁时执行的方法)
public class Xinxin{
@PostConstruct
public void init() {
// do some initialization work
}
@PreDestroy
public void destroy() {
// do some destruction work
}
}
- 通用扩展点:用于所有 Bean 的扩展,单独定义类实现接口来扩展功能
(1) BeanPostProcessor 接口:实现该接口可以在 Bean 初始化前后添加自定义逻辑。
@Component
public class XinxinInitProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// before initialization work
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// after initialization work
return bean;
}
}
(2)InstantiationAwareBeanPostProcessor 接口:实现该接口可以在 Bean 实例化前后添加自定义逻辑。
@Component
public class XinxinInstanceProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
// before instantiation work
return null;
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
// after instantiation work
return true;
}
}
(3) DestructionAwareBeanPostProcessor 接口:实现该接口可以在 Bean 销毁前添加自定义逻辑。
@Component
public class BeanDestroyProcessor implements DestructionAwareBeanPostProcessor {
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
// before destruction work
}
}
13.在 Spring中如何注入一个java集合?
Spring提供以下几种集合的配置元素:
想要注入java集合,就是注入集合类。
list: 类型用于注入一列值,允许有相同的值。
set: 类型用于注入一组值,不允许有相同的值。
map: 类型用于注入一组键值对,键和值都可以为任意类型。
props: 类型用于注入一组键值对,键和值都只能为String类型。
14.bean的自动装配
spring会在上下文中自动寻找,并自动给bean装配属性。之前属性需要我们手动注入。
15.spring用到了那些设计模式
工厂模式: beanFactory就用到了简单工厂模式。
单例模式: Bean默认为单例模式。
代理模式: AOP用到了JDK的动态代理模式。
模板模式: 减少代码冗余,Jdbc模板等。
观察者模式: 定义对象间的一对多的关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。spring监听器的实现就用了观察者模式。
16.spring的常用注解
@Component(任何层)、@Controller(表现层)、@Service(逻辑层)、@Repository(持久层): 被修饰的类,会被spring扫描到并注入到bean容器中(实例化对象)。
@Scope: 设置spring的作用域。
@Bean: 用于将方法返回值对象放入容器。
@Import: 在一个配置类中导入其它配置类的内容。
@AutoWired: 按照类型匹配注入。
@Qualifier: 和AutoWired联合使用,在按照类型匹配的基础上,在按照名称匹配。
@Resource: 按照名称匹配依赖注入。
@Configuration: 被此注解标注的类,会被 Spring 认为是配置类。
@ComponentScan: 用于对组件(Component)进行扫描。
@Transactional: 可以用于类和方法上,具有事务管理的功能
@Value: 将外部的值动态注入到 Bean 中。
@Value(“${}”):可以获取配置文件的值。
@Value(“#{}”):表示SpEl(Spring Expression Language是Spring表达式语言,可以在运行时查询和操作数据。)表达式通常用来获取 bean 的属性,或者调用 bean 的某个方法。
17.spring 事务实现方式有哪些?
编程式: beginTransaction()、commit()、rollback()等事务管理相关的方法,灵活度高,但是维护性差。
声明式: 利用注解或者xml配置,将业务和事务分离出来。
18.spring事务的实现方式和实现原理
spring事务就是对数据库事务的支持,没有数据库的事务支持,spring是无法提供事务
功能的。
19.说一下 spring 的事务隔离?
五大隔离级别。
ISOLATION_DEFAULT: 默认值,使用数据库的隔离级别。
ISOLATION_READ_UNCOMMITTED: 读未提交。
ISOLATION_READ_COMMITTED: 读已提交。
ISOLATION_REPEATABLE_READ: 可重复读。
ISOLATION_SERIALIZABLE: 序列化。
20.spring事务的传播行为
REQUIRED(默认):默认事务传播行为,存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
REQUIRE_NEW:它不管是否存在事务,它都会新开启一个事务来执行,新老事务相互独立的,外部事务抛出异常,并不会影响内部事务的一个正常提交。
NESTED:如果当前存在事务,就嵌套当前事务中去执行,如果当前没有事务,那么就新建一个事务,类似 REQUIRE_NEW这个样一个传播行为。
SUPPORTS:表示支持当前当前的事务,如果当前不存在事务,就以非事务的方式去执行。
NOT_SUPPORT: 总是非事务地执行,并挂起任何存在的事务。
MANDATORY:如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
NEVER:就是以非事务的方式来执行,如果存在事务则抛出一个异常。
七.SpringMVC
1.简单介绍下你对springMVC的理解?
是基于java的MVC设计模式的轻量级MVC框架,通过对Model、view、Controller分离,把web应用分成逻辑清晰的几部分,简化了开发,方便了开发人员的配合。
2.说一说SpringMVC的重要组件及其作用
前端控制器(DispatcherServlet): 接收请求、响应结果,解耦了其他组件。
处理器映射器(HandlerMapping): 根据url去查找对应的处理器(Handler)。
处理器适配器(HandlerAdapter): 执行处理器(Handler)。
处理器(Handler): 处理业务逻辑的类,程序员编写。
视图解析器(ViewResolver): 进行视图的解析,根据视图逻辑名将ModelAndView解析成真正的视图(view)。
视图(View): 是一个接口,它的实现类支持不同类型的视图,jsp,pdf等。
3.SpringMVC的工作原理或流程
①用户发送请求,前端控制器接到请求。
②前端控制器把请求分发给处理器映射器。
③处理器映射器根据url去找对应的处理器。
④获取处理器,返回给处理器映射器。
⑤处理器映射器返回给前端控制器。
⑥前端控制发给处理器适配器,请求执行处理器。
⑦处理器适配器通知处理器执行业务逻辑。
⑧然后处理器返回ModelAndView。
⑨处理器适配器把ModelAndView返回给前端控制器。
⑩前端控制器发给视图解析器,视图解析器根据视图名称去查询视图。
⑪返回真正的视图。
⑫渲染视图。
⑬返回视图,给前端控制器。
⑭然后响应用户的请求。
4.SpringMVC的优点
①支持各种视图。JSP、PDF等
②可以与spring集成。
③各个组件分工明确。解耦。
5.SpringMVC常用注解
@RequestMapping:用于处理请求的URL,可以用于方法或类。
@RequestBody:接受请求的json数据,转换成java对象。
@ResponseBody:将controller返回对象,转成json响应回去。
@RestController:相当于@Controller和@ResponseBody。
@PathVariable:用于接收路径参数,比如@RequestMapping(“/hello/{name}”)声明的路径,将注解放在参数前,即可获取该值
6.SpringMVC和struts2的区别
①SpringMVC入口是servlet(前端控制器),struts2入口是过滤器。
②SpringMVC基于方法开发,struts2基于类开发。
7.怎么实现SpringMVC拦截器
①实现HandlerInterceptor接口。
②继承适配器类。
8.SpringMvc的控制器是不是单例模式?如果是,有什么问题?怎么解决?
是单例模式,在多线程访问的时候有线程安全问题。
解决方案是不在控制器里面写可变状态量,如果需要使用这些可变状态,可以使用ThreadLocal机制解决,为每个线程单独生成一份变量副本,独立操作,互不影响。
9.在 SpringMVC 中拦截器的使用步骤是什么样的?
定义拦截器类: SpringMVC 为 我 们 提 供 了 拦 截 器 规 范 的 接 口 , 创 建 一 个 类 实 现 HandlerInterceptor,重写接口中的抽象方法。
preHandle 方法: 在调用处理器之前调用该方法,如果该方法返回 true 则请 求继续向下进行,否则请求不会继续向下进行,处理器也不会调用。
postHandle 方法: 在调用完处理器后调用该方法 。
afterCompletion 方法: 在前端控制器渲染页面完成之后调用此方法。
10.说一下SSM和SSH都代表哪些框架?
SSM:SpringMVC,Spring,Mybatis。
SSH:struts,Spring,Hibernate。
八.SpringBoot
1.谈谈你对springBoot的理解
是一站式解决方案,简化了spring繁重的配置,提供了各种启动器,让开发者更好上手。
2.为什么使用springBoot或springBoot的优点
(1)版本锁定: 解决maven依赖版本冲突问题
(2)起步依赖: 解决jar过多问题
(3)自动配置: 解决配置文件过多问题
(4)内置tomcat: 通过内置tomcat,不需要其他外置tomcat,也能运行javaEE。
3.springBoot与springCloud 区别
SpringBoot是快速开发的Spring框架,SpringCloud是完整的微服务框架,SpringCloud依赖于SpringBoot。
4.springBoot的核心配置文件有哪些,作用是什么
application 和 bootstrap 配置文件。
application: 用于项目的自动化配置。
bootstrap: 一些不能被覆盖的属性和加密或解密的场景。比application要先加载。
5.springBoot配置文件有几种类型,区别是什么
.yml和.properties
区别主要是书写方法不一样,.properties加载优先级大于.yml。
server.port = 8088 //.properties方式
server: //.yml方式
port:8088
6.什么是热部署?springBoot怎么实现热部署
热部署: 修改代码不需要重启,就可以实现编译并部署到服务器上。(1)在开发时修改代码不需要重启服务,节省了时间。(2)部署在服务器上的程序,可以在不停止的情况下进行升级。
①使用devtools。在配置文件中把devtools.restart.enabled 设置为 true。每次都需要build一下才行。
②idea设置实现热部署。
7.什么是 JavaConfig?
JavaConfig 是 Spring 社区的产品,它提供了配置 Spring IoC 容器的纯 Java 方法,有助于避免使用 XML 配置。
8.Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的或介绍一下 @SpringBootApplication 注解
@SpringBootApplication:包含了三个注解。
①SpringBootConfiguration: 实现了@Configuration注解,实现配置文件的功能。
②@EnableAutoConfiguration: 打开自动配置的功能,也可以关闭某个自动配置的选项, 例
如: java 如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
③@ComponentScan: Spring组件扫描。
9.springBoot的启动器starter
可以通过启动器去集成其他技术:web、redis、maybatis等。
在pom配置 spring-boot-starter-web,就可以进行web开发。
原理:会加载这些配置类,放进spring容器中,就可以从容器中获取这些类。
10.springBoot项目的启动方法
打包
maven插件运行
main启动
11.如何在 Spring Boot 启动的时候运行一些特定的代码?
可以实现接口 ApplicationRunner或者 CommandLineRunner,这两个接口实现方式一样(项目启动完成后执行) ,它们
都只提供了一个 run 方法,在run方法写你的业务逻辑。
12.springBoot的全局异常处理
@ControllerAdvice:开启了全局异常的捕获。然后自定义一个方法使用@ExceptionHandler注解然后定义捕获异常的类型即可对这些捕获的异常进行统一的处理。
@ControllerAdvice
public class ExceptionUtil{
@ExceptionHandler(value =Exception.class)
@ResponseBody
public String exceptionHandler(Exception e){
System.out.println("全局异常捕获:"+e);
return "全局异常捕获,错误原因:"+e.getMessage();
}
}
13.spring Boot常用注解
@SpringBootApplication: SpringBootConfiguration配置类、componentScan扫描包、EnableAutoConfiguration导入其他配置类
@RestController: @ResponseBody和@Controller的作用。
@Component,@Service,@Controller,@Repository: 将类注入容器。
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping: 映射请求,只能接收的对应的请求。
@AutoWired: 按照类型匹配注入。
@Qualifier: 和AutoWired联合使用,在按照类型匹配的基础上,在按照名称匹配。
@Resource: 按照名称匹配依赖注入。
@Bean: 用于将方法返回值对象放入容器。
@RequestParam: 获取查询参数。即url?name=这种形式
@RequestBody: 该注解用于获取请求体数据(body),get没有请求体,故而一般用于post请求。@PathVariable: 获取路径参数。即url/{id}这种形式。
@Value: 将外部的值动态注入到 Bean 中。
@Value(“${}”):可以获取配置文件的值。
@Value(“#{}”):表示SpEl(Spring Expression Language是Spring表达式语言,可以在运行时查询和操作数据。)表达式通常用来获取 bean 的属性,或者调用 bean 的某个方法。
九.SpringCloud
一.微服务
1.微服务是什么?
分布式,多个模块,每一个模块都是一个单独的系统。
2.你知道哪些RPC框架
RPC(Remote Procedure Call):远程过程调用。
Dubbo: 国内最早开源的 RPC 框架,由阿里巴巴公司开发并于 2011 年末对外开源。
Spring Cloud: 国外公司 2014 年对外开源的 RPC 框架。
3.springCloud和Dubbo有什么区别
①定位不同: springCloud微服务架构下的一站式解决方案;Dubbo主要用于服务的调用和治理。
②生态环境不同: springCloud依靠spring平台,更完善;Dubbo相对匮乏。
③调用方式不同: springCloud是采用Http协议做远程调用,接口一般是Rest风格,比较灵活;Dubbo是采用Dubbo协议,接口一般是Java的Service接口,格式固定。
简单来说: springCloud是品牌机,Dubbo是组装机。
4.SpringCloud由什么组成
Spring Cloud Eureka: 服务注册与发现。
Spring Cloud Feign: 服务接口调用。
Spring Cloud Ribbon: 客户端负载均衡。
Spring Cloud Hystrix: 断路器。
Spring Cloud Zuul: 服务网关。
Spring Cloud Config: 分布式统一配置管理。
等等。
二.Spring Cloud Eureka
1.Eureka包含几个组件
Eurake Client(客户端): 负责将这个服务的信息注册到Eureka Server中。
Eureka Server(服务端): 注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号。
2.Eureka的工作原理
原理: 系统中的其他服务使用Eureka的客户端将其连接到Eureka服务端中,并且保持心跳,这样工作人员可以通过Eureka服务端来监控各个微服务是否运行正常。
3.说一下什么是Eureka的自我保护机制
如果Eureka服务端在一定时间内没有接收到某个微服务的心跳(默认90s),Eureka服务端会进入自我保护模式,在该模式下Eureka服务端会保护服务注册表中的信息,不在删除注册表中的数据,当网络故障恢复后,Eureka服务端节点会自动退出自我保护模式。
4.什么是CAP原则
CAP原则: 又称CAP定理,指的是在一个分布式系统中,强一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
强一致性(Consistency): 访问所有的节点,得到的数据结果都是一样的。
可用性(Availability): 保证每个请求不管成功或者失败都有响应。
分区容错性(Partiton tolerance): 系统中任意信息的丢失或失败不会影响系统的继续运作。
5.都是服务注册中心,Eureka比Zookeeper好在哪里?
在分布式系统中分区容错性是必须要保证的,因此只能保证A或C,只能AP和CP。
我们在服务使用中可以容忍注册中心返回几分钟之前的注册信息,但是不能接受服务直接down掉不可用。
Zookeeper: 保证的是CP,当主机的节点发生网络故障了,会选取新的主节点,响应时间过长。
Eureka: 保证的是AP,Eureka的节点都是平等的,不存在主机从机,因此Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像Zookeeper那样是整个注册中心瘫痪。
6.Nacos和Eureka的区别
共同点
①都支持服务的注册和拉取。
②都支持服务提供者以心跳检测来判断是否健康(临时实例)。
不同点
①nacos支持注册中心主动询问服务提供者的状态(非临时实例)。
②nacos支持注册中心消息变更主动推送。
③心跳不正常会被剔除(临时实例)。
三.Spring Cloud Ribbon
1.Ribbon的作用
主要功能是提供客户端的软件负载均衡算法,默认是轮询算法。
2.Ribbon的原理
Ribbon会从注册中心获取到服务的信息,然后通过轮询算法,从中选择一台机器。
3.Ribbon和nginx的区别
nginx: 反向代理实现负载均衡,相当于从nginx服务器进行请求转发。
Ribbon: 客户端负载均衡,全程都是客户端操作。
四.Spring Cloud Feign
1.Feign的作用
Feign集成了Ribbon,Feign 是一个声明web服务客户端,这使得编写web服务客户端更容易,远程调用更加简单。
2.SpringCloud有几种调用接口方式
Feign
RestTemplate
3.Ribbon和Feign调用服务的区别
Ribbon: 需要我们自己构建http请求,然后通过RestTemplate去发给其他服务,比较繁琐。
Feign: 不需要自己构建Http请求,直接接口调用就行。
五.Spring Cloud Hystrix
1.说一说什么是服务雪崩
服务雪崩:多个服务相互调用时,A调B,B调C,C调D等等更多调用,那么如果中间调用需要很长时间,然后再去调用A,那么占用的资源就越来越多,导致系统崩溃。
2.Hystrix断路器是什么
防止服务雪崩的一个工具,具有服务降级、服务熔断(@HystrixCommand(fallbackMethod = “hystrixById”) //失败了调用的方法)、服务隔离、监控等防止雪崩的技术。
3.什么是服务降级、服务熔断、服务隔离
服务降级: 当出现请求超时、资源不足时(线程或者信号量),会进行服务降级,就是去返回fallback方法的结果。
服务熔断: 当失败率(网络故障或者超时造成)达到阈值自动触发降级,是一种特殊的降级。
服务隔离: 为隔离的服务开启一个独立的线程,这样在高并发情况下,也不会影响该服务。一般使用线程池实现(还有信号量方式实现)。
4.服务降级和服务熔断的区别
区别: 降级每个请求都会发送过去,而熔断不一定,达到失败率,请求就不会再去发送了。请求出错时熔断返回的是fallback数据,而熔断则是一段时间不会去访问服务提供者。
比如:
①降级:A调B,发送10个请求,即使每个请求都超时,也会去请求B。
②熔断:A调B,发送10个请求,失败率设置为50%,如果5个请求失败,此时失败率到了50%,那么后面的5个请求就不会走到B。
六.Spring Cloud Zuul 和 Spring Cloud Gateway
1.什么是Zuul微服务网关
接收所有的请求,并且将不同的请求转发至不同的微服务模块。
2.Zuul的应用场景
①过滤器
②权限认证
③降级限流
④安全
3.Gateway
功能强大丰富,性能好,维护性好,实现异步,可以替代Zuul网关。
七.Spring Cloud Config
1.什么是Spring Cloud Config
集中管理配置文件,不需要每个服务编写配置文件,服务会向配置中心拉取配置。
实时刷新(需要spring cloud bus)。
八.Spring Cloud Alibaba Nacos
1.Nacos可以做什么?
注册中心和配置中心。
2.Eureka保证是AP,那么Nacos保证的是什么?默认是什么?
Nacos可以是AP也可以是CP。默认是AP
十.MySQL
1.char和varchar的区别
①char设置多少长度就是多少长度,varchar可以改变长度,所以char的空间利用率不如varchar的空间利用率高。
②因为长度固定,所以存取速度要比varchar快。
③char适用于固定长度的字符串,比如身份证号、手机号等,varchar适用于不固定的字符串。
2.数据库的三大范式
第一范式(1NF): 保证字段不可再分,保证原子性。
第二范式(2NF): 满足1NF前提下,表的每一列都必须和主键有关系。消除部分依赖关系。
第三范式(3NF): 满足2NF前提下,表的每一列比必须和主键有直接关系,不能是间接关系。消除传递依赖
3.你了解sql的执行顺序吗?
⑧select ⑨distinct(去重) ⑥聚合函数
①from 表1
③inner join | left join | right join 表2
②on(连接条件) 表1.字段 = 表2.字段
④where 查询条件
⑤group by(分组) 字段
⑦having 分组过滤条件
⑩order by(排序) 字段
⑪limit(分页) 0,10
4.索引是什么
是一种高效获取数据的数据结构,相当于目录,更快的找到数据,是一个文件,占用物理空间。
5.索引的优点和缺点
优点:
①提高检索的速度。
②索引列对数据排序,降低排序成本。
③mysql 8之后引入了,隐藏索引,当一个索引被隐藏就不会被优化器所使用,就可以看出来索引对数据库的影响,有利于调优。
缺点:
①索引也是一个文件,所以会占用空间。
②降低更新的速度,因为不光要更新数据,还要更新索引。
6.索引的类型
①普通索引: 基本索引类型,允许定义索引的字段为空值和重复值。
②唯一索引: 索引的值必须唯一,允许定义索引的字段为空值。
③主键索引: 索引的值必须唯一,不可以为空。
④复合索引: 多个字段加索引,遵守最左匹配规则。
⑤全局索引: 只有在 MyISAM 引擎上才能使用。
7.索引怎么设计(优化)
①选择唯一性索引:值是唯一的,查询的更快。
②经常作为查询条件的字段加索引。
③为经常需要排序、分组和联合操作的字段建立索引:order by、group by、union(联合)、distinct(去重)等。
④限制索引个数:索引数量多,需要的磁盘空间就越多,更新表时,对索引的重构和更新就很费劲。
⑤表数据少的不建议使用索引(百万级以内):数据过少,有可能查询的速度,比遍历索引的速度都快。
⑥删除不常用和不再使用的索引。
⑦用类型小的类型做索引:比如:int和BIGINT能用int就使用int。因为类型小,查询速度快和索引占用的空间更少。
⑧使用前缀索引,要是字符串越长,那么索引占的空间越大,并且比较起来就时间就越长。
8.怎么避免索引失效(也属于sql优化的一种)
①某列使用范围查询(>、<、like、between and)时, 右边的所有列索引也会失效。
②不要对索引字段进行运算。
③在where子句中不要使用 OR、!=、<>和对值null的判断。
④避免使用’%'开头的like的模糊查询。
⑤字符串不加单引号,造成索引失效。
9.索引的数据类型
Hash: 查询时调用Hash函数获得地址,回表查询实际数据。(InnoDB和MylSAM不支持,Memory支持)。
B+树: 每次从根节点出发去查询,然后得到地址,回表查询实际数据。
10.索引为什么使用树结构
因为可以加快查询效率,而且可以保持有序。
11.二叉查找树、B树、B+树
二叉查找树(二叉排序树、二叉搜索树): 一个节点最多两个子节点(左小右大),查询次数和比较次数都是最小的,但是索引是存在磁盘的,当数据量过大的时候,不能直接把整个索引文件加载到内存,需要分多次IO,最坏的情况IO的次数就是树的高度,为了减少IO,需要把树从竖向变成横向。
B树( B- ): 是一种多路查询树,每个节点包含K个子节点,节点都存储索引值和数据,K是B树的阶(树高被称为树的阶)。虽然比较的次数比较多,但是是在内存的比较,可以忽略不计,但是B树IO的次数要比二叉查找树要少,因为B树的高度可以更低。
B+树: B树的升级版,只有叶子节点储存的是索引值指向的数据库的数据。
12.为什么使用B+树不用B树
①B树只适合随机检索,而B+树同时支持随机检索和顺序检索(因为叶子节点相当于链表,保存索引值都是有序的)。
顺序检索: 按照序列顺序遍历比较找到给定值。
随机检索: 不断从序列中随机抽取数据进行比较,最终找到结果。
②减少了磁盘IO,提高空间利用率: 因为B+树非叶子节点不会存放数据,只有索引值,所以非叶子节点可以保存更多的索引值,这样B+树就可以更矮,减少IO次数。
③B+树适合范围查找: 这才是关键,因为数据库大部分都是范围查找,B+树的叶子节点是有序链表,直接遍历就行,而B树的范围查找可能两个节点距离很远,只能通过中序遍历去查找,所以使用B+树更合适。
中序遍历: (根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)
13.最左匹配原则
最左优先,以最左边为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between and、like)就会停止匹配。
例如:Z表建立联合索引 (a,b,c)
//这样索引abc列都起效,因为符合最左匹配原则,where子句几个搜索条件顺序调换不影响查询结果,因为Mysql中有查询优化器,会自动优化查询顺序
select * from Z where a = 1 and b = 2 and c = 3
//因为a列是起点,没有a列匹配不上,所以索引失效
select * from table_name where b = 2 and c = 3
//因为连续不到b,所以只有a列索引生效
select * from table_name where a = 1 and c = 3
14.Mysql怎么查看是否使用到索引或怎么查看sql执行计划
使用explain
例如:explain select * from 表名 where 条件
结果:会查出key,key就是你使用的索引。还有type这个字段,可以看到索引是全表扫描还是索引扫描等等。
type字段内容性能对比:ALL < index < range ~ index_merge < ref < eq_ref < const < system
15.一条sql查询非常慢,我们怎么去排查和优化?
排查:
(1) 开启慢查询。
(2) 查看慢查询日志(定位低效率sql,命令:show processlist)。
(3) 使用explain查看sql的执行计划(看看索引是否失效或者性能低)
优化:
sql优化 + 索引 + 数据库结构优化 + 优化器优化
16.MylSAM和InnoDB、Memory的区别
MylSAM: mysql5.5之前的存储引擎,是表锁(悲观锁)级别的,不支持事务和外键。
InnoDB: mysql5.5之后的存储引擎,是行锁(乐观锁)级别的,支持事务和外键。
Memory: 内存数据库引擎,因为在内存操作,所以读写很快,但是Mysql服务重启,会丢失数据,不支持事务和外键。
17.什么是事务
事务和隔离级别详解及实际应用
事务是对数据库中一系列操作进行统一的回滚或者提交的操作,主要用来保证数据的完整性和一致性。
18.事务的四大特性(ACID)
原子性(Atomicity): 要么全部成功要么全部失败。
一致性(Consistency): 事务执行前和事务执行后,原本和数据库一致的数据仍然一致。
隔离性(Isolation): 事务与事务之间互不干扰。
持久性(Durability): 事务一旦被提交了,那么对数据库中的数据的改变就是永久的。
19.脏读、不可重复读、幻读
脏读: 也叫"读未提交",顾名思义,就是某一事务A读取到了事务B未提交的数据。
不可重复读: 在一个事务内,多次读取同一个数据,却返回了不同的结果。实际上,这是因为在该事务间隔读取数据的期间,有其他事务对这段数据进行了修改,并且已经提交,就会发生不可重复读事故。
幻读: 在同一个事务中,第一次读取到结果集和第二次读取到的结果集不同。像幻觉一样所以叫幻读。
从上面可以看出脏读和不可重复读是基于数据值的错误,幻读是基于条数增加或者减少的错误
20.事务的隔离级别?
① read uncommited(读取未提交内容): 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。读取未提交的数据,也被称之为脏读(Dirty Read)
② read committed(读取提交内容): 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。一个事务只能看见已经提交事务所做的改变。可解决脏读
③ repeatable read(可重读): 这是MySQL的默认事务隔离级别,同一事务的多个实例在并发读取数据时,会看到同样的数据。不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。可解决脏读、不可重复读
④ serializable(可串行化) : 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。可解决脏读、不可重复读、幻读。
21.怎么优化数据库
①SQL优化
②加缓存
③分表
④读写分离
22.SQL优化
①不要用select *,要使用具体字段。
②使用数值代替字符串,比如:0=唱,1=跳,2=rap。
③避免返回大量数据,采用分页最好。
④使用索引,提升查询速度,不宜建太多索引,不能建在重复数据比较多的字段上。
⑤批量插入比单条插入要快,因为事务只需要开启一次,数据量太小体现不了。
⑥避免子查询,优化为多表连接查询。
⑦尽量使用union all替代union,因为union会自动去重。
23.常用的聚合函数
①sum(列名) 求和
②max(列名) 最大值
③min(列名) 最小值
④avg(列名) 平均值
⑤first(列名) 第一条记录
⑥last(列名) 最后一条记录
⑦count(列名) 统计记录数不包含null值 count(*)包含null值。
24.几种关联查询
内连接(inner join): 查询两个表匹配数据。
左连接(left join): 查询左表全部行以及右表匹配的行。
右连接(right join): 查询右表全部行以及左表匹配的行。
25.in和exists的区别
in(): 适合子表(子查询)比主表数据小的情况。
exists(): 适合子表(子查询)比主表数据大的情况。
26.drop、truncate、delete的区别
速度: drop > truncate > delete。
回滚: delete支持,truncate和drop不支持。
删除内容: delete表结构还在,删除部分或全部数据,不释放空间。truncate表结构还在,删除全部数据,释放空间。drop表结构和数据不在,包括索引和权限,释放空间。
十一.Mybatis
1.mybatis是什么
是一个半自动ORM(对象关系映射)框架,内部封装了JDBC,开发时只需要关注SQL就可以,不需要花费精力去处理数据库驱动、数据库连接等过程。
2.mybatis的优点和缺点
优点:
①因为内置JDBC,所以减少了大量的代码冗余。
②基于SQL编程,很灵活,SQL写在xml文件里,与程序解耦,便于管理。
③兼容各种数据库。
④提供映射标签,对象和数据库字段的相互映射。
缺点:
①SQL的编写量很大的时候,对开发人员的SQL功底有要求。
②SQL依赖数据库,导致数据库移植性差,不能随意更换数据库。
3.mybatis和Hibernate的区别
①mybatis是半自动化对象关系映射框架(提供数据库和实体类的映射关系,还需要自己写sql),Hibernate是全自动化对象关系映射框架(提供数据库和实体类的映射关系,自动生成sql)。
②mybatis控制sql执行,灵活度高。Hibernate虽然灵活度没那么高,但是可以节省代码,效率高。
③mybatis数据库无关性差,移植性差,Hibernate数据库无关性好,移植性好,靠的是强大的hql语言。
④Hibernate日志更加完善,而mybatis只是基础记录功能。
4.#{}和${}的区别
SQL注入问题:
select * from user_table where username=' "+userName+" ' and password=' "+password"'
传入参数:
username = "1' OR '1'='1";
password = "1' OR '1'='1";
变为:导致我们直接进入系统
select * from t_user where (username = '1' or '1'='1') and (password = '1' or '1'='1');
#{}: 就相当于标识符 ?,可以有效解决SQL注入问题。
${}: 相当于字符串拼接,直接拼接,会有sql注入问题。
5.resultType和resultMap的区别
resultType: 返回我们的实体类、Integer、Map等。
resultMap: 返回自定义实体类。
6.怎么进行批量操作
使用foreach标签。
<foreach collection="集合名" item="自定义集合里每个元素的名字" open="(" close=")" separator=",">
sql语句
</foreach>
7.一个xml文件对应一个dao接口,dao接口的工作原理是什么?dao接口的方法可以重载吗?
原理: dao接口就是使用JDK代理,生成一个代理对象,代理对象会拦截dao接口的方法,根据接口的全限定名+方法名 去判断是那个xml文件的sql,不能重载,因为是根据 全限定名+方法名 去判断。
8.mybatis怎么实现的分页
(1) 自己编写分页: 获取数据的list,然后编写代码进行分页。
(2) 利用sql进行分页: 比如mysql的limit。
(3) RowBounds实现分页
//service层调用dao层接口,start开始的条数,limit每页几条
public List<RoleBean> queryByPage(int start, int limit) {
return roleDao.queryUsersByPage(new RowBounds(start, limit));
}
//dao层接口加入RowBounds参数,就可以实现分页
public List<UserBean> queryUsersByPage(RowBounds rowBounds);
9.mybatis支持延迟加载(懒加载)吗?原理是什么?
支持的对象: Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。
概念: 延迟加载的原理是在查询时只加载部分数据,当需要访问未加载的数据时再进行加载。这样可以减少查询所需的时间和资源,提高系统性能。
原理: MyBatis实现延迟加载的方式是使用代理对象,在访问未加载的数据时触发代理对象的方法,从而进行数据的加载。延迟加载可以通过配置文件或注解来实现。
10.mybatis的一级、二级缓存
一级缓存: 默认开启,在同一个sqlSession会话下的操作都会存入缓存,请求查询的数据都会存入缓存。当会话,有增删改操作,并提交的时候,缓存清空,没有失效时间,会随会话结束而结束。
二级缓存: 默认关闭,会话是全局的,一个sqlSessionFactory里面可能有多个sqlSession,sqlSession之间共享数据。当一级缓存关闭或者清除的时候会存进二级缓存。当会话中,有增删改的操作,并且commit时,缓存就会清空。有失效时间,cache默认是一小时,每当有数据存进来,就会判断是否超过一小时,如果超过就释放二级缓存。实体类还需要实现反序列化(Serializable接口)。
11.Xml文件中有哪些常用标签
<select>、<insert>、<delect>、<update>、<resultMap>、<sql>、<include>、<where>、<foeach>、<choose>、<if>、<when>、<trim>等。
12.如何获取自动生成的(主)键值?
使用usegeneratedkeys=”true”,在新增成功之后,会返回id,到你的对象中。
<insert id="insertname" usegeneratedkeys="true" keyproperty="id">
新增sql
</insert>
十二.Redis
1.redis是什么
是由c语言开发的高性能的非关系型数据库。
2.redis的存储结构有哪些
String:键值对。应用场景:缓存、短信验证码等。
set key value //设置值
get key //获取值
Hash: 键值对,可以存放多个键值对,适合存储对象。应用场景:缓存等,比String更节省空间。
hset key field value //设置值
hget key field //获取值
List: 相当于链表。应用场景:消息队列等。
LPUSH key element1 element2 element3 ... //左边插入元素,头插法
LPOP key //移除并返回列表左侧的第一个元素,没有则返回nil
Set: HashSet类似,无序,不可重复。应用场景:共同好友等。
SADD key member1 member2 member3 ... //向set中添加一个或多个元素
SREM key member1 //移除set中的指定元素
Zset: 类似TreeSet,不可重复,有一个权重参数,按这个排序。应用场景:排行榜。
ZADD key score1 member1 score2 member2 score3 member3 //添加一个或多个元素到sorted set ,如果已经存在则更新其score值
ZREM key member1 //删除sorted set中的一个指定元素
3.为什么要用redis和redis为什么那么快
①内存操作,速度快。每秒读11万次,写8万次。
②支持事务和持久化。
③单线程,避免了线程的切换。redis 6 引进了多线程。
④采用IO多路复用,单线程处理多个连接请求。
4.缓存雪崩、缓存穿透、缓存击穿
缓存雪崩: 缓存中的数据大量过期,然后大量请求去访问数据库,导致数据库压力过大或者down机。或者redis宕机了。
解决:
①使缓存过期时间分布比较均匀。
②设置高可用集群。
缓存穿透: 缓存和数据库都没有要的数据,然后会一直请求,给数据库压力。
解决:
①给缓存设置一个空值或者默认值。
②使用布隆过滤器。先通过过滤器判断数据是否存在,存在再继续向下查。
缓存击穿: 某个key过期,恰巧有大量请求访问这个key,然后导致数据库压力过大。
解决:
①使用互斥锁,只让一个请求去访问,然后把数据带到缓存,然后剩余请求再去访问请求。
②设置缓存不过期。
5.redis的持久机制
RDB(默认): 一定时间内将内存数据以快照(记录某一时刻数据,相当于拍了一张照片)形式保存到硬盘中。某个时间点把数据写到临时文件,然后替换上次持久化的文件。
# 在 10秒之后,如果至少有 1 个 key 发生变化,Redis就会自动创建快照
save 10 1
优点: 恢复大的数据集时,比AOF效率更高。
缺点: 不安全,数据丢失。
AOF: 会把每次写的命令记录到日志文件(同一个日志文件,不会替换),redis重启会将持久化的日志文件恢复。如果两种持久化都开启,优先恢复AOF。
# 开启 AOF 持久化
appendonly yes
# 每次有数据修改发生时都会写入 AOF 文件,速度缓慢但是最安全
appendfsync always
# 每秒钟同步一次,显示地将多个写命令同步到硬盘。AOF 默认使用的
appendfsync everysec
# 让操作系统决定何时进行同步,速度最快
appendfsync no
优点: 安全,几乎不会丢失数据。
缺点: AOF文件比RDB文件大,且恢复速度慢。
6.redis的过期策略
定时删除: 每个key都需要创建一个定时器,到时间就会清除key,所以对内存很友好,但是会占用cpu的大量资源去处理过期。
惰性删除: 用到key的时候再去判断过没过期,过期就清除,可以节省cpu的资源,但是对内存不友好。可能出现大量过期的key没有清除。
定期删除: 定时删除和惰性删除的结合体,每隔一段时间抽取设置过期的key检测是否过期(默认是1s执行10次清除,每次抽取5个检测),过期就清除。
# 1秒检查10次
hz 10
# 一次抽取的个数,默认是5个
maxmemory-samples 5
7.redis的淘汰策略
redis内存满了,进行内存淘汰,删除一部分key。redis 4.0 添加的lfu策略。
# 默认的最大内存设置为1GB
maxmemory 1GB
①volatile-lru: 针对设置了过期时间的key,使用lru算法(最近最少使用的key:根据时间)进行淘汰。
②allkeys-lru: 针对所有key使用lru算法进行淘汰。
③volatile-lfu: 针对设置了过期时间的key,使用lfu算法(最近最少使用:根据计数器)进行淘汰。
④allkeys-lfu: 针对所有key使用lfu算法进行淘汰。
⑤volatile-random: 从所有设置了过期时间的key中使用随机淘汰机制进行淘汰。
⑥allkeys-random: 针对所有的key使用随机淘汰机制进行淘汰。
⑦volatile-ttl: 删除过期时间最早(剩余存活时间最短的)。
⑧no-eviction(默认): 不删除键,操作报错。
8.redis怎么设置高可用或者集群
①主从复制: 一个主,一个或多个从,从节点在主节点复制数据。主节点负责写,从节点负责读。可以更好的分担主节点的压力,但是如果主节点宕机了,会导致部分数据不同步。
②哨兵模式(重点): 也是一种主从模式,哨兵定时去查询主机,如果主机太长时间没有相应,多个哨兵就投票选出新的主机。提高了可用性,但是在选举新的主节点期间,还是不能工作。
③Cluster集群模式: 采用多主多从(一般都是三主三从),按照规则进行分片,每台redis节点储存的数据不一样,解决了单机储存的问题。还提供了复制和故障转移功能。配置比较麻烦。
9.redis实现分布式锁
分布式锁: 是控制分布式系统不同进程共同访问共享资源的一种锁的实现。秒杀下单、抢红包等等业务场景,都需要用到分布式锁。redis可以当分布式锁。
①命令setnx + expire分开写。
setnx key value //新增一个key,key存在返回1,不存在不增加返回0
expire key 100 //key过100秒过期。单位是秒
问题:无法保证原子性,setnx锁了之后,然后出现问题,过期时间没有设置,那么锁就是永久的了(别的线程就永远获取不到锁了)。
②setnx + value值是过期时间。
setnx key 过期时间 //把过期时间放到value,解决过期时间没有设置的问题,对比过期时间和当前时间就可以知道是否过期。
问题:取当前时间,分布式要求时间同步。
③使用Lua脚本(包含SETNX + EXPIRE两条指令:保证原子性)。
④set的扩展命令(SET key value[EX seconds][PX milliseconds][NX|XX])。
EX seconds:将键的过期时间设置为 seconds 秒。
PX milliseconds:将键的过期时间设置为 milliseconds 毫秒。
NX:只在键不存在的时候,才对键进行设置操作。SET key value NX 等同于 SETNX key value
XX:只在键已经存在的时候,才对键进行设置操作
set key value ex 10 nx //key不存在才能新增,并且设置过期时间为10s。
问题:(1) 锁过期释放了,业务还没执行完。a获取锁,执行时间超过10秒,然后被b线程获取锁,导致代码执行顺序不一致。
(2) 锁被误删,因为(1),a没执行完释放锁,被b获取,然后a执行完,删除锁,然后b可能还没执行完。
⑤set ex px nx + 校验唯一随机值,再删除。
把 value设置成一个随机数,删除的时候对比随机数,一致再删除。
问题:还是存在业务没执行完,锁释放。
⑥开源框架~Redisson。
方案五还存在业务没执行完,锁释放问题。
Redisson:给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。
⑦多机实现的分布式锁Redlock。假设有5个主节点,再五台服务器上运行。
按顺序向5个master节点请求加锁
根据设置的超时时间来判断,是不是要跳过该master节点。锁过期时间是10ms,超时时间是20ms,那么就跳过。
如果大于等于3个节点(N/2+1)加锁成功,并且使用的时间小于锁的有效期,即可认定加锁成功啦。
如果获取锁失败,再所有主节点解锁,没获取到锁也会解锁,防止九年义务教育漏网之鱼。
10.分布式锁的特征
互斥性: 任意时刻,只有一个客户端能持有锁
锁超时释放: 持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁
可重入性: 一个线程如果获取了锁之后,可以再次对其请求加锁
高性能和高可用: 加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效
安全性: 锁只能被持有的客户端删除,不能被其他客户端删除
11.redis的应用场景
①缓存
②数据库
③排行榜
④计数器
⑤消息队列
⑥分布式锁
⑦共享Session
m: 从所有设置了过期时间的key中使用随机淘汰机制进行淘汰。
⑥allkeys-random: 针对所有的key使用随机淘汰机制进行淘汰。
⑦volatile-ttl: 删除过期时间最早(剩余存活时间最短的)。
⑧no-eviction(默认): 不删除键,操作报错。
8.redis怎么设置高可用或者集群
①主从复制: 一个主,一个或多个从,从节点在主节点复制数据。主节点负责写,从节点负责读。可以更好的分担主节点的压力,但是如果主节点宕机了,会导致部分数据不同步。
②哨兵模式(重点): 也是一种主从模式,哨兵定时去查询主机,如果主机太长时间没有相应,多个哨兵就投票选出新的主机。提高了可用性,但是在选举新的主节点期间,还是不能工作。
③Cluster集群模式: 采用多主多从(一般都是三主三从),按照规则进行分片,每台redis节点储存的数据不一样,解决了单机储存的问题。还提供了复制和故障转移功能。配置比较麻烦。
9.redis实现分布式锁
分布式锁: 是控制分布式系统不同进程共同访问共享资源的一种锁的实现。秒杀下单、抢红包等等业务场景,都需要用到分布式锁。redis可以当分布式锁。
①命令setnx + expire分开写。
setnx key value //新增一个key,key存在返回1,不存在不增加返回0
expire key 100 //key过100秒过期。单位是秒
问题:无法保证原子性,setnx锁了之后,然后出现问题,过期时间没有设置,那么锁就是永久的了(别的线程就永远获取不到锁了)。
②setnx + value值是过期时间。
setnx key 过期时间 //把过期时间放到value,解决过期时间没有设置的问题,对比过期时间和当前时间就可以知道是否过期。
问题:取当前时间,分布式要求时间同步。
③使用Lua脚本(包含SETNX + EXPIRE两条指令:保证原子性)。
④set的扩展命令(SET key value[EX seconds][PX milliseconds][NX|XX])。
EX seconds:将键的过期时间设置为 seconds 秒。
PX milliseconds:将键的过期时间设置为 milliseconds 毫秒。
NX:只在键不存在的时候,才对键进行设置操作。SET key value NX 等同于 SETNX key value
XX:只在键已经存在的时候,才对键进行设置操作
set key value ex 10 nx //key不存在才能新增,并且设置过期时间为10s。
问题:(1) 锁过期释放了,业务还没执行完。a获取锁,执行时间超过10秒,然后被b线程获取锁,导致代码执行顺序不一致。
(2) 锁被误删,因为(1),a没执行完释放锁,被b获取,然后a执行完,删除锁,然后b可能还没执行完。
⑤set ex px nx + 校验唯一随机值,再删除。
把 value设置成一个随机数,删除的时候对比随机数,一致再删除。
问题:还是存在业务没执行完,锁释放。
⑥开源框架~Redisson。
方案五还存在业务没执行完,锁释放问题。
Redisson:给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。
⑦多机实现的分布式锁Redlock。假设有5个主节点,再五台服务器上运行。
按顺序向5个master节点请求加锁
根据设置的超时时间来判断,是不是要跳过该master节点。锁过期时间是10ms,超时时间是20ms,那么就跳过。
如果大于等于3个节点(N/2+1)加锁成功,并且使用的时间小于锁的有效期,即可认定加锁成功啦。
如果获取锁失败,再所有主节点解锁,没获取到锁也会解锁,防止九年义务教育漏网之鱼。
10.分布式锁的特征
互斥性: 任意时刻,只有一个客户端能持有锁
锁超时释放: 持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁
可重入性: 一个线程如果获取了锁之后,可以再次对其请求加锁
高性能和高可用: 加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效
安全性: 锁只能被持有的客户端删除,不能被其他客户端删除
11.redis的应用场景
①缓存
②数据库
③排行榜
④计数器
⑤消息队列
⑥分布式锁
⑦共享Session