@toc
Java八股👇👇👇
1、Object类下面有哪些方法
object构造方法、getClass、hashCode、equals、clone、toString、notify、notifyAll、wait
2、HashMap的put过程
1.判断键值对数组tab是否为空或为null,如果为空则执行resize()进行扩容;
2.根据键值key计算hash值得到索引i,如果tab[i]==null,则直接新建节点添加,进入第6步,如果tab[i]不为空,进入第3步;
3.判断tab[i]的首个元素的key是否和传入key一样并且hashCode相同,如果相同直接覆盖value,否则转进入第4步;
4.判断tab[i] 是否为treeNode(红黑树),如果是红黑树,则直接在树中插入新节点,否则进入第5步;
5.遍历tab[i]判断是否遍历至链表尾部,如果到了尾部,则在尾部链入一个新节点,然后判断链表长度是否大于8,如果大于8的话把链表转换为红黑树,否则进入6;遍历过程中若发现key已经存在,直接覆盖value,进入第6步;
6.插入成功后,判断size是否超过了阈值(当前容量*负载因子),如果超过,进行扩容。
3、线程池7大核心参数,线程池执行任务的过程。
详见这个文章!!
4、线程有哪些状态?等待(WAITING)和阻塞(BLOCKED)的区别?Wait超时后会怎样?
等待阻塞(WAITING)和同步阻塞(BLOCKED)的区别?
- Blocked:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
- Waiting:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中;
- 一个线程由等待队列迁移到同步队列时,线程状态将会由wating转化为blocked。可以这样说,blocked状态是处于wating状态的线程重新焕发生命力的必由之路。
Wait超时后会怎样?
未在等待时间内被唤醒,则当前线程也会自动“醒来”,所谓的“醒来”只不过是从等待队列到了锁池队列,在获取到锁之前并不会继续执行
5、哈希值有什么用?
可以把哈希值简单地理解成是一段数据(某个文件,或者是字符串)的DNA,或者身份证,它常常用来判断两个文件是否相同。
6、HasMap的put过程中,hashcode方法为什么要(n - 1) & hash
?如果重写hashcode方法都为1会怎么样?
hashcode方法为什么要(n - 1) & hash
?
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。Hash 值的范围加起来前后大概大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。
如果重写hashcode方法都为1会怎么样?
一直会发生哈希冲突,全部结点在那一个下标下形成链表,然后转化为红黑树。
7、计算存储下标时&操作比%操作的好处?长度采用2^n的好处?
可以接15问。
后面的数叫做除数,除号前面的数叫做被除数。
取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。
8、HTTP有哪些请求方法,分别代表什么意思
9、什么是seesion共享?
在分布式系统中假设第一次访问服务A生成一个sessionid并且存入cookie中,第二次却访问服务B客户端会在cookie中读取sessionid加入到请求头中,但是在服务B通过sessionid没有找到对应的数据那么它创建一个新的并且将sessionid返回给客户端,这样导致seesion不能共享
解决:
- 通过cookie:很不安全,不推荐
- 改用token,不用seesion
- 使用spring-session以及集成好的解决方案,存放在redis中(推荐)
我们讲一下第三种方法的原理:
就是当Web服务器接收到http请求后,当请求进入对应的Filter进行过滤,将原本需要由web服务器创建session的过程转交给Spring-Session进行创建,本来创建的会话保存在Web服务器内存中,通过Spring-Session创建的会话信息可以保存第三方的服务中,如:redis,mysql等。Web服务器之间通过连接第三方服务来共享数据,实现Session共享!
10、协程是什么
是一种基于线程之上,但又比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。
协程的特点:
- 线程的切换由操作系统负责调度,协程由用户自己进行调度,因此减少了上下文切换,提高了效率。
- 适用于被阻塞的,且需要大量并发的场景。
11、synchronized原理
JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。
具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。
其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。
而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁。
流程图如下:
12、jdk1.6后对synchronized做了哪些优化
JDK1.6对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略
13、什么是fail—fast?
fail-fast是一种错误检测机制,一旦检测到可能发生错误,就立马抛出异常,程序不继续往下执行。狭义上来说是针对多线程情况下的集合迭代器而言的。
比如说一个线程在修改集合,而另一个线程在迭代它。这种情况下,迭代的结果是不确定的。如果检测到这种行为,一些 Iterator(比如说 ArrayList 的内部类 Itr)就会选择抛出该异常。这样的迭代器被称为 fail-fast 迭代器
比如在集合的
for each 循环中
,尝试remove元素就会抛出异常,集合遍历其实是通过迭代器 Iterator 配合 while 循环实现的。而Iterator 使用了 fail-fast 的保护机制。
14、面向对象的特点,分别解释一下?
面向对象的三个基本特征是:封装、继承、多态
封装
封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承
继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。
- 多态性
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
15、seesion的存储方式,优缺点?
16、 java访问修饰符的作用范围
17、枚举类和普通类的区别
- 枚举类可以实现一个或多个接口,使用menu定义的枚举直接继承了java.long.Enum类,而不是继承Object类。其中java.long.Enum类实现了java.long.Serializable和java.long.Comparable两个接口。
- 使用enum定义、非抽象的枚举默认修饰符为final,因此枚举不能派生子类。
- 枚举的构造器只能使用private访问控制符,如果省略了枚举的访问修饰符其默认为private修饰;如果加强制定访问修饰符则只能使用private。
- 枚举的所有实例必须在枚举的第一行显示列出,否则这个枚举永远都不能生产实例,列出这些实例时系统会自动添加public static final修饰,无需程序员显式添加。
- 所有的枚举类都提供了一个values方法,该方法可以很方便的遍历所有的枚举值。
- 常量值地址唯一,可以用==直接对比,性能会有提高。
18、说说红黑树
19、为什么要使用泛型?泛型的原理
指定泛型之后,我们去取出数据后就不再需要进行强制类型转换了,这样就减少了发生强制类型转换的风险。
泛型原理:
在 Java 中的 泛型,常常被称之为 伪泛型,就是在实际代码的运行中,将实际类型参数的信息擦除掉了。
真泛型:泛型中的类型是真实存在的。
伪泛型:仅于编译时类型检查,在运行时擦除类型信息。
为什么不直接用真泛型呢?假如要用真泛型那么有以下两个问题
- 修改 JVM 源代码,让 JVM 能正确的的读取和校验泛型信息
- 为了兼容老程序,需为原本不支持泛型的 API 添加一套泛型 API
所以Java选择类型擦除:
- 不再为参数化类型创造新类了,同时在编译期间将泛型类型中的类型参数全部替换 Object
20、HashMap为什么使用红黑树而不是AVL树?
- 因为 AVL 树比红黑树保持着更加严格的平衡,所以 AVL 树查找效果会比较快,如果是查找密集型任务使用 AVL 树比较好,相反插入密集型任务,使用红黑树效果就比较好
- AVL 树的旋转比红黑树的旋转更加难以平衡和调试,如果两个都给 O(lgn) 查找, AVL 树可能需要 O(log n) 旋转,而红黑树最多需要两次旋转使其达到平衡
21、HashMap转换为红黑树的阈值为什么是8?
因为链表的时间复杂度是 n/2 ,红黑树时间复杂度是 logn ,当 n 等于 8 的时候, log8 要比 8/2 小,这个时候红黑树的查找速度会更快一些
22、红黑树为什么是小于 6 的时候转为链表,而不是 7 的时候就转为链表呢?
频繁的从链表转到红黑树,再从红黑树转到链表,开销会很大,特别是频繁的从链表转到红黑树时,需要旋转
23、HashMap 为什么是线程不安全的?(比面试突击详细)
HashMap 的线程不安全主要体现在两个方面:扩容时导致的死循环 & 数据覆盖。
- 扩容时导致的死循环,这个问题只会在 1.7 版本及以前出现,因为在 1.7 版本及以前,扩容时的实现,采用的是头插法,可能会导致循环指向,从而在获取数据get()的时候陷入死循环
在并发下: 若当前线程此时获得entry节点,但是被线程中断无法继续执行,此时线程二进入transfer函数,并把函数顺利执行,此时新表中的某个位置有了节点,之后线程一获得执行权继续执行,因为并发transfer,所以两者都是扩容的同一个链表,当线程一执行到e.next = new table[i] 的时候,由于线程二之前数据迁移的原因导致此时new table[i] 上就有ertry存在,所以线程一执行的时候,会将next节点,设置为自己,导致自己互相使用next引用对方,因此产生链表,导致死循环。
尾插法:元素插入的时候都是从尾部插入,这样新进来的就在头部,后进来的就在尾部,扩容的时候,先进来的先出,指向next和扩容前方向一致,所以不存在循环指向的问题。
还有个问题, 1.8 版本是没有解决的,那就是数据覆盖问题
- 假设现在线程 A 和线程 B 同时进行 put 操作,特别巧的是这两条不同的数据 hash 值一样,并且这个位置数据为 null ,那么是不是应该让线程 A 和 B 都执行 put 操作。假设线程 A 在要进行插入数据时被挂起,然后线程 B 正常执行将数据插入了,然后线程 A 获得了 CPU 时间片,也开始进行数据插入操作,那么就将线程 B 的数据给覆盖掉了。因为 HashMap 对 put 操作没有进行加锁的操作,那么就不能保证下一个线程 get 到的值,就一定是没有被修改过
24、Reentranlock怎么支持可打断机制的?打断之后Node节点状态的变化?
lock内部有一个lockInterruptibly()
,它内部会调用acquireInterruptibly()
这个方法判断当前线程是否被中断,如果中断就会抛出异常InterruptedException
。然后当外部调用interrupt()方法
自我中断的时候,就会抛出异常了
打断后节点的state变为CANCELLED
25、两个线程交替打印1-100
public class TestPrintNumber {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number,"线程1");
Thread t2 = new Thread(number,"线程2");
t1.start();
t2.start();
}
}
class Number implements Runnable{
private int i = 0;
@Override
public void run() {
while (true){
synchronized (this) {
notify();
if(i < 100){
i++;
System.out.println(Thread.currentThread().getName()+"---"+i);
}else{
break;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
26、直接内存什么时候GC
27、HashMap的负载因子为啥是0.75
- 如果是0.5 , 那么每次达到容量的一半就进行扩容,默认容量是16, 达到8就扩容成32,达到16就扩容成64, 最终使用空间和未使用空间的差值会逐渐增加,空间利用率低下。 如果是1,那意味着每次空间使用完毕才扩容,在一定程度上会增加put时候的时间。
28、JVM的分代年龄为什么是15?而不是16,20之类的呢?
HotSpot虚拟机的对象头其中一部分用于存储对象自身的运行时数据,官方称它为“Mark word”
,(这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit),在32位的HotSpot虚拟机中,只有4bit
用来存储对象分代年龄,而4个bit位能表示的最大数就是15
29、抽象类与接口区别
从语法层面:
- 接口的方法默认是public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法
- 接口中的实例变量默认是final类型的,而抽象类中则不一定
- 一个类可以实现多个接口,但最多只能实现一个抽象类
- 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
设计层面上的区别:
抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。
30、内存泄漏怎么排查?
- 用JDK自带的工具jvisualvm查看那个区域比较异常
- 可以
dump
下来那个地方的文件 - 然后可以用
mat
进行分析,其中哪些代码片段占用内存比较大,一般就是这块存在泄漏行为
31、浅拷贝和深拷贝怎么实现?
浅拷贝实现:
- 对象的类实现Cloneable接口;
- 覆盖Object类的clone()方法(覆盖clone()方法,访问修饰符设为public,默认是protected,但是如果所有类都在同一个包下protected是可以访问的);
- 在clone()方法中调用super.clone();
深拷贝实现:通过序列化
实现深克隆
- 在Java里深克隆一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象
- 现在有个
SerializationUtils.clone(T object);
就可以实现了序列化:把对象写到流里
反序列化:把对象从流中读出来
32、synchronized 和 volatile 的区别?
volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
volatile仅能使用在变量级别,synchronized则可以使用在方法,类等.
volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性.
volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
当一个域的值依赖于它之前的值时,volatile就无法工作了,如n=n+1,n++等。如果某个域的值受到其他域的值的限制,那么volatile也无法工作
使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。
33、lock和volatile的底层实现?
lock: Lock底层实现基于AQS实现
volatile:
可见性原理:
- 如果对声明了volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写会到系统内存。这一步确保了如果有其他线程对声明了volatile变量进行修改,则立即更新主内存中数据。
有序性原理:
- 加入多个分段锁浪费内存空间。
- 生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
- 为了提高 GC 的效率
35、那为什么要用synchronized不用reentrantLock?(接上一问)
- 减少内存开销
假设使用reentrantLock来获得同步支持,那么每个节点都需要通过继承AQS来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。 - 获得JVM的支持
reentrantLock毕竟是API这个级别的,后续的性能优化空间很小。
synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得synchronized能够随着JDK版本的升级而不改动代码的前提下获得性能上的提升。
36、HashMap1.7/1.8扩容机制?
1.7: 需要满足两个条件
- 当前数据存储的数量(即size())大小必须大于等于阈值
- 当前加入的数据发生了hash冲突
就是hashmap在存值的时候(默认大小为16,负载因子0.75,阈值12),可能达到最后存满16个值的时候,再存入第17个值才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。
1.8: 当前存放新值
(注意不是替换已有元素位置时)的时候已有元素的个数大于等于阈值
37、一个char类型的字符能否存放中文
Java中的一个char采用的是Unicode编码集,占用两个字节,而一个中文字符也是两个字节,因此Java中的char是可以表示一个中文字符的。
38、ArrayBlockingQueue和LinkedBlockingQueued的区别
ArrayBlockingQueue:
- 基于数组实现的一个阻塞队列,在创建对象时必须指定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的。
- 查看源码就可以知道
ArrayBlockingQueue
生产者方放入数据、消费者取出数据都是使用同一把重入锁
LinkedBlockingQueue:
- 基于链表实现的一个阻塞队列,在创建对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE
- LinkedBlockingQueue生产者和消费者分别使用两把重入锁来实现同步,所以可以提高系统的并发度
39、BigDecimal的用处有哪些?
Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。比如一般和钱有关的可以定义成BigDecimal
40、ConcurrentHashMap里的size()方法是如何获得Map的长度的?
- JDK1.7 中,是先不加锁计算三次,如果三次结果不一样在加锁。
- JDK1.8 size 是通过对
baseCount
和 辅助内部类counterCell
进行 CAS 计算,最终通过 baseCount 和 遍历 CounterCell 数组得出 size。 - JDK 8 推荐使用
mappingCount
方法,因为这个方法的返回值是 long 类型,不会因为 size 方法是 int 类型限制最大值。
41、动态代理和静态代理的区别?适用场景?
静态代理: 需要提前实现接口编写代理类,在代码运行之前,代理类的.class文件就已经存在
动态代理: 不需要提前实现接口编写代理类,在代码运行时,由JVM来动态的创建代理类
详细的内容可以看一下这篇文章
42、过滤器和拦截器的区别?
实现原理不同
过滤器和拦截器 底层实现方式大不相同,
过滤器
是基于函数回调
的拦截器
则是基于Java的反射机制(动态代理)
实现的。使用范围不同
过滤器
的使用要依赖于Tomcat等容器,导致它只能在web程序中使用拦截器(Interceptor)
它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Swing等程序中触发时机不同
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
- 拦截的请求范围不同
过滤器
几乎可以对所有进入容器的请求起作用拦截器
只会对Controller中请求或访问static目录下的资源请求起作用。
43、Synchronized那个阶段CPU会飙升?
在锁升级过程中,处于轻量级锁状态一直自旋的时候消耗大量CPU
44、Hash冲突的解决方法有哪些?
1、再哈希
再哈希法又叫双哈希法,有多个不同的Hash函数,当发生冲突时,使用第二个,第三个,….,等哈希函数去计算地址,直到无冲突。
虽然不易发生聚集,但是增加了计算时间。
2、开放寻址法
所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到
3、拉链法(hashmap就是这么做的)
个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,如果发生冲突,就把节点添加到链表中去
45、什么是符号引用?
在JVM的类加载过程中,在解析阶段,Java虚拟机会把类的符号引用替换为直接引用。
字面量:可以理解为字面意思的常量。比如,字符串字面量:”abc”;整型字面量:123。比如:int a = 123; a是变量,123是字面量
符号引用可以理解为 任意形式的字面量,只要能准确定位到目标就行。最终还是会转化成直接引用来访问目标。
而像可以直接访问到目标的指针就算一个直接引用。
46、如何监控GC,能不能使用日志查询?
一般我是用jdk自带的监控工具jvisualvm
来查看。可以通过日志来观察,通过加入 -XX:+PrintGCDetails
参数则可以打印详细GC信息至控制台。
47、HashMap中null为键值的元素存在何处?
放在索引为0的位置
48、GC回收碰到跨代引用的对象怎么办?
跨代引用现象: minorGC可达性分析的时候有些对象扫描不到,因为老年代中的某个成员变量就在新生代中,那么这些对象是不能被回收的。这就叫跨代引用
解决: 在新生代引入记录集(Remember Set)的数据结构,避免整个老年代都加入GC Roots的扫描范围。hotpot
是使用的卡表。
卡表类似于一个数组,数组中的每个元素存放了 卡页的地址,卡页则是存放在老年代。每个卡页中可以存放多个对象,只要有1个存在跨代引用那么就把卡页标记为1。在年轻代GC的时候,会查询数组,如果有标记为1的元素就会查询到卡页,然后全部作为GCRoots
49、ArrayList不用迭代器删除会有什么问题, 为什么迭代器就不会?
会报一个ConcurrentModificationException
,也就是并发修改异常(可以往fail-fast
上扯一扯),用迭代器的话,迭代器的remove方法会修改expectedModCount,从而使modCount与之相等。
modCount是集合添加元素、删除元素的次数,expectedModCount是预期的修改次数
50、雪花算法如何解决时钟回拨?
时间回拨问题:由于机器的时间是动态的调整的,有可能会出现时间跑到之前几毫秒,如果这个时候获取到了这种时间,则会出现数据重复
- 采用直接抛异常方式:这种很不友好,太粗暴
- 在内存里把过去1个小时之内生成的每一毫秒的每台机器生成的id都在内存里,保存最大的那个id。如果发生了时钟回拨,此时你看看时钟汇报到了之前的哪一毫秒里去,直接接着在那一毫秒里的最大的id继续自增就可以了。
51、G1相较于CMS的缺点?
G1 优点:
- 停顿时间短;
- 用户可以指定最大停顿时间;
- 不会产生内存碎片
缺点:
G1 需要记忆集 (具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存
52、ArrayList扩容后旧的ArrayList去哪里了?
扩大容量之后,将原来的数组copy到扩大后的容量中,调用了copyOf方法
53、for循环里面string用+会发生什么?
54、反射为什么效率低?
- invoke 方法会对参数做封装和解封操作
- 需要校验参数
- 需要检查方法可见性
55、Java 为什么支持反射?
答案是Java运行时仍然拥有类型信息,它包含了这个类一切:它有哪些字段、哪些方法,各是何种保护级别等等,还有这个类依赖于哪些类。在Java中,类信息以对象的形式存放,这些对象是一种元对象,它们的类型就是Class。拥有了这些信息,无论是动态创建对象还是调用某些方法都是轻而易举的。
56、一个线程OOM后,其他线程还能运行吗?
能运行。当一个线程抛出OOM异常后,它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行
57、CAS性能什么时候比锁差?
单核CPU的情况下,直接阻塞比独占cpu自旋快
58、Object作为HashMap的key的话,对Object有什么要求?
Hashmap不允许有重复的key,所以要重写它的hashcode和equal方法,以便确认key是否重复
59、当concurrenthashmap扩容了之后分段锁数量会改变吗?
在一个ConcurrentHashMap创建后Segment的个数是不能变的,扩容过程过改变的是每个Segment的大小。
60、多个线程之间共享数据的方式?
1.如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象有那个共享数据
2.如果每个线程执行的代码不同,这时候需要使用不同的Runnable对象。
将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象,每个线程对共享数据的操作方法也分配到那个对象身上去完成
61、HashMap、ConcurrentHashMap扩容过程
62、线程池是如何实现线程复用的?
当Thread的run方法执行完一个任务之后,会循环地从阻塞队列中取任务来执行,这样执行完一个任务之后就不会立即销毁了
63、静态编译和动态编译的区别【链接编译的过程】?
- 动态编译的 可执行文件 需要附带一个的 动态链接库 ,在执行时,需要调用其对应动态链接库中的命令。优点一方面是缩小了执行文件本身的体积,另一方面是加快了编译速度,节省了 系统资源 。缺点一是哪怕是很简单的程序,只用到了链接库中的一两条命令,也需要附带一个相对庞大的链接库
- 静态编译就是编译器在编译可执行文件的时候,将可执行文件需要调用的对应动态链接库(.so)中的部分提取出来,链接到可执行文件中去,使可执行文件在运行的时候不依赖于动态链接库。
64、Java内部类的种类,以及作用?
成员内部类、局部内部类、静态内部类、匿名内部类
作用:
每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整
65、动态链接和静态链接的区别?
- 动态链接是只建立一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在运行时再装入;
- 而静态链接是把所有的代码和数据都复制到本模块中,运行时就不再需要库了。
66、AQS的condition原理?
AQS中维护了一个队列来控制线程的执行,condition中使用了另一个等待队列来实现条件的判断,condition必须在aqs的acquire获取锁后使用,调用condition.await()方法将添加一个node到等待队列中,在调用signal()或signalAll()后将此节点移出condition的等待队列放到锁的等待队列中去竞争锁,取到锁后继续执行后续逻辑。
67、Stringbuilder扩容流程?
首先会把容量扩充为(2*旧容量)+2
,如果还是放不下,那就直接开辟为旧长度+要存进来的长度
数据库👇👇👇
1、MySQL索引结构,InnoDB的聚簇索引和普通索引。
mysql采取的索引结构有B+树和哈希表。在mysql中,只有memory的存储引擎显式支持哈希索引。
聚簇索引: 不是单独的索引类型,而是一种数据存储方式,指的是数据文件跟索引文件存储在一起
非聚簇索引: 数据文件跟索引文件分开存放
2、回表的概念、最左匹配原则
3、B+树相比于B树的优点。B+树好于B树,那么B树有哪些使用场景?以及B+树、B树、红黑树的区别
b+树的优点:
- b+树的中间节点不保存数据,所以磁盘页能容纳更多节点元素
- b+树查询必须查找到叶子节点,b树只要匹配到即可不用管元素位置,因此b+树查找更稳定
- 对于范围查找来说,b+树只需遍历叶子节点链表即可,b树却需要重复地中序遍历
b树的优点:
- 对于在内部节点的数据,可直接得到,不必根据叶子节点来定位。
B树使用场景:mongoDB数据库
①B树和 B+树的区别:
- B/B+树用在磁盘文件组织、数据索引和数据库索引中。其中B+树比B 树更适合实际应用中操作系统的文件索引和数据库索引
- B+树中所有叶子节点都是通过指针连接在一起,而B树不会。
②B树和红黑树的区别:
最大的区别就是红黑树树的深度较高,在磁盘I/O方面的表现不如B树。这也是不用红黑树的原因。
上面的进化之路只只讲到了红黑树,这里补充一下为啥有了红黑树还有B树:因为红黑树每个结点只能存储1个数据,数据量较大时,也会导致查找时间复杂度较大,所以就出现了B树
4、MySQL的隔离级别,幻读的概念
mysql隔级别有:
①读未提交
②读已提交
③可重复读(InnoDB 存储引擎的默认)
④串行化
5、MySQL的默认隔离级别是RR,如何解决幻读的问题(RR级别已经解决了幻读)?
使用间隙锁(Gap Lock),(间隙锁,锁的就是两个值之间的空隙)。其他事务不能在锁范围内插入数据,这样就防止了别的事务新增幻影行。
6、MVCC的工作原理。Read view如何实现数据可见性判断?
详细见这篇文章
工作原理:
MVCC是多版本并发控制机制,顾名思义支持MVCC的数据库表中每一行数据都可能存在多个版本,对数据库的任何修改的提交都不会直接覆盖之前的数据,而是产生一个新的版本与老版本共存,通过读写数据时读不同的版本来避免加锁阻塞。MVCC的实现主要依赖于数据库在每个表中添加的三个隐藏字段以及事务在查询时创建的快照(read view)和数据库的数据版本链(Undo log)。
实现数据可见性判断:
①所谓 read view就是在某一时刻给事务创建 snapshot(快照),把 当时状态(包括活跃读写事务数组) 记下来,之后的所有操作根据其事务ID(即 trx id)与 快照中的状态作比较,以此判断数据行对于本事务的可见性。
7、Redis为什么这么快?
主要原因还是存储在内存中,数据操作快。采用了单线程,避免了不必要的上下文切换以及竞争。还一个原因是key-value键值对数据操作简单。
8、Zset的底层实现
跳表。跳表的详细概念可以看这篇文章
9、如何保证数据库和Redis的数据一致性,先更新数据库再删除缓存。先删除缓存再更新数据库这两个方法分别存在什么问题?
1、先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据
2、先删除缓存再更新数据库在高并发的情况下可能会有以下情况:数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。
10、Mysql索引的优缺点
优点:
1、大大减少了服务器需要扫描的数据量
2、帮助服务器避免排序和临时表
3、将随机io变成顺序io
缺点:
1、创建索引和维护索引需要时间、空间成本,这个成本随着数据量的增加而加大
2、会降低表的增删改的效率,因为每次增删改索引需要进行动态维护,导致时间变长
11、关系型数据库和非关系型数据库的区别、各自的优势
首先一般非关系型数据库是基于CAP模型,而传统的关系型数据库是基于ACID模型的
区别:
关系型数据库:
高度组织化结构化数据,数据和关系都存储在单独的表中,SQL语言划分成不同的模块语句进行操作,比如数据操纵语言,数据定义语言,严格的一致性,基础事务的相关操作。
非关系型数据库:
存储方式有更多的选择:”键-值”对存储,列存储,文档存储,图形数据库等,没有声明性查询语言,没有预定义的模式,非结构化和不可预知的数据,高性能,高可用性和可伸缩性。
非关系型数据库的优势:
- 性能
NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。 - 可扩展性
同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
关系型数据库的优势:
- 复杂查询
可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。 - 事务支持
使得对于安全性能很高的数据访问要求得以实现。
12、讲讲 group by 的实现原理
下面有一张表 t,存储了不同日期对应的不同品类的销量,具体数据如下:
现在我们要统计2019年1月1到1月3期间每个品类的总销量,这个需求我们就可以用 group by 来实现,实现代码如下:
select cat ,sum(sales)
from
t
where sale_date between "2019/1/1" and "2019/1/3"
group by cat
上面代码中的 group by 具体执行过程是什么样子的呢?我们看一下下面这这张图。
通过上图我们可以看出 group by 会对所有的数据先根据 cat 字段进行分组,然后针对分组后的数据在组内进行聚合运算(计数、求和、求均值等),最后再将聚合后的每组数据进行汇总就得到了我们想要的结果
13、讲讲redis混合持久化方式?
重启Redis时,我们很少使用rdb来恢复内存状态,因为会丢失大量数据。我们通常使用AOF日志重写,
但是AOF重写性能相对rdb来说要慢很多,这样在Redis实例很大的情况下,启动需要花费很长的时间。
save命令和bgsave命令都可以生成RDB文件。
- save命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在Redis服务器阻塞期间,服务器不能处理任何命令请求。
- 而bgsave命令会创建一个子进程,由子进程来负责创建RDB文件,父进程(即Redis主进程)则继续处理请求。bgsave命令执行过程中,只有fork子进程时会阻塞服务器
Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。
AOF在进行(aof文件里可能有太多没用指令,所以aof会定期根据内存的最新数据生成aof文件)时 , 将重写这一刻之前的内存rdb快照文件的内容和增量的AOF修改内存数据的命令日志文件存在一起,都写入新的,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,原子的覆盖原有的AOF文件,完成新旧两个AOF文件的替换。
混合持久化文件结构:
14、可以说下数据库范式吗?
第一范式(1NF):数据库表的每一列都是不可分割的原子项
应该拆分为:
第二范式(2NF):在第一范式的基础上,每个表必须有且仅有一个数据元素为主键(Primary key),其他属性需完全依赖于主键
第三范式(3NF):在第二范式的基础上,数据表中的每一列都和主键直接相关,而不能间接相关
很明显,这里的城市人口、特色等属性都仅仅依赖于用户所在的城市,而不是用户,只能算间接的关系。
因此最好的做法是将城市相关的属性分离到一个城市信息表中。
15、Sql注入原理以及如何预防?
原理:
sql注入的原理是将sql代码伪装到输入参数中,传递到服务器解析并执行的一种攻击手法。也就是说,在一些对server端发起的请求参数中植入一些sql代码,server端在执行sql操作时,会拼接对应参数,同时也将一些sql注入攻击的“sql”拼接起来,导致会执行一些预期之外的操作。
预防:
- 加一些判断条件,用一些正则过滤
- 过滤和转义特殊字符
- 利用预编译:比如preparedStatment,这样的话那些注入的东西都会被当作参数,而不会被解析
16、redis为什么可以做分布式锁
1、Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。
2、Redis的SETNX
命令可以方便的实现分布式锁。
setNX(SET if Not eXists)
语法:SETNX key value
返回值:设置成功,返回 1 ;设置失败,返回 0 。
当且仅当 key 不存在时将 key 的值设为 value,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。
综上所述,可以通过setnx的返回值来判断是否获取到锁,并且不用担心并发访问的问题,因为Redis是单线程的,所以如果返回1则获取到锁,返回0则没获取到。当业务操作执行完后,一定要释放锁,释放锁的逻辑很简单,就是把之前设置的key删除掉即可,这样下次又可以通过setnx该key获取到锁了。
17、可重复读隔离下为什么会产生幻读?
在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现。
快照读
读取的是快照数据。不加锁的简单的 SELECT都属于快照读,比如这样:
SELECT * FROM player WHERE ...
当前读
就是读取最新数据,而不是历史版本的数据。加锁的 SELECT,或者对数据进行增删改都会进行当前读,比如:
SELECT * FROM player LOCK IN SHARE MODE;
SELECT * FROM player FOR UPDATE;
INSERT INTO player values ...
DELETE FROM player WHERE ...
UPDATE player SET ...
比如在可重复读的隔离条件下,我开启了两个事务,在另一个事务中进行了插入操作,当前事务如果使用当前读 是可以读到最新的数据的。
18、可重复读的隔离界别下怎么解决幻读
用间隙锁。
间隙锁,锁的就是两个值之间的空隙。比如下图,初始化插入了 6 个记录,这就产生了 7 个间隙。
间隙锁和行锁合称 next-key lock,每个 next-key lock 是前开后闭区间。也就是说,我们的表 t 初始化以后,如果用
SELECT * FEOM t FOR UPDATE
要把整个表所有记录锁起来,就形成了 7 个 next-key lock,分别是 (负无穷,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, 正无穷]。
- 间隙锁是在可重复读隔离级别下才会生效的
- 怎么加间隙锁呢?使用写锁(又叫排它锁)时自动生效,也就是说我们执行
SELECT * FEOM t FOR UPDATE
时便会自动触发间隙锁。 - 注意:在加了间隙锁以后,当A事务开启以后,并对(5, 10]这个区间加了间隙锁,那么B事务则无法插入数据了。但是当A事务对(5, 10]加了间隙锁以后,B事务也可以对这个区间加间隙锁。
上一条的解释:
间隙锁的目的是阻止往这个区间插入数据,因此A事务加了以后B事务继续加间隙锁,这并不矛盾。但是对于写锁和读锁就不一样了。
19、undo log redo log 和binlog
一、redo log
重做日志
作用: 确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
内容: 物理格式的日志,记录的是物理数据页面的修改的信息,其redo log是顺序写入redo log file的物理文件中去的。
二、bin log
归档日志(二进制日志)
作用: 用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。用于数据库的基于时间点的还原。
内容: 逻辑格式的日志,可以简单认为就是执行过的事务中的sql语句。
但又不完全是sql语句这么简单,而是包括了执行的sql语句(增删改)反向的信息,也就意味着delete对应着delete本身和其反向的insert;update对应着update执行前后的版本的信息;insert对应着delete和insert本身的信息。
binlog 有三种模式:Statement(基于 SQL 语句的复制)、Row(基于行的复制) 以及 Mixed(混合模式)三、undo log
回滚日志
作用: 保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读
内容: 逻辑格式的日志,在执行undo的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,这一点是不同于redo log的。
20、Mysql主从复制的延迟怎么解决
1、优化网络
2、升级从库硬件配置
3、升级到mysql5.7
已经支持了多线程的主从复制。
4、关闭从库的binlog
21、Mysql可重复读的实现机制
答:MVCC+undo log
MVCC顾名思义,数据库中的数据有多个版本,对同一条数据而言,MySQL 会通过ReadView
机制控制每一个事务看到不同版本的数据,这样也就解决了不可重复读的问题。
22、事务最开始读是当前读还是快照读?
当前读
23、数据库索引为什么不用hash表而用b+树?
hash表只能匹配是否相等,不能实现范围查找
select * from xx where id > 23; 这时就没办法索引了
当需要按照索引进行order by时,hash值没办法支持排序
select * from xx order by score desc;如果score为建立索引的字段,hash值没办法辅助排序。
组合索引可以支持部分索引查询,如(a,b,c)的组合索引,查询中只用到了a和b也可以查询的,如果使用hash表,组合索引会将几个字段合并hash,没办法支持部分索引
当数据量很大时,hash冲突的概率也会非常大
24、mysql四大事务特性的实现原理
问题一:Mysql怎么保证一致性的?
从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。例如,原子性无法保证,显然一致性也无法保证。
问题二: Mysql怎么保证原子性的?
利用Innodb的undo log(回滚日志)
,例如当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据。
undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
问题三: Mysql怎么保证持久性的?
利用Innodb的redo log,详情见知识点19
的redo log
问题四: Mysql怎么保证隔离性的?
利用的是锁和MVCC机制,如果一个事务读取的行正在做DELELE或者UPDATE操作,读取操作不会等行上的锁释放,而是读取该行的快照版本。
25、数据库主从的模式有哪些?
- 基于SQL语句的复制
- 基于行的复制
- 混合模式复制
26、为什么主键要是整形且自增
因为:
- int 相比varchar、char、text使用更少的存储空间,而且数据类型简单,可以节约CPU的开销,更便于表结构的维护
- InnoDB默认都会在主键上建立主键索引,使用int作为主键可以将更多的索引载入内存,提高查询性能
- 在数据插入时,可以保证逻辑相邻的元素物理也相邻,便于范围查找
但是也是有一定的缺点:
- 如果存在大量的数据,可能会超出自增长的取值范围
- 安全性低,因为是有规律的,容易被非法获取数据
27、Mysql什么时候建索引、什么时候不适合建索引?
那些情况需要创建索引:
1、主键自动建立唯 一 索引
2、频繁作为查询条件的字段应该创建索引
3、频繁更新的字段不适合创建索引,因为每次更新不单是更新了记录还会更新索引
4、查询中排序的字段,排序字段若通过索引法访问将大大提高排序速度
5、查询中统计或者分组字段
那些情况下不要建立索引:
1. 表记录太少
原因:提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE.
经常增删改的表
因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。
注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果
假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率大约为50%。那么对这种表A字段建索引一般不会提高数据库的査询速度。
索引的选择性是指索引列中不同值的数目与表中记录数的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是1980/2000.99。那么这个索引的选择性越接近于1,这个索引的效率就越高。
28、为何更新redis的时候是直接删除redis中对应的数据,而不是更新redis中的值?
- 高并发环境下,无论是先操作数据库还是后操作数据库而言,如果加上更新缓存,那就更加容易导致数据库与缓存数据不一致问题。(删除缓存直接和简单很多)
- 如果每次更新了数据库,都要更新缓存【这里指的是频繁更新的场景,这会耗费一定的性能】,倒不如直接删除掉。等再次读取时,缓存里没有,那我到数据库找,在数据库找到再写到缓存里边(体现懒加载的思想)
29、redis数据类型的应用场景?
30、redis实现分布式锁的步骤说一说?
1、首先要用SETNX命令设置一个key,作为锁标记,然后需要一个value作为用户标示
2、我们需要在finally块儿中去删除这个key(为了防止中途出错而无法释放锁)
3、还要给key设置一个过期时间。这样可以防止没走到finally就挂了的情况
4、处理完业务后释放锁的时候要判断一下当前线程是不是拥有锁的线程,如果是那么才能删除锁(也就是用之前设置的value和redis.getKey()进行比较)
5、但是这样无法解决锁续期的问题,所以一般都用redisson来实现分布式锁
31、如何基于MySQL实现分布式锁?
32、为什么mysql单表最多不放超过2000w行数据呢?
当年百度的 DBA 测试 MySQL性能时发现,当单表的量在 2000 万行量级的时候,SQL 操作的性能急剧下降,因此,结论由此而来。事实上,这个数值和实际记录的条数无关,而与 MySQL 的配置以及机器的硬件有关。
因为,MySQL 为了提高性能,会将表的索引装载到内存中。InnoDB buffer size 足够的情况下,其能完成全加载进内存,查询不会有问题。但是,当单表数据库到达某个量级的上限时,导致内存无法存储其索引,使得之后的 SQL 查询会产生磁盘 IO,从而导致性能下降。
33、优化下列语句
求name 为a开头的数据
select name from tb where name like ‘a%’
优化:[alert table tb add index name_idx(name)]求name为z结尾的数据
select name from tb where name like ‘%z’
优化:【在表中增加一个列,用来存放name的倒叙,比如name原来为AZ,现在倒序后就为ZA,然后给倒叙的字段加索引,就可以用最左匹配了】求name 为a开头z结尾的数据
select name from tb where name like ‘a%z’
优化:为name创建一个全文索引,再去查
34、left join、right join、inner join的区别?
left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录
right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录
inner join(等值连接) 只返回两个表中联结字段相等的行
35、mysql默认级别,为什么是RR?
低版本的MYSQL,使用RC+STATEMENT组合会导致主从不一致(1.5之前binlog只有statement格式,按照commit时间顺序保存,先插入后删除,master是先删除后插入),
但在RR级别下binlog任何格式都不会造成主从不一致,现在已经修复了问题,但沿用了老的设定
binlog不同模式:
statement:每修改一条会修改数据的sql都记录在binlog 一致性问题
Row:不记录sql语句上下文相关信息,仅保存哪条记录被修改 一条update执行多次,多条数据
Mixedlevel: 是以上两种level的混合使用,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog
36、Mysql连接器详细说说?
mysql客户端和服务器端的连接使用的是TCP协议,在完成TCP握手之后,连接器开始进行身份认证。如果用户名或密码不对,那么连接器会抛出错误。结束链接。如果用户名密码认证通过,那么连接器会到权限表中查出账号拥有的权限。以后所有的权限判断都是基于此时查出的权限。这意味着,一个用户成功建立连接后,即使root用户修改了这个用户的权限,对之前的连接也不能生效。
37、Mysql优化器怎么进行优化的?
简单来说优化器的输入是一个语法树,输出是一个执行树。而优化主要分为逻辑变化、代价优化、其它优化三个步骤。
逻辑变化:
比如它可能会进行一些化简。
代价优化:
基于代价的优化主要是用来确定对于每个表,根据条件是否应用索引,应用哪个索引和确定多表连接的顺序等问题。
其它优化:
确定JOIN顺序。MySQL采用称之为“贪婪算法”的策略,尽可能找到最优join路径,将N个表按照数据量大小和索引有无指标综合排序,小的放前面。并将第一个作为初始表,开始试探;按照深度遍历算法对后续表进行展开,记录当前最优路径的代价;
38、为什么唯一索引会直接去修改硬盘数据?
唯一索引是不可以使用缓存区。因为唯一索引的插入操作必须保证数据唯一性,所以唯一索引每次更新操作都不得不进行一次磁盘IO,对比原数据后才能判断该更新操作是否合法。
39、B+数的增删查时间复杂度?
增删:O(1)
查:
含有N个值 n阶
40、zset层高怎么确定的?
是通过一个随机函数来确定的,层数在1~64之间,层数越高出现的概率越小。层高一旦确定后就不会修改
幂次定律:少数几个事件的发生频率占了整个发生频率的大部分, 而其余的大多数事件只占整个发生频率的一个小部分
#define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */
int zslRandomLevel(void) {
int level = 1;
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
level += 1;
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
上述代码中,level的初始值为1,通过while循环,每次生成一个随机值,取这个值的低16位作为x,当x小于0.25倍的0xFFFF时,level的值加1;否则退出while循环。最终返回level和ZSKIPLIST_MAXLEVEL两者中的最小值。
41、mysql锁有哪些?以及加锁的情况?
42、mysql隔离级别实现原理
读未提交: 压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离
读已提交: MVVC (多版本并发控制)–>每次执行语句的时候都要重新创建一次快照,所以会产生幻读
可重复读: MVVC (多版本并发控制)–>仅在事务开始是创建一次
串行化: 读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。
操作系统👇👇👇
1、进程间通信方式
2、linux常用命令 , 查看cpu,内存使用,TCP连接
查看 CPU 使用率
top
查看进程相关信息占用的内存情况
pmap -d 端口号
3、上下文指什么
操作系统用来跟踪 进程运行所需的所有状态信息
,这种状态,也就是上下文,它包括许多信息。例如某一时间点 CPU 寄存器和程序计数器的内容。
4、上下文切换时发生了什么
(1)挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处
(2)在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复
(3)跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。
5、linux底层是怎么创建线程的
假如某个进程包含四个线程,那么它会为这 4 个线程创建 4 个 task_struct 结构体,然后在 task_struct 中指定它们共享的资源就可以了。
Linux 使用 task_struct 结构体描述和记录线程,每个线程都有唯一属于自己的 task_struct 结构
6、操作系统给进程分配了哪些资源?
堆、栈、方法区还有在运行时所需要的I/O设备,已打开的文件,信号量等
7、父子进程 COW 共享的是哪些数据?
fork()
在借用cow策略实现时,其实父子进程会共享数据段、代码段、堆,而栈是父子进程独有的.
8、大端规则和小端规则?
在计算机存储中存储字节的顺序有两种分别为大端规则和小端规则。
- 小端规则(littel endian):低序字节存储到内存较低的位置,即起始位置。
- 大端规则(big endian):低序字节存储到内存较高的位置,即高序字节存储到起始位置。
判断当前机器为大端规则还是小端规则,其本质是对于一个变量,判断其各字节的存储顺序
9、软链接(也称符号链接)和硬链接的区别?
- 软链接:软链接的
inode
所指向的内容实际上是保存了一个绝对路径,当用户访问这个文件时,系统会自动将其替换成其所指的文件路径,如果这个文件已经被删除了,那么就会显示无法找到该文件了。 - 硬链接: 与普通文件没什么不同,
inode
都指向同一个文件在硬盘中的区块
10、Linux进程的五个段(🈲注意啊,不要和JVM搞混淆了)
- BSS段:通常是指用来存放程序中
未初始化的全局变量
的一块内存区域。BSS段属于静态内存分配。
- 数据段:通常是指用来存放程序中
已初始化的全局变量
的一块内存区域。数据段属于静态内存分配
。 - 代码段:通常是指用来存放
程序执行代码
的一块内存区域。这部分区域的大小在程序运行前就已经确定。 - 堆(heap):用于存放进程运行中
被动态分配的内存段
,它的大小并不固定,可动态扩张或缩减。 - 栈(stack): 是用户存放
程序临时创建的局部变量
,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。
11、进程切换和线程切换的区别?
最主要的一个区别在于进程切换涉及虚拟地址空间的切换而线程不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。
12、Linux查看端口号的命令?
netstat -tunlp | grep 端口号
-t (tcp) 仅显示tcp相关选项
-u (udp)仅显示udp相关选项
-n 拒绝显示别名,能显示数字的全部转化为数字
-l 仅列出在Listen(监听)的服务状态
-p 显示建立相关链接的程序名
13、vim的缺点?
会将文件全部加载入内存中
14、内存飙高怎么排查?
- jstat命令查看FGC发生的次数和消耗的时间,次数越多,耗时越长说明存在问题;
- 用jmap查看内存的详细分配情况
- 用jmap -histo到处导出文件,对比一下加载对象的差异,一般差异的地方就是发生问题的地方
##15、cpu飙高排查?
4. top查找出哪个进程消耗的CPU高
5. top -h -p查找出哪个线程消耗的cpu高(top -h -p pid)
6.然后用jstack查看栈信息,定位到代码
16、jps、jstack等命令的含义?
- 查看Java进程:jps
- 查看线程堆栈命令:jstack命令
- 得到运行java程序的内存分配的详细情况。例如实例个数,大小等:jmap
17、linux中|管道符的作用?
管道符左边的命令作为右边的操作对象
18、进程切换、线程切换中CPU保留的现场是什么?
(1)用户级上下文: 正文、数据、用户堆栈以及共享存储区;
(2)寄存器上下文: 通用寄存器、程序寄存器(EIP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);
(3)系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。
- 进程切换需要切换(1)(2)(3)
用户态和内核态的切换只需要(2)
线程的切换也只需要(2)
19、系统 32 位和 64 位的区别?
一、支持的内存不同
32位的操作系统,最多支持4G的内存;64位系统支持4G 8G 16G 32G 64G 128G 256G内存等,理论上可以无限支持,只要你主板上有足够的内存条。
二、处理数据的能力
32和64表示CPU可以处理最大位数,一次性的运算量不一样,理论上64位的会比32位快1倍
20、如何检测死锁?解除死锁?
检测死锁
用某种数据结构来保存资源的请求和分配信息。如果能消掉所有的边,证明没有死锁
解除死锁
- 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
- 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止
21、什么限制了创建线程的数量?
- 进程的虚拟内存空间上限,因为创建一个线程,操作系统需要为其分配一个栈空间,如果线程数量越多,所需的栈空间就要越大,那么虚拟内存就会占用的越多。
- 系统参数限制,虽然 Linux 并没有内核参数来控制单个进程创建的最大线程个数,但是有系统级别的参数来控制整个系统的最大线程个数。
计算机网络👇👇👇
1、TCP粘包,为什么出现,如何解决?
什么是TCP粘包?
TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。
造成TCP粘包的原因?
1)发送方原因:
TCP默认使用Nagle算法(主要作用:减少网络中报文段的数量),而Nagle算法主要做两件事:
只有上一个分组得到确认,才会发送下一个分组
收集多个小分组,在一个确认到来时一起发送
Nagle算法造成了发送方可能会出现粘包问题
2)接收方原因:
TCP接收到数据包时,并不会马上交到应用层进行处理,或者说应用层并不会立即处理。实际上,TCP将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包。
如何处理粘包现象?
发送方:对于发送方造成的粘包问题,可以通过关闭Nagle算法来解决
接收方没有办法来处理粘包现象,只能将问题交给应用层来处理。
应用层解决办法: 循环处理,应用程序从接收缓存中读取分组时,读完一条数据,就应该循环读取下一条数据,直到所有数据都被处理完成
2、https可以被攻击么?
首先我们假设不存在认证机构,任何人都可以制作证书,这带来的安全风险便是经典的“中间人攻击”问题。具体过程如下
为什么需要证书?
防止”中间人“攻击,同时可以为网站提供身份证明。
使用 HTTPS 会被抓包吗?
会被抓包,HTTPS 只防止用户在不知情的情况下通信被监听,如果用户主动授信,是可以构建“中间人”网络,代理软件可以对传输内容进行解密。
3、HTTP头部有哪些字段?有哪些方法?
http组成是由请求行、请求头、请求体
HTTP头部有哪些字段?举几个例子:
- Accept:用户代理可以处理的媒体类型
- Host:请求资源所在的服务器
- Range:实体的字节范围请求
有哪些方法?
就是七大方法
4、大量的close wait如何定位问题?
首先要搞清楚:在被动关闭方收到主动关闭方的FIN
后,自己还没有发出去FIN
的这段时间就处于close wait
。
所以可以发现如果一直保持在CLOSE_WAIT状态,那么只有一种情况,就是在对方关闭连接之后服务器程序自己没有进一步发出FIN信号。一般原因都是TCP连接没有调用关闭方法。
所以排查close wait
总结起来就是一句话:查代码,因为问题出在服务端。
5、Time_wait的缺点以及状态过多的优化
缺点:
(1)在socket的TIME_WAIT状态结束之前,该socket所占用的本地端口号将一直无法释放。
(2)在高并发(每秒几万qps)并且采用短连接方式进行交互的系统中运行一段时间后,系统中就会存在大量的time_wait状态,如果time_wait状态把系统所有可用端口都占完了且尚未被系统回收时,就会出现无法向服务端创建新的socket连接的情况。此时系统几乎停转,任何链接都不能建立。
优化:
总体来说,有两种方式:
方式一:调整系统内核参数, 超过一定阈值后重置TCP状态
方式二:调整短链接为长链接
短连接是指SOCKET连接后发送后接收完数据后马上断开连接。这样就会产生time_wait状态,换成长连接就比较少了
6、ipv4和ipv6的区别是什么?
- 地址空间不同,IPv4中规定IP地址长度为32,而IPv6中IP地址的长度为128。
- 路由表大小不同,IPv6的路由表相比IPv4的更小。
7、三次握手四次挥手有关的超详细的题目
8、udp如何做到可靠传输
最简单的方式是在应用层模仿传输层TCP的可靠性传输。
1、添加seq/ack机制,确保数据发送到对端
2、添加发送和接收缓冲区,主要是用户超时重传。
3、添加超时重传机制。
9、http使用的是tcp还是udp
http 底层用什么协议得分版本,http1.0和http2.0都是基于tcp;http3.0是基于udp的,利用介于传输层和应用层之间的一个协议QUIC协议保证可靠传输
10、get与post请求的区别,post的安全性体现在那里,是否可以发现他的请求内容
答:区别
- GET把 参数包含在URL中,POST通过request body传递参数
- GET请求会被 浏览器主动cache,而POST 不会,除非手动设置。
- GET请求在URL中传送的参数是有 长度限制的,而POST 么有。
- GET比POST更不安全,因为参数 直接暴露在URL上,所以不能用来传递敏感信息。
- GET请求只能进行 url编码,而POST支持 多种编码方式。
- GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
- 对参数的数据类型, GET只接受ASCII字符,而 POST没有限制。
- GET在浏览器回退时是无害的,而POST会再次提交请求。
- GET产生的URL地址可以被 Bookmark,而POST不可以。
- GET产生 一个TCP数据包;POST产生 两个TCP数据包。
post的安全性主要体现在上面的2,4,5,6点。可以通过抓包的形式获取到他的请求内容,不过,可能需要进行转码,解密。
12、ping的过程,以及用到的协议,主机是如何区分收到的icmp包的?
通过DNS协议,将ping后接的域名转换为ip地址。(DNS使用的传输层协议是UDP)
通过ARP解析服务,由ip地址解析出MAC地址,以在数据链路层传输。
ping是为了测试另一台主机是否可达,发送一份ICMP回显请求给目标主机,并等待ICMP回显应答
在发送数据包的时候把当前进程ID设置为发送包的标志,收到 包的标志和当前进程ID相同的包才会被被进程给过滤出来。
13、HTTPS 的实现原理,以及加密过程
HTTPS 在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段。
① 证书验证阶段:
1)浏览器发起 HTTPS 请求;(在本地生成一个随机数,稍后用于生成对话密钥)
2)服务端返回 HTTPS 证书(同时也返回了一个随机数,作用与1
相同);
3)客户端验证证书是否合法,如果不合法则提示告警。(然后取出公钥)
② 数据传输阶段:
1)当证书验证合法后,在本地生成随机数;
2)通过公钥加密随机数,并把加密后的随机数传输到服务端;
3)服务端通过私钥对随机数进行解密;
4)服务端通过客户端传入的随机数构造对称加密算法,对返回结果内容进行加密后传输。
14、为什么Https数据传输是用对称加密?而不是一直用非对称加密
首先: 非对称加密的加解密效率是非常低的,而 http 的应用场景中通常端与端之间存在大量的交互,非对称加密的效率是无法接受的。
另外: 在 HTTPS 的场景中只有服务端保存了私钥,一对公私钥只能实现单向的加解密,所以HTTPS 中内容传输加密采取的是对称加密,而不是非对称加密。
15、Socket建立网络连接的步骤?
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
- 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
- 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。
为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
- 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
16、TCP和IP分别解决了网络什么样的问题?
TCP负责建立连接,IP负责找到具体的机器,这里其实还用到了ARP
,ARP是根据IP地址获取MAC地址的一种协议。
17、如果三次握手的时候第1/2/3次握手丢失怎么办?
第一次握手的丢了:
如果客户端第一个「SYN」包丢了,也就是服务端根本就不知道客户端曾经发过包,那么处理流程主要在客户端。
客户端只要在一定时间内没有收到应答的「ACK」包,无论是请求包对方没有收到,还是对方的应答包自己没有收到,均认为是丢包了,会触发超时重传机制。
第二次握手丢了:
此时服务端已经收到了数据包并回复,如果这个回复的「SYN,ACK」包丢了,站在客户端的角度,会认为是最开始的那个「SYN」丢了,那么就继续重传,就是我们前面说的「错误 1 流程」。
而对服务端而言,如果发送的「SYN,ACK」包丢了,在超时时间内没有收到客户端发来的「ACK」包,也会触发重传
第三次握手丢了:
如果最后一个「ACK」包丢了,服务端因为收不到「ACK」会走重传机制,而客户端此时进入 ESTABLISHED 状态。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。
18、三次握手时,第三次握手丢失,客户端给服务端发数据,这个时候服务端是什么状态?
户端进入 ESTABLISHED 状态后,则认为连接已建立,会立即发送数据。但是服务端因为没有收到最后一个「ACK」包,依然处于 SYN-RCVD 状态。那么这里的关键,就在于服务端在处于 SYN-RCVD 状态下,收到客户端的数据包后如何处理?
答:
当服务端处于 SYN-RCVD
状态下时,接收到客户端真实发送来的数据包时,会认为连接已建立,并进入 ESTABLISHED 状态。
当客户端在 ESTABLISHED 状态下,开始发送数据包时,会携带上一个「ACK」的确认序号,所以哪怕客户端响应的「ACK」包丢了,服务端在收到这个数据包时,能够通过包内 ACK 的确认序号,正常进入 ESTABLISHED 状态。
19、输入www.taobao.com会发生什么?中DNS解析过程讲一讲
20、HTTP常见状态码说说?
21、客户端一直反复发SYN然后马上结束 服务器一直等待 最先消耗完的资源是什么?
由于服务器长时间处于半连接状态,最后消耗过多的 CPU
和内存
资源导致死机。
22、tcp建立连接后传输数据包流程?
建立链接后两台主机就可以通信了
- 发送方会发一个数据包给接收方,接收方收到以后要回一个
ACK
,而ACK
的值为1301- 这是因为
Ack号 = Seq号 + 传递的字节数 + 1
- 如果Ack 号不加传输的字节数,这样虽然可以确认数据包的传输,但无法明确100字节全部正确传递还是丢失了一部分。
23、如果你在浏览器上输入一个网址返回error怎么排查?
ping对应的ip,看到底是为啥。
如果你ping出来的ip是128.0.0.1怎么办?
:
肯定是对应的浏览器缓存映射、或者本级host被修改
24、TCP 拥塞控制算法的宏观流程
- 拥塞窗口cwnd初始化为1个报文段,慢开始门限初始值为16
- 执行慢开始算法,指数规律增长到第4轮,即
cwnd=16=ssthresh
,改为执行拥塞避免算法,拥塞窗口按线性规律增长 - 假定cwnd=24时,网络出现超时(拥塞),则更新ssthresh=12(cwnd的一半),cwnd重新设置为1,并执行慢开始算法。当cwnd=12=ssthresh时,改为执行拥塞避免算法
25、http2.0相较于之前的版本的区别?
- 增加了多路复用:做到同一个连接并发处理多个请求
- 数据压缩:HTTP1.1不支持header数据的压缩,HTTP2.0使用HPACK算法对header的数据进行压缩
26、ajax和websocket它们的区别是什么?
1.生命周期不同
websocket建立的是长连接,在一个会话中一直保持连接;而ajax是短连接,数据发送和接受完成后就会断开连接。
2.发起人不同
Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以相互推送信息。
二者都是异步的
27、 如果发送方发送的数据包是乱序的那么接收方怎么处理?接收方接收到数据包自己会不会进行一个调整 排序?
TCP为了提供可靠的数据传输,它给发送的每个数据包做顺序化。主机每次发送数据时,TCP就给每个数据包分配一个序列号。接收主机利用序列号对接收的数据进行确认,以便检测对方发送的数据是否有丢失或者乱序等,接收主机一旦收到已经顺序化的数据,就会交给上层处理
对于不按序到达的数据应该如何处理,tcp并无明确规定。如果接受方把不按序到达的数据一律丢弃,那么接收窗口的管理将会比较简单,但这样做对网络资源的利用不利。因此tcp通常对不按序到达的数据先是临时存放在接收窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程
28、说说tpc的backlog?
- backlog其实是一个连接队列,在Linux内核2.2之前,backlog大小包括半连接状态和全连接状态两种队列大小。
- 在Linux内核2.2之后,分离为两个backlog来分别限制半连接(SYN_RCVD状态)队列大小和全连接(ESTABLISHED状态)队列大小。
29、tcp最多可以建立多少链接?
客户端:
本地端口个数最大只有65536,端口0有特殊含义,不能使用,这样可用端口最多只有65535
服务端:
server端单机最大tcp连接数约为2的48次方
30、TCP的Keep Alive和HTTP的Keep Alive的区别是什么?
两者在写法上不同,http keep-alive 中间有个”-“符号。
HTTP协议的keep-alive 意图在于连接复用,同一个连接上串行方式传递请求-响应数据
TCP的keepalive机制意图在于保活、心跳,检测连接错误。
31、DNS 在传输层为什么数据量大的时候用 TCP?
因为 UDP报文的最大长度为512字节,而TCP则允许报文长度超过512字节。当DNS查询超过512字节时,协议的TC标志出现删除标志,这时则使用TCP发送。
32、arp地址解析过程
- A看看自己的arp表(存放了IP—对应的mac地址)中有无该mac地址,有就直接发送数据。结束。
- 没有就先缓存该数据报文,然后广播发送一个icmp报文,报文格式为
A的IP地址+A的mac地址+B的IP地址+全0的mac地址
,当B看到有自己ip地址的报文就拿到,然后回传mac地址。 - A收到应答后,就拿到Bmac地址,并且缓存在自己的arp表中,同时将原来缓存的IP数据包再次修改(在目的MAC地址字段填上主机B的MAC地址)后发送出去。
33、SSL协议在那一层?用的什么加密算法?
它介于应用层与传输层之间。握手阶段 存在于应用层,传输阶段 存在于传输层
非对称加密阶段用的RSA。传输数据阶段用的对称算法(如 DES 或 RC4)进行加密的。
34、非对称加密假如公钥与私钥都泄露了怎么办?
没关系的,B收到消息后如何确认发信人是A而不是第三方呢?其实也很简单,只要发消息前多进行一次使用自己的私钥加密的过程就可以了,这次使用自己私钥加密信息的步骤就叫做签名。
35、对称加密算法比非对称加密算法快多少?
对称加密算法比非对称加密算法快大约1500倍
36、https比http慢一些,大概慢多少?
SSL握手的耗时(64毫秒)大概是TCP握手(22毫秒)的三倍。也就是说,在建立连接的阶段,HTTPs链接比HTTP链接要长3倍的时间,具体数字取决于CPU的快慢和网络状况。
Spring相关框架👇👇👇
1、简述SpringMvc工作流程
SpringMVC框架是一个基于请求驱动的Web框架,并且使用了‘前端控制器’模型来进行设计,再根据‘请求映射规则’分发给相应的页面控制器进行处理。
具体步骤:
第一步:发起请求到前端控制器(DispatcherServlet)
第二步:前端控制器请求HandlerMapping查找 Handler (可以根据xml配置、注解进行查找)
第三步:处理器映射器HandlerMapping向前端控制器返回Handler,HandlerMapping会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象,多个HandlerInterceptor拦截器对象),通过这种策略模式,很容易添加新的映射策略
第四步:前端控制器调用处理器适配器去执行Handler
第五步:处理器适配器HandlerAdapter将会根据适配的结果去执行Handler
第六步:Handler执行完成给适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回ModelAndView (ModelAndView是springmvc框架的一个底层对象,包括 Model和view)
第八步:前端控制器请求视图解析器去进行视图解析 (根据逻辑视图名解析成真正的视图(jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可
第九步:视图解析器向前端控制器返回View
第十步:前端控制器进行视图渲染 (视图渲染将模型数据(在ModelAndView对象中)填充到request域)
第十一步:前端控制器向用户响应结果
2、spring的aop顺序
首先看一下Aop常用注解:
由一个例子来看一下在srping4和spring5下Aop的顺序:
@Service
public class CalcServiceImpl implements CalcService {
@Override
public int div(int x, int y) {
int result = x / y;
System.out.println("=========>CalcServiceImpl被调用了,我们的计算结果:" + result);
return result;
}
}
然后再自己建一个切面方法:
@Aspect
@Component
public class MyAspect
{
@Before("execution(public int com.sl.spring_mianshi.aop.CalcServiceImpl.*(..))")
public void beforeNotify()
{
System.out.println("******** @Before我是前置通知MyAspect");
}
@After("execution(public int com.sl.spring_mianshi.aop.CalcServiceImpl.*(..))")
public void afterNotify()
{
System.out.println("******** @After我是后置通知");
}
@AfterReturning("execution(public int com.sl.spring_mianshi.aop.CalcServiceImpl.*(..))")
public void afterReturningNotify()
{
System.out.println("********@AfterReturning我是返回后通知");
}
@AfterThrowing("execution(public int com.sl.spring_mianshi.aop.CalcServiceImpl.*(..))")
public void afterThrowingNotify()
{
System.out.println("********@AfterThrowing我是异常通知");
}
@Around("execution(public int com.sl.spring_mianshi.aop.CalcServiceImpl.*(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
{
Object retValue = null;
System.out.println("我是环绕通知之前AAA");
retValue = proceedingJoinPoint.proceed();
System.out.println("我是环绕通知之后BBB");
return retValue;
}
}
再创建一个test类:
@Autowired
private CalcService calcService;
@Test
void testAOP4() {
System.out.println("spring版本:"+ SpringVersion.getVersion()+"\t"+"SpringBoot版本:"+ SpringBootVersion.getVersion());
System.out.println();
calcService.div(10,2);
}
@Test
void testAOP5() {
System.out.println("spring版本:"+ SpringVersion.getVersion()+"\t"+"SpringBoot版本:"+ SpringBootVersion.getVersion());
System.out.println();
calcService.div(10,0);
}
spring5+springboot2的顺序:
另:spring5默认动态代理用的是cglib,不再是JDK的动态代理,因为JDK必须要实现接口,但有些类它并没有实现接口,所以更加通用的话就是cglib
spring4+springboot1的顺序
另:spring4默认用的是JDK的动态代理
3、spring的循环依赖
什么是循环依赖
多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A。也就是说,Spring的循环依赖,是Spring容器注入时候出现的问题
循环依赖现象在Spring容器中 注入依赖的对象,有2种情况
①构造器方式注入依赖
@Component
public class ServiceA {
private ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Component
public class ServiceB {
private ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
/**
* 通过构造器的方式注入依赖,构造器的方式注入依赖的bean,下面两个bean循环依赖
*
* 测试后发现,构造器循环依赖是无法解决的
*/
public class ClientConstructor {
public static void main(String[] args) {
new ServiceA(new ServiceB(new ServiceA(new ServiceB()))); ....
}
}
结论:构造器注入没有办法解决循环依赖, 你想让构造器注入支持循环依赖,是不存在的
②以set方式注入依赖
@Component
public class ServiceA {
private ServiceB serviceB;
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
System.out.println("A 里面设置了B");
}
}
@Component
public class ServiceB {
private ServiceA serviceA;
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
System.out.println("B 里面设置了A");
}
}
public class ClientSet {
public static void main(String[] args) {
//创建serviceA
ServiceA serviceA = new ServiceA();
//创建serviceB
ServiceB serviceB = new ServiceB();
//将serviceA注入到serviceB中
serviceB.setServiceA(serviceA);
//将serviceB注入到serviceA中
serviceA.setServiceB(serviceB);
}
}
总结:我们AB循环依赖问题只要注入方式是setter且singleton, 就不会有循环依赖问题
重要结论(spring内部通过3级缓存来解决循环依赖)
所谓的三级缓存其实就是spring容器内部用来解决循环依赖问题的三个map
只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
第一级缓存(也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象
第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
第三级缓存: singletonFactories,存放可以生成Bean的工厂
进行源码DEBUG来搞清楚解决循环依赖的流程
由于图片太多,所以单独写了一篇文章。
总结spring是如何解决的循环依赖
Spring创建bean主要分为两个步骤:
①创建原始bean对象②接着去填充对象属性和初始化。
假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充 B的属性时B发现自己依赖A,和上面相同,现在就去给B注入A这个属性。不同的是:这时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入beanB,就可以完成beanB的创建。既然 beanB创建好了,所以beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成。
Spring解决循环依赖依靠的是Bean的 “中间态”这个概念,而这个中间态指的是已经实例化但还没初始化的状态—->半成品。
实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。
spring解决循环依赖的整个流程图
Debug的步骤—->Spring解决循环依赖过程
1 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
2 在getSingleton()方法中,从一级缓存中查找,没有,返回null
3 doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
4 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法
5 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断。是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中〈即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中
6 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB
7 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性
8 此时beanB依赖于beanA,调用getsingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
9 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中
10 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中
4、为什么要有 SpringBoot?说出使用 Spring Boot 的主要优点
从Spring的优点来看,之所以使用springboot是因为它有以下的优点:
①简化编码
比如我们要创建一个 web 项目,使用 Spring 的朋友都知道,在使用 Spring 的时候,需要在 pom 文件中添加多个依赖,而 Spring Boot 则会帮助开发着快速启动一个 web 容器,在 Spring Boot 中,我们只需要在 pom 文件中添加如下一个 starter-web 依赖即可。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
我们点击进入该依赖后可以看到,Spring Boot 这个 starter-web 已经包含了多个依赖,由此可以看出,Spring Boot 大大简化了我们的编码
②简化配置
Spring 有很多配置文件,经常让人眼花缭乱。Spring Boot更多的是采用 Java Config 的方式。比如我新建一个类,但是我不用 @Service注解,也就是说,它是个普通的类,那么我们如何使它也成为一个 Bean 让 Spring 去管理呢?只需要@Configuration 和@Bean两个注解即可。
另外,部署配置方面,原来 Spring 有多个 xml 和 properties配置,在 Spring Boot 中只需要个 application.yml即可
③简化部署
在使用 Spring 时,项目部署时需要我们在服务器上部署 tomcat,然后把项目打成 war 包扔到 tomcat里,在使用 Spring Boot 后,我们不需要在服务器上去部署 tomcat,因为 Spring Boot 内嵌了 tomcat,我们只需要将项目打成 jar 包,使用 java -jar xxx.jar一键式启动项目。
④简化监控
我们可以引入 spring-boot-start-actuator 依赖,直接使用 REST 方式来获取进程的运行期性能参数,从而达到监控的目的,比较方便。
5、什么是 Spring Boot Starters?
6、Spring Boot 支持哪些内嵌 Servlet 容器?
Tomcat, Jetty和Undertow服务器
如果要使用jetty的话,只需要在maven的pom.xml排除tomcat并引入jetty即可
7、介绍一下@SpringBootApplication 注解
我们可以把 @SpringBootApplication看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合。
- @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
- @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。
- @Configuration:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类,说白了就是支持JavaConfig的方式来进行配置。
8、Spring Boot 的自动配置是如何实现的?
详细文章
还要注意不是所有存在于spring.factories
中的配置都会进行加载,而是通过@ConditionalOnClass
注解进行判断条件是否成立(只要导入了相应的stater,条件就能成立),如果条件成立则加载配置类,否则不加载该配置类。
9、开发 RESTful Web 服务常用的注解有哪些?
@GET 查询请求 相当于数据库的查询数据操作
@POST 插入请求 相当于数据库的插入数据操作
@PUT 更新请求 相当于数据库的更新数据操作
@DELETE 删除请求 相当于数据的删除数据操作
@Path uri路径 定义资源的访问路径,client通过这个路径访问资源。比如:@Path("user")
@PathParam uri路径参数 写在方法的参数中,获得请求路径参数。比如:@PathParam("username") String userName
10、YAML 配置的优势在哪里
①层次明显
②yaml中的数据是有序的,properties中的数据是无序的。在一些需要路径匹配的的配置中,顺序就显得尤为重要
11、Spring Boot 常用的读取配置文件的方法有哪些?
1、使用@Value注解读取
配置文件:
demo.name=Name
读取:
@Value("${demo.name}")
private String name;
2、使用Environment读取
配置文件:
demo.sex=男
demo.address=山东
@Autowired
private Environment environment;
.....
String sex=environment.getProperty("demo.sex")
3、使用@ConfigurationProperties注解读取
在实际项目中,当项目需要注入的变量值很多时,上述所述的两种方法工作量会变得比较大,这时候我们通常使用基于类型安全的配置方式,将properties属性和一个Bean关联在一起,即使用注解@ConfigurationProperties读取配置文件数据。
在src\main\resources下新建config.properties配置文件:
demo.phone=10086
demo.wife=self
创建ConfigBeanProp并注入config.properties中的值:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "demo")
@PropertySource(value = "config.properties")
public class ConfigBeanProp {
private String phone;
private String wife;
...get、set方法
}
@Component 表示将该类标识为Bean
@ConfigurationProperties(prefix = “demo”)用于绑定属性,其中prefix表示所绑定的属性的前缀。
@PropertySource(value = “config.properties”)表示配置文件路径。
使用时,先使用@Autowired自动装载ConfigBeanProp,然后再进行取值:
@Autowired
private ConfigBeanProp configBeanProp;
.........
String phone= configBeanProp.getPhone() ;
12、Spring Boot 加载配置文件的优先级了解么?
1、项目内部配置文件
spring boot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件
–file:./config/
–file:./
–classpath:/config/
–classpath:/
即如下图所示:
以上是按照优先级从高到低(1-4)的顺序,所有位置的文件都会被加载,高优先级配置内容会覆盖低优先级配置内容。
SpringBoot会从这四个位置全部加载主配置文件,如果高优先级中配置文件属性与低优先级配置文件不冲突的属性,则会共同存在—互补配置。
13、Spring Boot 如何监控系统实际运行状况?
spring boot中用Actuator 来监控,只需要引入对应的依赖,然后访问“http://localhost:程序端口/health”
就可以查看健康状态了。另Actuator 提供了13个API 接口:
14、如何使用 Spring Boot 实现全局异常处理?
它提供了一个 @ControllerAdvice注解以及 @ExceptionHandler注解,前者是用来开启全局的异常捕获,后者则是说明捕获哪些异常,对那些异常进行处理。
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(value =Exception.class)
public String exceptionHandler(Exception e){
System.out.println("发生了一个异常"+e);
return e.getMessage();
}
}
15、Spring Boot 中如何实现定时任务 ?
- 基于 java.util.Timer 定时器,实现类似闹钟的定时任务
- 使用 Quartz、elastic-job、xxl-job 等开源第三方定时任务框架,适合分布式项目应用
- 使用 Spring 提供的一个注解: @Schedule,开发简单,使用比较方便,也是本文介绍的一种方式
①主启动类加上注解 @EnableScheduling
②然后编写定时任务类:
@Component
public class FixedPrintTask {
private Logger logger = LoggerFactory.getLogger(getClass());
private int i;
@Scheduled(cron = "*/15 * * * * ?")
public void execute() {
logger.info("thread id:{},FixedPrintTask execute times:{}", Thread.currentThread().getId(), ++i);
}
}
@Scheduled中的cron表达式一般都是百度的。
16、@Transactional属性有哪些?
属性 | 含义 |
---|---|
readOnly | 读写或只读事务,默认读写 |
timeout | 事务超时时间设置 |
isolation | 可选的事务隔离级别设置 |
17、Spring之AOP注解失效原因和解决方法?
在对象内部的方法中调用该对象的其他使用AOP注解的方法,被调用方法的AOP注解失效。是因为没有用SpringAop的代理对象去调用目标方法而是用原对象本身调用目标方法。
解决:SpringBoot
通过实现ApplicationContext获取代理对象。
18、Spring的两种代理JDK和CGLIB的区别
- JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
- cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
19、spring boot快速启动原理
启动流程主要分为三个部分,第一部分进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器,第二部分实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块,第三部分是自动化配置模块,也就是自动装配那一套说法。
20、Spring框架中的设计模式有哪些?举几个例子?
- 工厂模式 :例如Spring中的BeanFactory就是简单工厂模式的体现
- 单例模式 :Spring依赖注入Bean实例默认是单例的。
- 适配器模式 :SpringMVC中的适配器HandlerAdatper根据Handler规则执行不同的Handler
- 代理模式 :AOP底层,就是动态代理模式的实现
##21、@Autowired和@Resource的区别?
5. @Autowired默认是byType
来注入的,如果想要按照name来注入就需要使用@Qualifier("xxname")
。**@Resource**默认是byName
来注入的,但是可以通过type
属性来指定byType
的方式注入。
22、注解的原理?
注解本质是一个继承了Annotation
的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1
23、A方法@Transactional,B方法没有,B中直接调用A方法那A的@Transactional能生效吗?
总结:
方法A调用方法B:
1、如果只有A加@Transactional注解;则AB在同一事务中;
2、如果只有B加@Transactional注解;AB方法为同一类,事务失效;AB不同类,只有B有事务;
杂👇👇👇
1、git用过吗,怎么解决版本冲突的问题?
假如我git pull
的时候提示我产生了冲突,我们能看到是那个文件产生了冲突,然后就可以用vi
进行编辑该文件, 文件产生冲突的地方是用两排反向尖括号作为分界线的,我们只能保留其中的一个版本的代码。删除另一个版本的代码后,正常git add 、commit、push
即可
2、一亿个数怎么找最大的10万个,时间复杂度多少
维护一个小根堆,先拿10000个数建堆,然后一次添加剩余元素,如果大于堆顶的数(10000中最小的),将这个数替换堆顶,并调整结构使之仍然是一个最小堆,这样,遍历完后,堆中的10000个数就是所需的最大的10000个。建堆时间复杂度是O(mlogm),算法的时间复杂度为O(nmlogm)(n为10亿,m为10000)
3、有三个集合,每个集合 取出一个元素x,y,z;现在要求|x-y|+|x-z|+|y-z|的最大值。 思路
三个数组分别求出最大值和最小值,然后暴力代入公式,应该是有六种情况:x取集合中的最小值,剩下的取集合中的最大值,剩下两个依次,再就是x取集合中的最大值,剩下两个取集合中的最小值,剩下的两个类似,结果肯定是2倍某个最小值和某个最大值的差
4、烧绳子,只能从一边或者两边烧,一个绳子单向烧需要1个小时?问怎么计算1h15min
- 取两跟绳子A和B,A从一头开始烧,B从两头开始烧。当B烧完时,刚好30分钟。
- B烧好后,A停止烧。取绳子C,两头开始烧,C烧完后,又过30分钟。
- C烧好后,再从两头开始烧A,刚好15分钟。总共1小时15分钟。
5、海量数据处理题目
6、讲讲JWT的优缺点?
优点:
- 能很好的解决单点登录的问题
- 无状态 :jwt不在服务端存储任何状态
缺点:
3. 性能较差:jwt太长。由于是无状态使用JWT,所有的数据都被放到JWT里,如果还要进行一些数据交换,那载荷会更大,经过编码之后导致jwt非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在local storage里面。
4. 安全性较差:由于jwt是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。
7、讲讲Mybatis一二级缓存?
一级缓存介绍:
在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。具体执行过程如下图所示。
每个SqlSession中持有了Executor,每个Executor中有一个本地缓存。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。
我们来看看如何使用MyBatis一级缓存。开发者只需在MyBatis的配置文件中,添加如下语句,就可以使用一级缓存。共有两个选项,
SESSION
或者STATEMENT
,默认是SESSION级别,即在一个MyBatis会话中执行的所有语句,都会共享这一个缓存。一种是STATEMENT级别,可以理解为缓存只对当前执行的这一个Statement有效。
总结
- MyBatis一级缓存的生命周期和SqlSession一致。
- MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
- MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
二级缓存介绍:
在上面提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会有一个CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
总结
- MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
- MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
- 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。
8、讲讲Mybatis的Mapper底层原理?
总的来说是通过动态代理。动态代理的功能就是通过拦截器方法(invokeHandler),达到增强目标对象的目的。