Java八股文

笔记5

java基础

1、面向对象,面向过程

什么是面向对象?
面向对象是基于面向过程的。任何物体都能归成一类事物,每个个体都是一类事物的实例。
和面向过程区别?
(1)编程思路不同:面向过程主要是把功能函数实现,但是面向对象要先抽象出类、属性和方法,然后通过实例化类、执行对应方法才能实现。
(2)封装性:他俩都具有封装性,但面向过程封装的是功能,面向对象封装的是数据和功能。
(3)面向对象有继承性和多态性,但是面向过程没有继承性和多态性

2、面向对象的三大特性

(1)封装:把数据和操作数据的方法封装起来,对数据访问只能通过定义的接口。
(2)继承:有俩类,一个类继承另一个类信息。提供继承信息的是父类,得到继承信息的是子类。
父类和子类:
1、子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但父类的私有属性和方法子类是无法访问,只能拥有。因为在一个子类被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者合起来形成一个子类的对象;
2、子类可以有自己属性和方法;
3、子类可以通过重写方式实现父类的方法。
(3)多态:分为编译时多态(方法重载)和运行时多态(方法重写)。多态要做两件事:一是子类继承父类重写父类的方法,二是父类型引用子类型对象,这样调用同样的方法,就会根据子类对象的不同而表现出不同的行为。

3、重载和重写

重载:编译时多态、同一个类中同名的方法具有不同的参数列表、不能根据返回类型进行区分【因为:函数调用时不能指定类型信息,编译器不知道你要调哪个函数】;
重写:运行时多态、子类与父类之间、子类重写父类的方法具有相同的返回类型

4、JDK、JRE、JVM

JDK:是 Java 开发工具包,包括了 Java 运行环境 JRE、Java 工具和 Java 的基础类库。
JRE:是 Java 的运行环境,包含 JVM标准实现及 Java 核心类库。
JVM:是 Java 虚拟机,是整个 Java 实现跨平台的核心部分,能够运行以 Java写的程序。所有的 Java 程序会先被编译生成 .class 文件, .class文件就可以在虚拟机上运行。

5、是否可重写 private或static 方法

**static 方法:**不能被重写,而 static 方法是编译时就静态绑定的,方法重写是基于运行时动态绑定的,static 方法跟任何实例都不相关。
**静态方法补充:**静态的方法可以被继承,但是不能重写。如果父类和子类中存在同样名称和参数的静态方法,那么该子类的方法会把原来继承过来的父类的方法隐藏,而不是重写。就是父类的方法和子类的方法是两个没有关系的方法,具体调用哪一个方法是看是哪个对象的引用;这种父子类方法也不存在多态的性质
**private 方法:**不可以覆盖 , private 修饰的变量和方法只能在当前类使用, 其他继承类不能访问 private 变量或方法,更不能覆盖。

10、静态变量和实例变量

**静态变量:**被 static 修饰的变量,属于类,不管创建多少个对象,在内存中有且仅有一个拷贝;可以实现让多个对象共享内存。
**实例变量:**属于某一实例,需先创建对象,通过对象才能访问到它。

6、构造方法特性

(1)名字与类名相同;
(2)没有返回值,但不能用 void 声明构造函数;
(3)成类的对象时自动执行,无需调用。

7、无参构造方法作用

执行子类的构造方法之前,如果没有用 super() 调用父类特定的构造方法,会调用父类中“无参数构造方法”。
如果父类中只定义有参构造,子类的构造方法中又没 super() 调用父类中特定的构造方法,编译时将报错,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是:在父类里加上一个无参构造。

8、创建对象的方式

1、使用 new 关键字;通过这种方式我们还可以调用任意无参的和有参对象;
2、使用 Class 类的 newInstance 方法,这个方法可以通过反射调用无参的构造器创建对象(反射):Class.forName.newInstance();
3、使用 clone() 方法;无论何时我们调用一个对象的clone方法,JVM都会创建一个新的对象,将前面的对象的内容全部拷贝进去,用clone方法创建对象并不会调用任何构造函数。要使用clone方法,我们必须先实现Cloneable接口并实现其定义的clone方法。
4、反序列化,比如调用 ObjectInputStream 类的 readObject() 方法。当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象,在反序列化时,JVM创建对象不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口。

9、抽象类和接口

(1)抽象类中可以定义构造函数,接口不能定义构造函数;
(2)抽象类中可以有抽象方法和具体方法,而接口中只能有抽象方法(public abstract);
(3)抽象类中的成员权限可以是 public、默认、protected(抽象类中抽象方法就是为了重写,所以不能被 private 修饰),而接口中的成员只可以是 public(方法默认:public abstrat、成员变量默认:public static final);
(4)抽象类中可以包含静态方法,而接口中不可以包含静态方法;

11、Integer 和 int 区别

(1)int 是 Java 八种基本数据类型之一, Integer 是为 int 类型提供的封装类;
(2)int默认值是 0,Integer 默认值是 null,Integer 可区分出未赋值和值为 0 的区分;
(3)Integer 必须实例化后才可以使用,int 不需要。

Integer int 的⽐较延伸

1、 Integer 变量实际上是对⼀个 Integer 对象的引⽤,所以两个通过 new ⽣成的 Integer 变量永远是不相等
的,因为其内存地址是不同的;
2、Integer 变量和 int 变量⽐较时,只要两个变量的值是相等的,结果为 true。因为包装类 Integer 和基本数据
类型 int 类型进⾏⽐较时,Java 会⾃动拆包装类为 int,然后进⾏⽐较,实际上就是两个 int 型变量在进⾏⽐较;
3、⾮ new ⽣成的 Integer 变量和 new Integer() ⽣成的变量进⾏⽐较时,结果为 false。因为⾮ new ⽣成的
Integer 变量指向的是 Java 常量池中的对象,⽽ new Integer() ⽣成的变量指向堆中新建的对象,两者在内存中的
地址不同;
4、对于两个⾮ new ⽣成的 Integer 对象进⾏⽐较时,如果两个变量的值在区间 [-128, 127] 之间,则⽐较结果为
true,否则为 false。

包装类的缓存:

Boolean:全部缓存

Byte:全部缓存

Character:<= 127 缓存

Short:-128 — 127 缓存

Long:-128 — 127 缓存

Integer:-128 — 127 缓存

Float:没有缓存

Doulbe:没有缓存

12、装箱和拆箱

自动装箱:Java 编译器在基本数据类型和对应得包装类之间做的一个转化。 int 转化 Integer,double 转化 Double 。
自动拆箱:反之
原始类型:boolean、char、byte、short、int、long、float、double
封装类型:Boolean、Character、Byte、Short、Integer、Long、Float、Double

13、final、finally、finalize

**final:**用于声明属性、方法和类,表示属性不可变、方法不可覆盖、修饰的类不可继承;
**finally:**异常处理语句结构的一部分,表示总是执行;
**finallize:**Object类的一个方法,在垃圾回收时会调用被回收对象的finalize

14、== 和 equals 的区别

**= =:**基本数据类型,数值是否相等;引用数据类型,比较对象的地址值是否相等。
equals :比较两个对象内容是否相等。**注意:**equals 不能比较基本数据类型的变量。如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址(很多类重写了 equals 方法,比如 String、Integer 把它变成值比较,一般情况下 equals 比较的是值是否相等)。

15、hashCode和equals
1、hashCode相同,equals也为 true 吗

不一定为 true:在散列表中,hashCode() 相等即两个键值对的哈希值相等,并不一定能得出键值对相等。

2、为啥重写 equals一定要重写 hashCode

这个问题有个前提,需要用到 HashMap、HashSet 等 Java 集合,用不到哈希表的话,仅仅重写 equals() 方法也可以。工作中的场景是常常用到 Java 集合,所以 Java 官方建议重写 equals() 就一定要重写 hashCode() 方法。

3、hashCode()与equals()的相关规定

1、两个对象相等,hashCode 一定也是相同的;
2、两个对象相等,对两个对象分别调用 equals 方法都返回 true;
3、两个对象有相同的 hashCode 值,它们也不一定是相等的;
4、equals 方法被覆盖过,则 hashCode 方法也必须被覆盖;
5、hashCode的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

17、Object的常⽤⽅法

clone :⽤于创建并返回当前对象的⼀份拷⻉;
getClass :⽤于返回当前运⾏时对象的 Class;
toString :返回对象的字符串表示形式;
inalize :实例被垃圾回收器回收时触发的⽅法;
equals :⽤于⽐较两个对象的内存地址是否相等,⼀般需要᯿写;
hashCode :⽤于返回对象的哈希值;
notify :唤醒⼀个在此对象监视器上等待的线程。如果有多个线程在等待只会唤醒⼀个。
notifyAll :作⽤跟 notify() ⼀样,只不过会唤醒在此对象监视器上等待的所有线程,⽽不是⼀个线程。
wait :让当前对象等待;

18、& && 的区别

&& 和 & 都表示与的逻辑运算符,两边的表达式都为 true 时,整个运算结果为 true,否则为 false。
&&:有短路功能,当第⼀个表达式的值为 false 的时候,则不再计算第⼆个表达式;
& :不管第⼀个表达式是否为 true,第⼆个都会执⾏。 还可⽤作位运算符: 两边的表达式不是 Boolean 类型时,& 表示按位操作

19、参数传递时:传值or引⽤

Java 的参数是:传递的形式传⼊⽅法中
当传递⽅法参数类型为基本数据类型时,⼀个⽅法不可能修改⼀个基本数据类型的参数。
当传递⽅法参数类型为引⽤数据类型时,⼀个⽅法将修改⼀个引⽤数据类型的参数所指向对象的值。即使 Java 函数在传递引⽤数据类型时,只拷⻉了引⽤值,之所以能修改引⽤数据是因为它们同时指向了⼀个对象,仍然是按值调⽤⽽不是引⽤调⽤。

20、 Math.round(-1.5)

等于 -1。因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负 0.5 是直接舍弃。

21、⼆进制数的异或

两个⼆进制数异或结果是这两个⼆进制数差的绝对值。表达式如下:a^b = |a-b|。

两个⼆进制 a 与 b 异或,即 a 和 b 两个数按位进⾏运算。如果对应的位相同,则为 0(相当于对应的算术相减),如果不同为 1(相当于对应的算术相加)。由于⼆进制每个位只有两种状态,要么是 0,要么是 1,按位异或操作可看做是为按位相减取相对值,再按位累加。

22、对象的克隆

(1)实现 Cloneable 接⼝重写 Object 类中的 clone() ⽅法;
(2)实现 Serializable 接⼝,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。

23、深克隆和浅克隆

**浅克隆:**拷⻉对象和原始对象的引⽤类型引⽤同⼀个对象。浅克隆只是复制了对象的引⽤地址,两个对象指向同⼀个内存地址,所以修改其中任意的值,另⼀个值会随之变化,这就是浅克隆。
**深克隆:**拷⻉对象和原始对象的引⽤类型引⽤不同对象。是将对象及值复制过来,两个对象修改其中任意的值另⼀个值不改变,(例:JSON.parse() 和 JSON.stringify(),但是此⽅法⽆法复制函数类型)。
补充:深克隆的实现是在引⽤类型所在类实现 Cloneable 接⼝,并使⽤ public 访问修饰符重写 clone ⽅法。 clone 没有深浅之分,都是统⼀的调⽤ Object 的 clone ⽅法。为什么会有深克隆的概念?由于在实现的过程中刻意嵌套了 clone ⽅法的调⽤。也就是说深克隆需要克隆的对象类型的类中重新实现克隆⽅法 clone()。

24、序列化

**序列化:**是⽤于将对象状态转换为字节流的过程,可将对象保存到磁盘⽂件中或通过⽹络发送到任何其他程序。
**反序列化:**从字节流创建对象的相反的过程称为反序列化。
创建的字节流与平台⽆关,在⼀个平台上序列化的对象可以在不同的平台上反序列化。序列化是为了解决在对象流进⾏读写操作时所引发的问题。
序列化的实现:将要被序列化的类实现 Serializable 接⼝,(接⼝没有需要实现的⽅法,只是标注对象是可被序列化的),然后⽤⼀个输出流(如:FileOutputStream)来构造⼀个 ObjectOutputStream 对象,接着⽤ ObjectOutputStream 对象的 writeObject(Object obj) ⽅法可以将参数为 obj 的对象写出,要恢复的话则使⽤输⼊流。
什么情况下需要序列化
(1)当你想把的内存中的对象状态保存到⼀个⽂件中或者数据库中时候;
(2)当你想⽤套接字在⽹络上传送对象的时候;
(3)当你想通过 RMI 传输对象的时候。

泛型与反射

1、反射

1、反射是什么意思

每个类都有⼀个 Class 对象,包含了与类有关的信息。当编译⼀个新类时,会产⽣⼀个同名的 .class ⽂件,该⽂件内容保存着 Class 对象。类加载相当于 Class 对象的加载,类在第⼀次使⽤时才动态加载到 JVM 中。也可以使⽤Class.forName(“com.mysql.jdbc.Driver”) 这种⽅式来控制类的加载,⽅法会返回⼀个 Class 对象。
反射可以提供运⾏时的类信息,并且这个类可以在运⾏时才加载进来,甚⾄在编译时期该类的 .class 不存在也可以加载进来。Class 和 java.lang.reflect ⼀起对反射提供了⽀持,java.lang.reflect 类库主要包含了以下三个类:
(1)Field :可以使⽤ get() 和 set() ⽅法读取和修改 Field 对象关联的字段;
(2)Method :可以使⽤ invoke() ⽅法调⽤与 Method 对象关联的⽅法;
(3)Constructor :可以⽤ Constructor 创建新的对象。

2、应⽤场景

应⽤举例:⼯⼚模式,使⽤反射机制,根据全限定类名获得某个类的 Class 实例

3、优缺点

**优点:**运⾏期类型的判断,class.forName() 动态加载类,提⾼代码的灵活度;

**缺点:**如果⼀个功能可以不⽤反射完成,那么最好就不⽤。
(1)性能开销 :反射涉及了动态类型的解析,所以 JVM ⽆法对这些代码进⾏优化。反射操作的效率要⽐⾮反射操作低得多。应该避免在经常被执⾏的代码或对性能要求很⾼的程序中使⽤反射。
(2)安全限制 :使⽤反射技术要求程序必须在⼀个没有安全限制的环境中运⾏。如果程序必须在有安全限制的环境中运⾏,如 Applet
(3)内部暴露:由于反射允许代码执⾏⼀些在正常情况下不被允许的操作(⽐如:访问私有的属性和⽅法),所以使⽤反射可能会导致意料之外的副作⽤,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发⽣改变的时候,代码的⾏为就有可能也随着变化。

2、泛型

1、泛型如何⼯作

泛型通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运⾏时不存在任何类型相关的信息。例如:List 在运⾏时仅⽤⼀个 List 来表示。这样做的⽬的,是确保能和 Java 5 之前的版本开发⼆进制类库进⾏兼容。

2、类型擦除

类型擦除:泛型信息只存在于代码编译阶段,在进⼊ JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 < T > 则会被转译成普通的 Object 类型,如果指定了上限如 < T extends String > 则类型参数就被替换成类型上限。

3、限定通配符和⾮限定通配符

限定通配符对类型进⾏了限制。泛型类型必须⽤限定内的类型来进⾏初始化,否则会导致编译错误。
两种:
(1)< ? extends T > 通过确保类型必须是 T 的⼦类来设定类型上界
(2)< ? super T >通过确保类型必须是 T 的⽗类来设定类型的下界。

⾮限定通配符: < ? > ,因为 < ? > 可以⽤任意类型来替代。

4、List<? extends T> 和 List <? super T>

List< ? extends T > 可以接受任何继承⾃ T 的类型的 List,
List < ?super T > 可以接受任何 T 的⽗类构成的 List。例如 List< ? extends Number > 可以接受 List< Integer > 或 List 。

3、动态代理

1、动态代理是什么

当想要给实现了某个接⼝的类中的⽅法,加⼀些额外的处理。⽐如说加⽇志,加事务等。可以给这个类创建⼀个代理,就是创建⼀个新的类,这个类不仅包含原来类⽅法的功能,还在原来的基础上添加了额外处理的新功能。这个代理类并不是定义好的,是动态⽣成的。具有解耦意义,灵活,扩展性强。

2、怎么实现

⾸先必须定义⼀个接⼝,还要有⼀个 InvocationHandler处理类(将实现接⼝的类的对象传递给它)。再有⼀个⼯具代理类 Proxy(因为调⽤它的 newInstance() 可以产⽣代理对象,其实它只是⼀个产⽣代理对象的⼯具类)。利⽤InvocationHandler,拼接代理类源码,将其编译⽣成代理类的⼆进制码,利⽤加载器加载,并将其实例化产⽣代理对象,最后返回。
每个动态代理类都必须要实现InvocationHandler 这个接⼝,并且每个代理类的实例都关联到了⼀个 handler,当通过代理对象调⽤⼀个⽅法的时候,这个⽅法的调⽤就会被转发为由 InvocationHandler 这个接⼝的invoke ⽅法来进⾏调⽤。我们来看看 InvocationHandler 这个接⼝的唯⼀⼀个⽅法 invoke ⽅法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy: 指代我们所代理的那个真实对象
method: 指代的是我们所要调⽤真实对象的某个⽅法的 Method 对象
args: 指代的是调⽤真实对象某个⽅法时接受的参数
Proxy 类的作⽤是动态创建⼀个代理对象的类。它提供了许多的⽅法,但是我们⽤的最多的就是
newProxyInstance 这个⽅法:Object invoke(Object proxy, Method method, Object[] args) throws Throwableloader:⼀个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对⽣成的代理对象进⾏加载;
interfaces:⼀个 Interface 对象的数组,表示的是我将要给我需要代理的对象提供⼀组什么接⼝,如果我提供了⼀组接⼝给它,那么这个代理对象就宣称实现了该接⼝(多态),这样我就能调⽤这组接⼝中的⽅法了
handler:⼀个 InvocationHandler 对象,表示的是当我这个动态代理对象在调⽤⽅法的时候,会关联到哪⼀个InvocationHandler 对象上。通过 Proxy.newProxyInstance 创建的代理对象是在 Jvm 运⾏时动态⽣成的⼀个对象,它并不是我们的InvocationHandler 类型,也不是我们定义的那组接⼝的类型,⽽是在运⾏是动态⽣成的⼀个对象。

3、哪些应⽤

动态代理的应⽤:Spring 的 AOP 、加事务、加权限、加⽇志。

异常

1、常⻅的异常类

NullPointerException:当应⽤程序试图访问空对象时,则抛出该异常。
SQLException:提供关于数据库访问错误或其他错误信息的异常
IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
FileNotFoundException:当试图打开指定路径名表示的⽂件失败时,抛出此异常。
IOException:当发⽣某种 I/O 异常时,抛出此异常。此类是失败或中断的 I/O 操作⽣成的异常的通⽤类。
ClassCastException:当试图将对象强制转换为不是实例的⼦类时,抛出该异常。
IllegalArgumentException:抛出的异常表明向⽅法传递了⼀个不合法或不正确的参数。

2、throw throws

throw:在⽅法体内部,表示抛出异常,由⽅法体内部的语句处理;throw 是具体向外抛出异常的动作,它抛出的是⼀个异常实例;
throws:在⽅法声明后⾯,表示如果抛出异常,该⽅法的调⽤者进⾏异常的处理;表示出现异常的可能性,并不⼀定会发⽣这种异常。

3、运⾏时异常与受检异常

运⾏时异常:如:空指针异常、指定的类找不到、数组越界、⽅法传递参数错误、数据类型转换错误。可以编译通过,但是⼀运⾏就停⽌了,程序不会⾃⼰处理;
受检查异常:要么⽤ try … catch… 捕获,要么⽤ throws 声明抛出,交给⽗类处理。

4、Error Exception

Error 类和 Exception 类的⽗类都是 Throwable 类。主要区别如下:
Error 类: 指与虚拟机相关的问题,如:系统崩溃、虚拟机错误、内存空间不⾜、⽅法调⽤栈溢出等。这类错误将会导致应⽤程序中断,仅靠程序本身⽆法恢复和预防;
Exception 类:分为运⾏时异常和受检查的异常。

5、finally 何时执⾏

异常处理中,finally 块的作⽤就是为了保证⽆论出现什么情况,finally 块⾥的代码⼀定会被执⾏。由于 return 意味着结束前函数的调⽤跳出这个函数,因此任何语句都只能在 return 前执⾏(除⾮碰到 exit 函数),因此 finally 块⾥的代码也是在 return 之前执⾏的。如果 try-finally 或者 catch-finally 中都有 return,那么 finally 块中的 return 会覆盖别处的 return ,最终返回 finally 中 return 的值。

6、finally⼀定会执⾏吗

不⼀定:

(1)当程序进⼊ try 块之前就出现异常时,会直接结束,不会执⾏ finally 块中的代码;
(2)当程序在 try 块中强制退出时也不会去执⾏ finally 块中的代码,⽐如在 try 块中执⾏ exit ⽅法。

7、try-catch-finally 中,如果 **catch 中 **return了,finally还会执⾏吗?

会。程序在执⾏到 return 时会⾸先将返回值存储在⼀个指定的位置,再去执⾏ finally 块,最后再返回。
1、对基本数据类型,在 finally 块中改变 return 的值没有任何影响,直接覆盖掉。
2、对引⽤类型,返回在 finally 对 前⾯ return 语句返回对象的修改值。

8、try-catch-finally哪个可以省略

catch 可以省略。
try :只适合处理运⾏时异常
try+catch :适合处理运⾏时异常+普通异常。只⽤try 去处理普通异常却不加以 catch 处理,编译是通不过的,因为编译器硬性规定,普通异常选择捕获,必须⽤ catch 显示声明进⼀步处理。⽽运⾏时异常在编译时没有如此规定,所以 catch 可以省略。

9、主线程捕获子线程异常

正常情况下,如果不做特殊的处理,在主线程中是不能够捕获到⼦线程中的异常。如果想要在主线程中捕获⼦线程的异常,可以⽤如下的⽅式进⾏处理,使⽤ Thread 的静态⽅法

Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandle());

集合

1、常⽤的容器

Collection
Set
1、TreeSet:基于红⿊树实现,⽀持有序性操作,例如:根据⼀个范围查找元素的操作。但是查找效率不如
2、HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。
3、HashSet:基于哈希表实现,⽀持快速查找,不⽀持有序性操作。失去了元素的插⼊顺序信息,使⽤ Iterator 遍历 HashSet 得到的结果是不确定的。
4、LinkedHashSet:具有 HashSet 的查找效率,且内部使⽤双向链表维护元素的插⼊顺序。
List
1、ArrayList:基于动态数组实现,⽀持随机访问。
2、Vector:和 ArrayList 类似,但它是线程安全的。
3、 LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插⼊和删除元素。不仅如此LinkedList 还可以⽤作栈、队列和双向队列。
Queue
1、LinkedList:可以⽤它来实现双向队列。
2、 PriorityQueue:基于堆结构实现,可以⽤它来实现优先队列。

Map

1、TreeMap:基于红⿊树实现。
2、HashMap:基于哈希表实现。
3、HashTable:和 HashMap 类似,但它是线程安全的,这意味着同⼀时刻多个线程可以同时写⼊ HashTable并且不会导致数据不⼀致。它是遗留类,不应该去使⽤它。现在可以使⽤ ConcurrentHashMap 来⽀持线程安全,并且 ConcurrentHashMap 的效率会更⾼,因为 ConcurrentHashMap 引⼊了分段锁。
4、 LinkedHashMap:使⽤双向链表来维护元素的顺序,顺序为插⼊顺序或者最近最少使⽤(LRU)顺序。

2、Array ArrayList

1、Array 可以容纳基本类型和对象,⽽ ArrayList 只能容纳对象;
2、Array 是指定⼤⼩的,⽽ ArrayList ⼤⼩是固定的。
什么时候更适合使⽤ Array
1、如果列表的⼤⼩已经指定,⼤部分情况下是存储和遍历它们;
2、对于遍历基本数据类型,尽管 Collections 使⽤⾃动装箱来减轻编码任务,在指定⼤⼩的基本类型的列表上⼯作也会变得很慢;
3、 如果你要使⽤多维数组,使⽤ [ ][ ] ⽐ List> 更容易。

2、ArrayList和LinkedList区别

1、**ArrayList:**底层是基于数组实现,查找快,增删较慢;
2、**LinkedList:**底层基于链表实现,查找慢、增删快。LinkedList 链表由⼀系列表项连接,⼀个表项包含 3 个部分:元素内容、前驱表和后驱表。链表内部有⼀个 header 表项,既是链表的开始也是链表的结尾。header 的后继表项是链表中的第⼀个元素,header 的前驱表项是链表中的最后⼀个元素。

ArrayList 的增删未必就是⽐ LinkedList 要慢:
1、如果增删都是在末尾来操作【每次调⽤的都是 remove() 和 add()】,此时 ArrayList 就不需要移动和复制数组来进⾏操作了。如果数据量有百万级的时,速度是会⽐ LinkedList 要快的。
2、如果删除操作的位置在中间。LinkedList 消耗主要是在遍历上,ArrayList 消耗主要是在移动和复制上。LinkedList 的遍历速度是要慢于 ArrayList 的复制移动速度的如果数据量有百万级的时,还是 ArrayList 要快。

3、ArrayList

1、RandomAccess

1、RandomAccess 接⼝是⼀个标志接⼝,只要 List 集合实现这个接⼝,就能⽀持快速随机访问。通过查看Collections 类中binarySearch() ⽅法,可以看出,判断 List 是否实现 RandomAccess 接⼝来实⾏indexedBinarySerach(list, key) 或 iteratorBinarySerach(list, key)⽅法。实现 RandomAccess 接⼝的 List 集合采⽤ for 循环遍历,未实现这接⼝采⽤迭代器,即ArrayList ⼀般采⽤ for 循环遍历, LinkedList ⼀般采⽤迭代器遍历;
2、ArrayList ⽤ for 循环遍历⽐ iterator 迭代器遍历快,LinkedList ⽤ iterator 迭代器遍历⽐ for 循环遍历快。在做项⽬时,应该考虑到 List 集合的不同⼦类采⽤不同的遍历⽅式,能够提⾼性能。

2、ArrayList 扩容机制

1、当⽤ add ⽅法时⾸先调⽤ ensureCapacityInternal ⽅法,传⼊ size+1 进去,检查是否需要扩充elementData 数组⼤⼩;
2、newCapacity = 扩充数组为原来的 1.5 倍(不能⾃定义),如果还不够,就使⽤它指定要扩充的⼤⼩minCapacity ,然后判断 minCapacity 是否⼤于 MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8) ,如果⼤于,就取 Integer.MAX_VALUE;
3、 扩容的主要⽅法:grow;
4、ArrayList 中 copy 数组的核⼼就是 System.arraycopy ⽅法,将 original 数组的所有数据复制到 copy 数组中,这是⼀个本地⽅法。

4、HashMap

1、实现原 or 底层结构

JDK1.7:Entry数组 + 链表
JDK1.8:Node 数组 + 链表/红⿊树,当链表上的元素个数超过 8 个并且数组⻓度 >= 64 时⾃动转化成红⿊树,节点变成树节点,以提⾼搜索效率和插⼊效率到 O(logN)。Entry 和 Node 都包含 key、value、hash、next 属性。

2、 put执⾏过程

往⼀个 HashMap 中添加⼀对 key-value 时,系统⾸先会计算 key 的 hash 值,然后根据 hash 值确认在table 中存储的位置。若该位置没有元素,则直接插⼊。否则迭代该处元素链表依次⽐较 key 的 hash 值。如果两个 hash 值相等且 key 值相等(e.hash ==hash && ((k = e.key) == key || key.equals(k))),则⽤新的 Entry 的value 覆盖原来节点的 value。如果两个 hash 值相等但 key 值不等 ,则将该节点插⼊该链表的链头。

3、get执⾏过程

通过 key 的 hash 值找到在 table 数组中索引处的 Entry,然后返回该 key 对应的 value 即可。能够根据 key 快速的取到 value 除了和 HashMap 的数据结构有关,还和 Entry 有关。HashMap 在存储过程中并没有将 key,value 分开存储,⽽是当做⼀个整体 key-value 来处理的,这个整体就是Entry 对象。 value 只相当于 key 的附属。在存储的过程中,系统根据 key 的 HashCode 来决定 Entry在 table 数组中的存储位置,在取的过程中同样根据 key 的 HashCode 取出相对应的 Entry 对象(value 就包含在⾥⾯)。

4、 resize ⽅法的执⾏过程

有两种情况会调⽤ resize ⽅法:
1、第⼀次调⽤ HashMap 的 put ⽅法时,会调⽤ resize ⽅法对 table 数组进⾏初始化,如果不传⼊指定值,默认⼤⼩为 16。
2、扩容时会调⽤ resize,当 size > threshold 时,table 数组⼤⼩翻倍。

每次扩容之后容量都是翻倍。扩容后要将原数组中的所有元素找到在新数组中的位置。
当我把 table[i] 位置的所有 Node 迁移到 newtab 中去的时候:⾥⾯的 node 要么在 newtab 的 i 位置(不变),要么在 newtab 的 i + n 位置。也就是我们可以这样处理:把 table[i] 这个桶中的 node 拆分为两个链表 l1和 l2:如果 hash & n == 0,那么当前这个 node 被连接到 l1 链表;否则连接到 l2 链表。这样下来,当遍历完table[i] 处的所有 node 的时候,我们得到两个链表 l1 和 l2,这时我们令 newtab[i] = l1,newtab[i + n] = l2,这就完成了 table[i] 位置所有 node 的迁移(rehash),这也是 HashMap 中容量⼀定的是 2 的整数次幂带来的⽅便之处。

5、size 为何是 2 整数次⽅

1、能够保证 HashMap 的底层数组⻓度是2 的 n 次⽅。当 length 为 2 的 n 次⽅时,h & (length - 1)就相当于对 length 取模,⽽且速度⽐直接取模快得多,这是 HashMap 在速度上的⼀个优化。⽽且每次扩容时都是翻倍。
2、如果 length 为 2 的次幂,则 length - 1 转化为⼆进制必定是 11111……的形式,在与 h 的⼆进制进⾏与操作时效率会⾮常的快,⽽且空间不浪费。如果 length 不是 2 的次幂,⽐如:length 为 15,则 length - 1为 14,对应的⼆进制为 1110,在于 h 与操作,最后⼀位都为 0 ,⽽ 0001,0011,0101,1001,1011,0111,1101 这⼏个位置永远都不能存放元素了,空间浪费相当⼤,更糟的是这种情况中,数组可以使⽤的位置⽐数组⻓度⼩了很多,意味着加了碰撞的⼏率,减慢了查询的效率,会造成空间的浪费。

6、多线程死循环

多线程同时 put 时,如果触发了 rehash 操作,会导致 HashMap 中的链表中出现循环节点,使得后⾯ get 的时候,会死循环。

7、get 能否判断某元素在 map

HashMap 的 get 函数的返回值不能判断⼀个 key 是否包含在 map 中,因为 get 返回 null 有可能是不包含该key,也有可能该 key 对应的 value 为 null。因为 HashMap 中允许 key 为 null,也允许 value 为 null。

5、HashMap HashTable

1、**HashMap 是基于 AbstractMap,Hashtble 基于 Dictionary 类。**Dictionary 是任何可将键映射到相应值类的抽象⽗类,⽽ AbstractMap 基于 Map 接⼝。
2、HashMap 的 key 和 value 都可 null,Hashtable 的 key 和 value 都不可为 null。HashMap 遇到key 为 null 的时候,调⽤ putForNullKey ⽅法进⾏处理,对 value 没有处理;Hashtable 遇到 null,直接返回 NullPointerException。
3、**HashMap 线程不安全的,HashTable 线程安全,**可以通过Collections.synchronizedMap(hashMap)实现同步。HashTable 线程安全的策略代价却太⼤,get/put 相关操作都是 synchronized 的,这相当于给整个哈希表加了⼀把锁,多线程访问时候,只要有⼀个线程访问或操作该对象,其他线程只能阻塞,相当于将所有的操作串⾏化,高并发场景中性能会很差。

6、HashMap ConcurrentHashMap

HashMap 线程不安全, ConcurrentHashMap 线程安全
ConcurrentHashMap 采⽤锁分段技术,将整个Hash桶进⾏了分段segment,将⼤的数组分成⼏个⼩⽚段 segment,每个⼩⽚段 segment 上都有锁存,在插⼊元素时候需要先找到应该插⼊到哪⼀个⽚段 segment,再在这个⽚段上⾯进⾏插⼊,还需要获取 segment 锁,减⼩了锁的粒度

7、HashTable ConcurrentHashMap

HashTable 和 ConcurrentHashMap 相⽐,效率低。 Hashtable 效率低主要是使⽤了 synchronized 关键字对 put 等操作进⾏加锁,⽽ synchronized 关键字加锁是对整张 Hash 表,每次锁住整张表让线程独占,效率低,ConcurrentHashMap 在对象中保存了⼀个 Segment 数组,将整个 Hash 表分为多个分段;每个分段类似于⼀个Hashtable;在执⾏ put 操作时⾸先根据 hash 算法定位元素属于哪个 Segment,然后对该 Segment 加锁,因此 ConcurrentHashMap 在多线程中可实现多线程 put操作。

8、ConcurrentHashMap

实现原理:数据结构
JDK 7: 采⽤了数组 + Segment + 分段锁的⽅式实现。
JDK 8:采⽤了数组 + 链表 + 红⿊树的实现⽅式来设计,内部⼤量采⽤ CAS 操作。
ConcurrentHashMap 采⽤了⾮常精妙的"分段锁"策略,ConcurrentHashMap 的主⼲是个 Segment 数组。

final Segment<K,V>[] segments;

Segment 继承 ReentrantLock,它是⼀种可重⼊锁(ReentrantLock)。在 ConcurrentHashMap,⼀个Segment 是⼀个⼦哈希表,Segment 维护了⼀个 HashEntry 数组,并发环境下,不同 Segment 的数据进⾏操作是不⽤考虑锁竞争的。按默认的 ConcurrentLevel 为 16 来讲,理论上就允许 16 个线程并发执⾏。对同⼀Segment 操作需考虑线程同步,不同的Segment ⽆需考虑。Segment 类似于HashMap,⼀个 Segment 维护⼀个HashEntry 数组:

transient volatile HashEntry<K,V>[] table;

HashEntry 是⽬前提到最⼩逻辑处理单元。⼀个 ConcurrentHashMap 维护⼀个 Segment 数组,⼀个Segment 维护⼀个 HashEntry 数组。ConcurrentHashMap 定位⼀个元素的过程需要进⾏两次 Hash 操作。第⼀次 Hash 定位到 Segment,第⼆次 Hash 定位到元素所在的链表的头部。

9、HashSet

实现原理
依赖于 HashMap,HashSet 的值都是存储在 HashMap 中。HashSet 的构造法中会初始化⼀个 HashMap 对象,HashSet 不允许值重复。HashSet 的值是作为 HashMap 的 key 存储在HashMap 中的,当存储的值已经存在时返回 false。
元素不重复
元素值作为 map 的 key,map 的 value 是 PRESENT 变量,这个变量只是作为放⼊ map 时的⼀个占位符存在。HashMap 的 key 不能重复,HashSet 的元素作为 map 的 key,也不能重复。

10、LinkedHashMap

是基于 HashMap 实现的,不同的是它定义了⼀个 Entry header, header 不是放在Table ⾥,它是额外独⽴出来的。LinkedHashMap 通过继承 hashMap 中的 Entry,并添加两个属性 Entrybefore,after 和 header 结合组成⼀个双向链表,实现按插⼊顺序或访问顺序排序。LinkedHashMap 定义了排序模式 accessOrder,属性为 boolean 型变量,对于访问顺序,为 true;对于插⼊顺序,为 false。⼀般情况下,不必指定排序模式,迭代顺序就默认为插⼊顺序。

11、Iterator

1、怎么使⽤?有什么特点?

迭代器是⼀种设计模式,是⼀个对象,可以遍历并选择序列中的对象,通常被称为“轻量级”对象,创建它的代价⼩。只能单向移动:  
1、 使⽤⽅法 iterator() 要求容器返回⼀个 Iterator。第⼀次调⽤ Iterator 的 next() ⽅法时,返回序列的第⼀个元素。注意:iterator() ⽅法是 java.lang.Iterable 接⼝,被 Collection 继承。  
2、使⽤next() 获得序列中的下⼀个元素。 
3、使⽤hasNext() 检查序列中是否还有元素。  
4、使⽤remove() 将迭代器新返回的元素删除。

12、Iterator ListIterator

Iterator 可⽤来遍历 Set 和 List 集合,但是 ListIterator 只能⽤来遍历 List。Iterator 对集合只能是前向遍历,ListIterator 既可以前向也可以后向。ListIterator 实现了 Iterator 接⼝,并包含其他的功能,⽐如:增加元素,替换元素,获取前⼀个和后⼀个元素的索引等等。

13、Iterator Enumeration

与 Enumeration 相⽐,Iterator 更加安全,因为当⼀个集合正在被遍历的时候,它会阻⽌其它线程去修改集合。否则会抛出 ConcurrentModificationException 异常。这其实就是 fail-fast 机制。具体区别有三点:
1、Iterator 的⽅法名⽐ Enumeration 更科学;
2、Iterator 有 fail-fast 机制,⽐ Enumeration 更安全;
3、Iterator 能够删除元素,Enumeration 并不能删除元素。

14、fail-fast fail-safe

Iterator 的 fail-fast 属性与当前的集合共同起作⽤,因此它不会受到集合中任何改动的影响。Java.util 包中的所有集合类都被设计为 fail-fast 的,⽽ java.util.concurrent 中的集合类都为 fail-safe 的。当检测到正在遍历的集合的结构被改变时,fail-fast 迭代器抛出ConcurrentModificationException,⽽ fail-safe 迭代器从不抛出ConcurrentModificationException。

15、Collection Collections

Collection:是最基本的集合接⼝,⼀个 Collection 代表⼀组 Object,即 Collection 的元素。它的直接继承接⼝有List,Set 和 Queue。Collections:是不属于 Java 的集合框架的,它是集合类的⼀个⼯具类/帮助类。此类不能被实例化, 服务于 Java的 Collection 框架。它包含有关集合操作的静态多态⽅法,实现对各种集合的搜索、排序、线程安全等操作。

并发

1、并⾏和并发

1、并⾏是指两个或者多个事件在同⼀时刻发⽣;⽽并发是指两个或多个事件在同⼀时间间隔发⽣;
2、并⾏是在不同实体上的多个事件,并发是在同⼀实体上的多个事件;
3、在⼀台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如 Hadoop 分布式集群。所以并发编程的⽬标是充分的利⽤处理器的每⼀个核,以达到最⾼的处理性能。

2、线程和进程

**进程:**是程序运⾏和资源分配的基本单位,⼀个程序⾄少有⼀个进程,⼀个进程⾄少有⼀个线程。进程在执⾏过程中拥有独⽴的内存单元,⽽多个线程共享内存资源,减少切换次数,从⽽效率更⾼。
**线程:**是进程的⼀个实体,是 cpu 调度和分派的基本单位,是⽐程序更⼩的能独⽴运⾏的基本单位。同⼀进程中的多个线程之间可以并发执⾏。

3、守护线程

守护线程(即 Daemon thread),是个服务线程,准确地来说就是服务其他的线程

4、创建线程的⼏种⽅式 (阿里)

1、继承 Thread 类创建线程;
2、实现 Runnable 接⼝创建线程;
3、通过 Callable 和 Future 创建线程;
4、通过线程池创建线程。

5、Runnable Callable

区别:

1、Runnable 接口中的 run() 方法的返回值是 void,它只是去执行 run() 方法中的代码;
2、Callable 接口中的 call() 方法是有返回值的,是一个泛型,和 Future、FutureTask 配合可用来获取异步执行的结果。

6、线程状态及转换

Thread 的源码中定义了6种状态:new(新建)、runnnable(可运行)、blocked(阻塞)、waiting(等待)、time waiting (定时等待)和 terminated(终止)。

img

7、sleep() wait()

1、sleep() ⽅法正在执⾏的线程主动让出 cpu(然后 cpu 就可以去执⾏其他任务),在 sleep 指定时间后 cpu 再回到该线程继续往下执⾏(注意:sleep ⽅法只让出了 cpu,⽽并不会释放同步资源锁);⽽ wait() ⽅法则是指当前线程让⾃⼰暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进⽽运⾏,只有调⽤了 notify() ⽅法,之前调⽤ wait() 的线程才会解除 wait 状态,可以去参与竞争同步资源锁,进⽽得到执⾏。(注意:notify 的作⽤相当于叫醒睡着的⼈,⽽并不会给他分配任务,就是说 notify 只是让之前调⽤ wait 的线程有权利᯿新参与线程的调度);
2、 sleep() ⽅法可以在任何地⽅使⽤,⽽ wait() ⽅法则只能在同步⽅法或同步块中使⽤;
3、 sleep() 是线程类(Thread)的⽅法,调⽤会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间⾃动恢复;wait() 是 Object 的⽅法,调⽤会放弃对象锁,进⼊等待队列,待调⽤ notify()/notifyAll() 唤醒指定的线程或者所有线程,才会进⼊锁池,不再次获得对象锁才会进⼊运⾏状态。

8、 run() start()

1、每个线程都是通过某个特定 Thread 对象所对应的⽅法 run() 来完成其操作的,⽅法 run() 称为线程体。通过调⽤ Thread 类的 start() ⽅法来启动⼀个线程;
2、start() ⽅法来启动⼀个线程,真正实现了多线程运⾏。这时⽆需等待 run() ⽅法体代码执⾏完毕,可以直接继续执⾏下⾯的代码;这时此线程是处于就绪状态,并没有运⾏。然后通过此 Thread 类调⽤⽅法 run() 来完成其运⾏状态,这⾥⽅法 run() 称为线程体,它包含了要执⾏的这个线程的内容,run() ⽅法运⾏结束,此线程终⽌。然后cpu 再调度其它线程;
3、 run() ⽅法是在本线程⾥的,只是线程⾥的⼀个函数,⽽不是多线程的。如果直接调⽤ run(),其实就相当于是调⽤了⼀个普通函数⽽已,直接待⽤ run() ⽅法必须等待 run() ⽅法执⾏完毕才能执⾏下⾯的代码,所以执⾏路径还是只有⼀条,根本就没有线程的特征,所以在多线程执⾏时要使⽤ start() ⽅法⽽不是 run() ⽅法。

9、怎么保证多线程的运⾏安全

线程安全在三个⽅⾯体现:
原⼦性:提供互斥访问,同⼀时刻只能有⼀个线程对数据进⾏操作,(atomic,synchronized);
可⻅性:⼀个线程对主内存的修改可以及时地被其他线程看到,(synchronized、volatile);
有序性:⼀个线程观察其他线程中的指令执⾏顺序,由于指令᯿排序,该观察结果⼀般杂乱⽆序,(happensbefore 原则)。

10、同步的⼏种⽅法

1、使⽤ Synchronized 关键字;
2、wait 和 notify;
3、使⽤特殊域变量 volatile 实现线程同步;
4、 使⽤可᯿⼊锁实现线程同步;
5、 使⽤阻塞队列实现线程同步;
6、使⽤信号量 Semaphore。

11、Thread.interrupt() ⼯作原理

在 Java 中,线程的中断 interrupt 只是改变了线程的中断状态,⾄于这个中断状态改变后带来的结果,那是⽆法确定的,有时它更是让停⽌中的线程继续执⾏的唯⼀⼿段。不但不是让线程停⽌运⾏,反⽽是继续执⾏线程的⼿段。在⼀个线程对象上调⽤ interrupt() ⽅法,真正有影响的是 wait、join、sleep ⽅法,当然这 3 个⽅法包括它们的᯿载⽅法。请注意:上⾯这三个⽅法都会抛出 InterruptedException。 1、对于 wait 中的等待 notify、notifyAll 唤醒的线程,其实这个线程已经“暂停”执⾏,因为它正在某⼀对象的休息室中,这时如果它的中断状态被改变,那么它就会抛出异常。这个 InterruptedException 异常不是线程抛出的,⽽是 wait ⽅法,也就是对象的 wait ⽅法内部会不断检查在此对象上休息的线程的状态,如果发现哪个线程的状态被置为已中断,则会抛出 InterruptedException,意思就是这个线程不能再等待了,其意义就等同于唤醒它了,然后执⾏ catch 中的代码。2、 对于 sleep 中的线程,如果你调⽤了 Thread.sleep(⼀年);现在你后悔了,想让它早些醒过来,调⽤interrupt() ⽅法就是唯⼀⼿段,只有改变它的中断状态,让它从 sleep 中将控制权转到处理异常的 catch 语句中,然后再由 catch 中的处理转换到正常的逻辑。同样,对于 join 中的线程你也可以这样处理。

12、 ThreadLocal

1、Java 的 Web 项⽬⼤部分都是基于 Tomcat。每次访问都是⼀个新的线程,每⼀个线程都独享⼀个ThreadLocal,我们可以在接收请求的时候 set 特定内容,在需要的时候 get 这个值。
2、 ThreadLocal 提供 get 和 set ⽅法,为每⼀个使⽤这个变量的线程都保存有⼀份独⽴的副本。

public T get() {...}
public void set(T value) {...}
public void remove() {...}
protected T initialValue() {...}

1、get() ⽅法是⽤来获取 ThreadLocal 在当前线程中保存的变量副本;
2、set() ⽤来设置当前线程中变量的副本;
3、 remove() ⽤来移除当前线程中变量的副本;
4、 initialValue() 是⼀个 protected ⽅法,⼀般是⽤来在使⽤时进⾏᯿写的,如果在没有 set 的时候就调⽤ get,会调⽤ initialValue ⽅法初始化内容。

13、 ThreadLocal

哪些场景下会使⽤

在调⽤ API 接⼝的时候传递了⼀些公共参数,这些公共参数携带了⼀些设备信息(是安卓还是 ios),服务端接⼝根据不同的信息组装不同的格式数据返回给客户端。假定服务器端需要通过设备类型(device)来下发下载地址,当然接⼝也有同样的其他逻辑,我们只要在返回数据的时候判断好是什么类型的客户端就好了。这种场景就可将传进来的参数 device 设置到 ThreadLocal 中。⽤的时候取出来就⾏。避免了参数的层层传递。

synchronized

1、了解

synchronized解决的是多线程访问资源的同步性问题,可以保证它修饰的⽅法或代码块,在任意时刻只能有⼀个线程执⾏。在 早期版本,synchronized 属于重量级锁,效率低,因为监视器锁是依赖于底层的操作系统的 Mutex Lock 实现,Java 的线程是映射到操作系统的原⽣线程之上的。要挂起或者唤醒⼀个线程,要操作系统帮忙完成,⽽操作系统实现线程间的切换时,要从⽤户态转换到内核态,这个状态转换需要时间长,这也是早期 synchronized 效率低的原因。JDK6 后 Java 官⽅从 JVM 层⾯对synchronized 引入优化,如⾃旋锁、适应性⾃旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术减少锁操作的开销。
synchronized 关键字底层原理属于 JVM 层⾯。synchronized 同步语句块的情况

public class SynchronizedDemo {
 public void method(){
 synchronized(this){
 System.out.println("manong qiuzhi xiaozhushou");
 }
 }
 }

通过 JDK ⾃带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:⾸先切换到类的对应⽬录执javacSynchronizedDemo.java 命令⽣成编译后的 .class ⽂件,然后执⾏ javap -c -s -v -l SynchronizedDemo.class

2、使用

synchronized 主要的三种使⽤⽅式:

1、修饰实例⽅法:对当前对象实例加锁,进⼊同步代码前,要获得当前对象实例锁;

2、修饰静态⽅法:也就是给当前类加锁,会作⽤于类的所有对象实例,因为静态成员不属于任何⼀个实例对象,是类成员(static 表明这是该类的⼀个静态资源,不管 new了多少个对象,只有⼀份,所以对该类的所有对象都加了锁)。如果线程 A 调⽤⼀个实例对象的⾮静态 synchronized ⽅法,线程 B 需要调⽤这个实例对象所属类的静态 synchronized ⽅法,是允许的,不会发⽣互斥现象,因为访问静态 synchronized ⽅法占⽤的锁是当前类的锁,⽽访问⾮静态 synchronized⽅法占⽤的锁是当前实例对象锁;

3、修饰代码块:指定加锁对象,对给定对象加锁,进⼊同步代码库前要获得给定对象的锁。和 synchronized ⽅法⼀样,synchronized(this) 代码块也是锁定当前对象的。synchronized 关键字加到 static 静态⽅法和synchronized(class) 代码块上都是是给 Class 类上锁。这⾥再提⼀下:synchronized 关键字加到⾮ static 静态⽅法上是给对象实例上锁。另外需要注意的是:尽量不要使⽤ synchronized(String a) ,因为 JVM 中,字符串常量池具有缓冲功能。

补充:双重校验锁实现单例模式

问到 synchronized 的使⽤,很有可能让你⽤ synchronized 实现个单例模式。这⾥补充下使⽤ synchronized 双᯿

校验锁的⽅法实现单例模式:

public class Singleton {
 private volatile static Singleton uniqueInstance;
 private Singleton() {}
 public static Singleton getUniqueInstance() {
 // 先判断对象是否已经实例过,没有实例化过才进⼊加锁代码
 if (uniqueInstance == null) {
 // 类对象加锁
 synchronized (Singleton.class) {
 if (uniqueInstance == null) {
 uniqueInstance = new Singleton();
 }
 }
 }
 return uniqueInstance;
 }
}

另外,需要注意 uniqueInstance 采⽤ volatile 关键字修饰也是很有必要。采⽤ volatile 关键字修饰也是很有必要

的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执⾏:

\1. 为 uniqueInstance 分配内存空间

\2. 初始化 uniqueInstance

\3. 将 uniqueInstance 指向分配的内存地址

public class Singleton {

private volatile static Singleton uniqueInstance;

private Singleton() {}

public static Singleton getUniqueInstance() {

// 先判断对象是否已经实例过,没有实例化过才进⼊加锁代码

if (uniqueInstance == null) {

// 类对象加锁

synchronized (Singleton.class) {

if (uniqueInstance == null) {

uniqueInstance = new Singleton();

}

}

}

return uniqueInstance;

}

}但是由于 JVM 具有指令᯿排的特性,执⾏顺序有可能变成 1 -> 3 -> 2。指令᯿排在单线程环境下不会出现问题,但

是在多线程环境下会导致⼀个线程获得还没有初始化的实例。例如,线程 T1 执⾏了 1 和 3,此时 T2 调⽤

getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未

被初始化。

使⽤ volatile 可以禁⽌ JVM 的指令᯿排,保证在多线程环境下也能正常运⾏。

3、synchronized优化

JDK1.6 后synchronized 关键字底层做了哪些优化?

JDK1.6 对锁的实现引⼊了⼤量的优化,如偏向锁、轻量级锁、⾃旋锁、适应性⾃旋锁、锁消除、锁粗化等技术来减

少锁操作的开销。

锁主要存在四种状态,依次是:⽆锁状态、偏向锁状态、轻量级锁状态、᯿量级锁状态,它们会随着竞争的激烈⽽

逐渐升级。注意锁可以升级不可降级,这种策略是为了提⾼获得锁和释放锁的效率。

偏向锁

引⼊偏向锁的⽬的和引⼊轻量级锁的⽬的很像,它们都是为了没有多线程竞争的前提下,减少传统的᯿量级锁使⽤

操作系统互斥量产⽣的性能消耗。但是不同是:轻量级锁在⽆竞争的情况下使⽤ CAS 操作去代替使⽤互斥量。⽽

偏向锁在⽆竞争的情况下会把整个同步都消除掉。

偏向锁的“偏”就是偏⼼的偏,它的意思是会偏向于第⼀个获得它的线程,如果在接下来的执⾏中,该锁没有被其他

线程获取,那么持有偏向锁的线程就不需要进⾏同步。

但是对于锁竞争⽐较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此

这种场合下不应该使⽤偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会⽴即膨胀为᯿量级锁,⽽

是先升级为轻量级锁。

轻量级锁

倘若偏向锁失败,虚拟机并不会⽴即升级为᯿量级锁,它还会尝试使⽤⼀种称为轻量级锁的优化⼿段(JDK1.6 之后

加⼊的)。轻量级锁不是为了代替᯿量级锁,它的本意是在没有多线程竞争的前提下,减少传统的᯿量级锁使⽤操作

系统互斥量产⽣的性能消耗,因为使⽤轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都⽤到了

CAS 操作。

轻量级锁能够提升程序同步性能的依据是“对于绝⼤部分锁,在整个同步周期内都是不存在竞争的”,这是⼀个经验

数据。如果没有竞争,轻量级锁使⽤ CAS 操作避免了使⽤互斥操作的开销。但如果存在锁竞争,除了互斥量开销

外,还会额外发⽣ CAS 操作,因此在有锁竞争的情况下,轻量级锁⽐传统的᯿量级锁更慢!如果锁竞争激烈,那

么轻量级将很快膨胀为᯿量级锁!

⾃旋锁和⾃适应⾃旋

轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层⾯挂起,还会进⾏⼀项称为⾃旋锁的优化⼿段。

互斥同步对性能最⼤的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转⼊内核态中完成(⽤户态转

换到内核态会耗费时间)。

⼀般线程持有锁的时间都不是太⻓,所以仅仅为了这⼀点时间去挂起线程/恢复线程是得不偿失的。所以,虚拟机

的开发团队就这样去考虑:“我们能不能让后⾯来的请求获取锁的线程等待⼀会⽽不被挂起呢?看看持有锁的线程

是否很快就会释放锁”。为了让⼀个线程等待,我们只需要让线程执⾏⼀个忙循环(⾃旋),这项技术就叫做⾃旋**百度百科对⾃旋锁的解释:**何谓⾃旋锁?它是为实现保护共享资源⽽提出⼀种锁机制。其实,⾃旋锁与互斥

锁⽐较类似,它们都是为了解决对某项资源的互斥使⽤。⽆论是互斥锁,还是⾃旋锁,在任何时刻,最多只

能有⼀个保持者,也就说,在任何时刻最多只能有⼀个执⾏单元获得锁。但是两者在调度机制上略有不同。

对于互斥锁,如果资源已经被占⽤,资源申请者只能进⼊睡眠状态。但是⾃旋锁不会引起调⽤者睡眠,如果

⾃旋锁已经被别的执⾏单元保持,调⽤者就⼀直循环在那⾥看是否该⾃旋锁的保持者已经释放了锁,"⾃

旋"⼀词就是因此⽽得名

⾃旋锁在 JDK1.6 之前其实就已经引⼊了,不过是默认关闭的,需要通过 --XX:+UseSpinning 参数来开启。JDK1.6

及 1.6 之后,就改为默认开启的了。需要注意的是:⾃旋等待不能完全替代阻塞,因为它还是要占⽤处理器时间。

如果锁被占⽤的时间短,那么效果当然就很好了。反之,⾃旋等待的时间必须要有限度。如果⾃旋超过了限定次数

任然没有获得锁,就应该挂起线程。⾃旋次数的默认值是 10 次,⽤户可以修改 --XX:PreBlockSpin 来更改。

另外,在 JDK1.6 中引⼊了⾃适应的⾃旋锁。⾃适应的⾃旋锁带来的改进就是:⾃旋的时间不在固定了,⽽是和前

⼀次同⼀个锁上的⾃旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了。

锁消除

锁消除理解起来很简单,它指的就是虚拟机即使编译器在运⾏时,如果检测到那些共享数据不可能存在竞争,那么

就执⾏锁消除。锁消除可以节省毫⽆意义的请求锁的时间。

锁粗化

原则上,我们在编写代码的时候,总是推荐将同步块的作⽤范围限制得尽量⼩。只在共享数据的实际作⽤域才进⾏

同步,这样是为了使得需要同步的操作数量尽可能变⼩,如果存在锁竞争,那等待线程也能尽快拿到锁。

⼤部分情况下,上⾯的原则都是没有问题的,但是如果⼀系列的连续操作都对同⼀个对象反复加锁和解锁,那么会

带来很多不必要的性能消耗

17、 synchronized ReenTrantLock

区别

1、synchronized 是的关键字(和 for、while⼀样),ReentrantLock是类,这是⼆者的本质区别。**拓展:**ReentrantLock 是类,提供了⽐ synchronized 更多灵活的特性:等待可中断、可实现公平锁、可实现选择性通知(锁可以绑定多个条件)、性能已经不是选择标准。

2、 synchronized 依赖于 JVM ,ReenTrantLock 依赖于 API。**解释:**JDK1.6 为synchronized 进⾏了很多优化,都在虚拟机层⾯实现的,没暴露给我们。ReenTrantLock 是 JDK 层⾯实现的(也就是 API 层⾯,需要 lock() 和 unlock ⽅法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。

18、synchronized volatile

区别

1、volatile 本质是在告诉 JVM,当前在寄存器(⼯作内存)中的变量值是不确定的,需要从主存中读取;synchronized 是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2、volatile 仅能⽤在变量级别;synchronized 能⽤在变量、⽅法、和类级别。
3、volatile 仅能实现变量的修改可⻅性,不能保证原⼦性;⽽ synchronized 可以保证变量的修改可⻅性和原⼦性。
4、volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
5、volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。

19、volatile

理解:

volatile 关键字保证有序性和可⻅性。我们写的代码,不⼀定按写的顺序来执⾏,编译器会做重排序,CPU 也会做重排序的,减少流⽔线阻塞,提⾼ CPU 执⾏效率。这需要⼀定的顺序和规则来保证,不然程序员⾃⼰写的代码都不知道对不对了,有条就是 volatile 变量规则:对⼀个变量的写先于读操作、有序性通过插⼊内存屏障来保证。被 volatile 修饰的共享变量,就有以下两点特性:

1 . 保证了不同线程对该变量操作的内存可⻅性;
2 . 禁⽌指令重排序。

20、ReentrantReadWriteLock

理解:

1、ReentrantReadWriteLock 允许多个读线程同时访问,但是不允许写线程和读线程、写线程和写线程同时访问。读写锁内部维护了两个锁:⼀个是⽤于读操作的 ReadLock,⼀个是⽤于写操作的 WriteLock。读写锁ReentrantReadWriteLock 可以保证多个线程可以同时读,所以在读操作远⼤于写操作的时候,读写锁就⾮常有⽤了。

2、ReentrantReadWriteLock 基于 AQS 实现,它的⾃定义同步器(继承 AQS)需要在同步状态 state 上维护多个读线程和⼀个写线程,该状态的设计成为实现读写锁的关键。ReentrantReadWriteLock 很好的利⽤了⾼低位。来实现⼀个整型控制两种状态的功能,读写锁将变量切分成了两个部分,⾼ 16 位表示读,低 16 位表示写。

ReentrantReadWriteLock 的特点:

1、写锁可以降级为读锁,但是读锁不能升级为写锁;
2、 不管是 ReadLock 还是 WriteLock 都⽀持 Interrupt,语义与 ReentrantLock ⼀致;
3、WriteLock ⽀持 Condition 并且与 ReentrantLock 语义⼀致,⽽ ReadLock 则不能使⽤ Condition,否则抛出

UnsupportedOperationException 异常;

4、 默认构造⽅法为⾮公平模式 ,开发者也可以通过指定 fair 为 true 设置为公平模式 。

升降级

1、 读锁⾥⾯加写锁,会导致死锁;

\2. 写锁⾥⾯是可以加读锁的,这就是锁的降级。

悲观锁和乐观锁

1、悲观锁和乐观锁的理解

悲观锁:总假设最坏情况,每次拿数据都认为别⼈会改,所以每次在拿数据的时都会上锁,别⼈想拿这个数据就会阻塞,直到它拿到锁(共享资源每次只给⼀个线程,其它线程阻塞,⽤完再把资源给其它线程)。传统的关系型数据库⽤到了很多这种锁机制,⽐如:⾏锁、表锁、读锁、写锁等,都是在做操作之前先上锁。Java 中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想。

乐观锁:总假设最好情况,每次拿数据都认为别⼈不修改,不会上锁,但是更新时会判断此期间别⼈有没有去更新这个数据,可⽤版本号机制和 CAS 算法实现。乐观锁适合多读,可提⾼吞吐量,像数据库提供的类似于 write_condition 机制,都是提供的乐观锁。在 Java 中java.util.concurrent.atomic 包下⾯的原⼦变量类就是⽤了乐观锁 CAS 实现的。

两种锁的使⽤场景

乐观锁:多读场景,冲突少,省去了锁的开销,加⼤了系统的整个吞吐量。

悲观锁:多写情况,冲突多,导致应⽤会不断的进⾏ retry,降低了性能。

2、乐观锁实现⽅式

乐观锁两种实现⽅式:版本号机制或者 CAS 算法实现。

版本号机制

⼀般是在数据表中加上⼀个数据版本号 version 字段,表示数据被修改的次数,当数据被修改时,version 值会加 1。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的version 值为当前数据库中的 version 值相等时才更新,否则᯿试更新操作,直到更新成功。

CAS 算法(⾮阻塞同步)

即 compare and swap(⽐较与交换),是⼀种⽆锁算法。⽆锁编程,不⽤锁情况下实现多线程间的变量同步,没有线程阻塞的情况下实现变量同步。CAS 算法涉及到三个操作数:

1、需要读写的内存值 V
2、进⾏⽐较的值 A
3、拟写⼊的新值 B

当 V 的值等于 A 时,CAS 通过原⼦⽅式⽤新值 B 来更新 V 的值,否则不会执⾏操作(⽐较和替换是⼀个原⼦操作)。是⼀个⾃旋操作,即不断的重试。

3、乐观锁的缺点

1. ABA 问题

如果变量 V 初次是 A 值,赋值时检查仍然是 A ,不能说明它没被其他线程修改过,可能被改过⼜改回 A,CAS 就会误认为它没被改过。

2. 循环时间开销⼤

⾃旋 CAS(不成功就⼀直循环执⾏直到成功),如果⻓时间不成功,会给 CPU 带来⾮常⼤的执⾏开销。如果JVM ⽀持处理器的 pause 指令,效率⼀定会提升。
**pause 指令有两个作⽤:**1、可以延迟流⽔线执⾏指令(de-pipeline),使 CPU 不消耗过多的执⾏资源,延迟的时间取决于具体实现的版本,在⼀些处理器上延迟时间是零。2、可避免在退出循环时因内存顺序冲突(memory order violation)引起 CPU流⽔线被清空(CPU pipeline flush),提⾼ CPU 的执⾏效率。

3. 只能保证⼀个共享变量的原⼦操作

CAS 只对单个共享变量有效,当涉及多个共享变量时 CAS ⽆效。 从 JDK 1.5 开始,提供了AtomicReference 类保证引⽤对象间的原⼦性,可把多个变量放在⼀个对象⾥进⾏ CAS 操作。所以可以⽤锁或者⽤ AtomicReference 类把多个共享变量合成⼀个共享变量来操作。

24、CASsynchronized 的使⽤场景

CAS 多读场景,冲突⼀般较少,synchronized 多写场景,冲突⼀般较多。

1、资源竞争较少(线程冲突较轻)情况,synchronized 同步锁进⾏线程阻塞,唤醒切换以及⽤户态内核态间的切换操作,额外浪费消耗 cpu 资源;⽽ CAS 基于硬件实现,不需要进⼊内核,不需要切换线程,操作⾃旋⼏率较少,可以获得更⾼的性能。

2、资源竞争严重(线程冲突严重)情况,CAS ⾃旋的概率较⼤,浪费更多的 CPU 资源,效率低于synchronized。

25、简单说下对 Java 中的原⼦类的理解

这⾥ Atomic 是指⼀个操作是不可中断的。即使是在多个线程⼀起执⾏的时候,⼀个操作⼀旦开始,就不会被其他

线程⼲扰。所以,所谓原⼦类说简单点就是具有原⼦操作特征的类。

并发包 java.util.concurrent 的原⼦类都存放在 java.util.concurrent.atomic 下。根据操作的数据类型,可以将

JUC 包中的原⼦类分为 4 类:

1. 基本类型

使⽤原⼦的⽅式更新基本类型:

AtomicInteger:整型原⼦类

AtomicLong:⻓整型原⼦类

AtomicBoolean :布尔型原⼦类

2. 数组类型

使⽤原⼦的⽅式更新数组⾥的某个元素:

AtomicIntegerArray:整型数组原⼦类

AtomicLongArray:⻓整型数组原⼦类

AtomicReferenceArray :引⽤类型数组原⼦类

3. 引⽤类型

AtomicReference:引⽤类型原⼦类

AtomicStampedReference:原⼦更新引⽤类型⾥的字段原⼦类

AtomicMarkableReference :原⼦更新带有标记位的引⽤类型

4. 对象的属性修改类型AtomicIntegerFieldUpdater:原⼦更新整型字段的更新器

AtomicLongFieldUpdater:原⼦更新⻓整型字段的更新器

AtomicStampedReference :原⼦更新带有版本号的引⽤类型。该类将整数值与引⽤关联起来,可⽤于解决原⼦

的更新数据和数据的版本号,可以解决使⽤ CAS 进⾏原⼦更新时可能出现的 ABA 问题。

26、atomic 的原理是什么?

Atomic 包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引⽤类型)变量进

⾏操作时,具有排他性,即当多个线程同时对该变量的值进⾏更新时,仅有⼀个线程能成功,⽽未成功的线程可以

向⾃旋锁⼀样,继续尝试,⼀直等到执⾏成功。

Atomic 系列的类中的核⼼⽅法都会调⽤ unsafe 类中的⼏个本地⽅法。我们需要先知道⼀个东⻄就是 Unsafe 类,

全名为:sun.misc.Unsafe,这个类包含了⼤量的对 C 代码的操作,包括很多直接内存分配以及原⼦操作的调⽤,

⽽它之所以标记为⾮安全的,是告诉你这个⾥⾯⼤量的⽅法调⽤都会存在安全隐患,需要⼩⼼使⽤,否则会导致严

᯿的后果,例如在通过 unsafe 分配内存的时候,如果⾃⼰指定某些区域可能会导致⼀些类似 C++ ⼀样的指针越界

到其他进程的问题。

27、说下对同步器 AQS 的理解?

AQS 的全称为:AbstractQueuedSynchronizer,这个类在 java.util.concurrent.locks 包下⾯。AQS 是⼀个⽤来构

建锁和同步器的框架,使⽤ AQS 能简单且⾼效地构造出应⽤⼴泛的⼤量的同步器,⽐如:我们提到的

ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask 等等

皆是基于 AQS 的。当然,我们⾃⼰也能利⽤ AQS ⾮常轻松容易地构造出符合我们⾃⼰需求的同步器。

28、AQS 的原理是什么?

AQS 核⼼思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的⼯作线程,并且将共享资源

设置为锁定状态。如果被请求的共享资源被占⽤,那么就需要⼀套线程阻塞等待以及被唤醒时锁分配的机制,这个

机制 AQS 是⽤ CLH 队列锁实现的,即将暂时获取不到锁的线程加⼊到队列中。

29、AQS 对资源的共享模式有哪些?

\1. Exclusive(独占):只有⼀个线程能执⾏,如:ReentrantLock,⼜可分为公平锁和⾮公平锁:

\2. Share(共享):多个线程可同时执⾏,如:CountDownLatch、Semaphore、CountDownLatch、

CyclicBarrier、ReadWriteLock。

30、AQS 底层使⽤了模板⽅法模式,你能说出⼏个需要重写的⽅法吗?

使⽤者继承 AbstractQueuedSynchronizer 并᯿写指定的⽅法。将 AQS 组合在⾃定义同步组件的实现中,并调⽤

其模板⽅法,⽽这些模板⽅法会调⽤使⽤者᯿写的⽅法。

\1. isHeldExclusively() :该线程是否正在独占资源。只有⽤到 condition 才需要去实现它。

\2. tryAcquire(int) :独占⽅式。尝试获取资源,成功则返回 true,失败则返回 false。

\3. tryRelease(int) :独占⽅式。尝试释放资源,成功则返回 true,失败则返回 false。

\4. tryAcquireShared(int) :共享⽅式。尝试获取资源。负数表示失败;0 表示成功,但没有剩余可⽤资源;正

数表示成功,且有剩余资源。

\5. tryReleaseShared(int) :共享⽅式。尝试释放资源,成功则返回 true,失败则返回 false。

31、说下对信号量 Semaphore 的理解?

synchronized 和 ReentrantLock 都是⼀次只允许⼀个线程访问某个资源,Semaphore (信号量)可以指定多个线程

同时访问某个资源。执⾏ acquire ⽅法阻塞,直到有⼀个许可证可以获得然后拿⾛⼀个许可证;每个 release ⽅法增加⼀个许可证,这可能会释放⼀个阻塞的 acquire ⽅法。然⽽,其实并没有实际的许可证这个对象,Semaphore 只是维持了⼀个可获得许可证的数量。Semaphore 经常⽤于限制获取某种资源的线程数量。当然⼀次也可以⼀次拿取和释放多个许可证,不过⼀般没有必要这样做。除了 acquire⽅法(阻塞)之外,另⼀个⽐较常⽤的与之对应的⽅法是ryAcquire ⽅法,该⽅法如果获取不到许可就⽴即返回 false。

32、CountDownLatch CyclicBarrier

区别:

1、CountDownLatch 是计数器,只能使⽤⼀次,

  CyclicBarrier 的计数器提供 reset 功能,可以多次使⽤。

2、CountDownLatch 来说,重点是“⼀个线程(多个线程)等待”,其他的 N 个线程在完成“某件事情”之后,可终⽌,也可等待。

  CyclicBarrier,重点是多个线程,在任意⼀个线程没有完成,所有的线程都必须等待。

3、CountDownLatch 是计数器,线程完成⼀个记录⼀个,只不过计数不是递增⽽是递减,

  CyclicBarrier 更像⼀个阀⻔,需要所有线程都到达,阀⻔才能打开,然后继续执⾏。

CountDownLatch 应⽤场景:

1、某⼀线程在开始运⾏前等待 n 个线程执⾏完毕:启动⼀个服务时,主线程需要等待多个组件加载完毕,之后再继续执⾏。

// 共享变量,使⽤ volatile 修饰保证线程可⻅性private volatile int state;
2、实现多个线程开始执⾏任务的最⼤并⾏性。注意是并⾏性,不是并发,强调的是多个线程在某⼀时刻同时开始执⾏。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。

3、死锁检测:⾮常⽅便的使⽤场景,可以⽤ n 个线程访问共享资源,在每次测试阶段的线程数⽬是不同的,并尝试产⽣死锁。

CyclicBarrier 应⽤场景:

CyclicBarrier 可以⽤于多线程计算数据,最后合并计算结果的应⽤场景。⽐如:我们⽤⼀个 Excel 保存了⽤户所有银⾏流⽔,每个 Sheet 保存⼀个帐户近⼀年的每笔银⾏流⽔,现在需要统计⽤户的⽇均银⾏流⽔,先⽤多线程处理每个 sheet ⾥的银⾏流⽔,都执⾏之后,得到每个 sheet 的⽇均银⾏流⽔,最后,再⽤ barrierAction ⽤这些线程的计算结果,计算出整个 Excel 的⽇均银⾏流⽔。

33、说下对线程池的理解?为什么要使⽤线程池

线程池提供了⼀种限制和管理资源(包括执⾏⼀个任务)的⽅式。每个线程池还维护⼀些基本统计信息,例如:已

完成任务的数量。

使⽤线程池的好处

1、降低资源消耗:通过᯿复利⽤已创建的线程降低线程创建和销毁造成的消耗;

2、 提⾼响应速度:当任务到达时,任务可以不需要的等到线程创建就能⽴即执⾏;

3、 提⾼线程的可管理性:线程是稀缺资源,如果⽆限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,

使⽤线程池可以进⾏统⼀的分配,调优和监控。

34、线程池的参数

1、 corePoolSize(线程池的基本⼤⼩):当⼀任务到线程池时,当 poolSize < corePoolSize 时,线程池会创建⼀个线程执⾏任务,即使其他空闲的基本线程能够执⾏新任务也会创建线程,等需要执⾏的任务数⼤于线程池基本⼤⼩时就不再创建。**拓展:**如果调⽤了线程池的prestartAllCoreThreads() ⽅法,线程池会提前创建并启动所有基本线程。

2、 maximumPoolSize(线程池最⼤数量):线程池允许创建的最⼤线程数。如果队列满了,已创建的线程数⼩于最⼤线程数,线程池会创建新的线程执⾏任务。**扩展:**如果⽤⽆界的任务队列,这个参数就没什么效果。

3、 keepAliveTime(线程活动保持时间):线程池的⼯作线程空闲后,保持存活的时间。如果任务很多,每个任务执⾏的时间较短,可以调⼤时间,提⾼线程的利⽤率。

4、 TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、⼩时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之⼀毫秒)和纳秒(NANOSECONDS,千分之⼀微秒)。

5、 workQueue(任务队列):⽤于保存等待执⾏任务的阻塞队列。

可以选择以下⼏个阻塞队列:

1、 ArrayBlockingQueue:基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素排序。
2、LinkedBlockingQueue:基于链表结构的阻塞队列,此队列按 FIFO 排序元素,吞吐量要⾼于ArrayBlockingQueue。静态⼯⼚⽅法 Executors.newFixedThreadPool() 使⽤了这个队列。
3、SynchronousQueue:不存储元素的阻塞队列。每个插⼊操作必须等另⼀个线程移除操作,否则插⼊操作⼀直处于阻塞状态,吞吐量要⾼于 LinkedBlockingQueue,静态⼯⼚⽅法Executors.newCachedThreadPool 使⽤了这个队列。
4、 PriorityBlockingQueue:具有优先级的⽆限阻塞队列。
6、 threadFactory:⽤于设置创建线程的⼯⼚,可以通过线程⼯⼚给每个创建出来的线程设置更有意义的名字。
7、RejectExecutionHandler(饱和策略):队列和线程池都满了,说明线程池处于饱和状态,必须采取⼀种策略处理提交的新任务。这个策略默认是 AbortPolicy,表示⽆法处理新任务时抛出异常。

饱和策略:

在 JDK1.5 中 Java 线程池框架提供了以下 4 种策略:

1、 AbortPolicy:直接抛出异常。
2、 CallerRunsPolicy:只⽤调⽤者所在线程来运⾏任务。
3、 DiscardOldestPolicy:丢弃队列⾥最近的⼀个任务,并执⾏当前任务。
4、 DiscardPolicy:不处理,丢弃掉。

也可以根据应⽤场景需实现RejectedExecutionHandler 接⼝⾃定义策略。如记录⽇志或持久化存储不能处理的任务。

35、如何创建线程池

⽅式⼀:通过 ThreadPoolExecutor 的构造⽅法实现:

可创建三种类型的 ThreadPoolExecutor:

1、FixedThreadPool:该⽅法返回⼀个线程数量固定的线程池。当有新任务提交时,线程池中若有空闲线程,则⽴即执⾏。若没有,新任务会存在任务队列中,有线程空闲时,便处理任务队列中的任务。

2、 SingleThreadExecutor:⽅法返回只有⼀个线程的线程池。若多⼀个任务,任务会被存在任务队列中,待线程空闲,按先进先出的顺序执⾏队列中的任务。

3、CachedThreadPool:该⽅法返回可调整线程数量的线程池。线程池的线程数量不确定,若有空闲线程可以复⽤,会优先⽤可复⽤的线程。若所有线程均在⼯作,⼜有新任务,会创建新线程处理任务。所有线程在当前任务执⾏完后,返回线程池进⾏复⽤。

注意

阿⾥巴巴Java开发⼿册》中强制线程池不允许使⽤ Executors 去创建,⽽是通过 ThreadPoolExecutor 的⽅式,这样的处理⽅式让写的同学更加明确线程池的运⾏规则,规避资源耗尽的⻛险。

⽅式⼆:通过Executor 框架的⼯具类 Executors 来实现:

Executors 创建线程池对象的弊端如下:
FixedThreadPool 和 SingleThreadExecutor :允许请求的队列⻓度为 Integer.MAX_VALUE,可能堆积⼤量的请求,导致 OOM。CachedThreadPool和ScheduledThreadPool:允许创建的线程数量为Integer.MAX_VALUE ,可能会创建⼤量线程,导致 OOM。

36、线程池中的线程数

⼀般怎么设置?要考虑哪些问题?

主要考虑下⾯⼏个⽅⾯:

1. 线程执⾏任务的性质:

密集型的任务:⽐较占 cpu,线程数设置的略⼤于等于 cpu 核数;
IO 型任务:主要消耗在 IO 等待上,cpu 压⼒不⼤,线程数⼀般设置较⼤。

2. cpu 使⽤率:

线程数设置较⼤时问题:1、线程的初始化,切换,销毁等操作会 cpu 资源,使cpu 利⽤率维持在较⾼⽔平。2、线程数⼤时,任务会短时间迅速执⾏,会给 cpu 造成较⼤压⼒。3、任务的集中⽀持,会让 cpu 的使⽤率短时间飙⾼,然后迅速下降,cpu 使⽤不合理,应减⼩线程数,让任务在队列等待,使 cpu 使⽤稳定在合理数值范围。所以 cpu够⽤时,不宜过⼤,不是越⼤越好。可通过机器的 cpu 使⽤率和cpu 负载来判断线程数是否合理。

3. 内存使⽤率:

线程数和队列⼤⼩会影响此数据,队列⼤⼩应通过计算线程池任务数,合理设置队列⼤⼩,不宜过⼩,让其不溢出,因为溢出会⾛拒绝策略,影响性能,增加复杂度。

4. 下游系统抗并发能⼒:

多线程给下游系统造成的并发等于设置的线程数,例如:多线程访问数据库,要考虑数据库连接池⼤⼩设置,数据库并发太多影响其 QPS,会把数据库打挂等问题。如果访问的是下游系统的接⼝,得考虑下游系统是否能抗的住这么多并发量,不能把下游系统打挂了。

37、execute()和submit()

区别:

1、 execute() ⽅法提交不需要返回值的任务,⽆法判断任务被线程池执⾏成功与否;

2、 submit() ⽅法提交要返回值的任务。线程池会返回⼀个 Future 类型的对象,通过 Future 对象可判断任务是否执⾏成功,可以通过 Future 的 get() ⽅法来获取返回值,get() ⽅法会阻塞当前线程直到任务完成,⽽⽤get(long timeout,TimeUnit unit) ⽅法会阻塞当前线程⼀段时间后⽴即返回,有可能任务没有执⾏完。

38、Fork/Join并⾏计算框架

理解:

Fork/Join 并⾏计算框架主要解决的是分治任务。分治:⼤任务拆成⼩的⼦任务的结果聚合起来得到最终结果。

Fork/Join 并⾏计算框架的核⼼组件是 ForkJoinPool。ForkJoinPool ⽀持任务窃取机制,让所有的线程的⼯作量均衡,不会有的线程忙,有的线程闲,性能很好。

ForkJoinPool 中的任务队列采⽤双端队列,⼯作线程正常获取任务和“窃取任务”分别是从任务队列不同的端消费,能避免不必要的数据竞争

39、JDK 并发容器

JDK 提供的容器在 java.util.concurrent 包中。

1、ConcurrentHashMap:线程安全的 HashMap;
2、CopyOnWriteArrayList:线程安全的 List,在读多写少场合性能好,好于 Vector;
3、ConcurrentLinkedQueue:⾼效的并发队列,⽤链表实现。⼀个线程安全的 LinkedList,是⼀个⾮阻塞队列;
4、BlockingQueue:是⼀个接⼝,JDK 内部通过链表、数组实现了这个接⼝。表示阻塞队列,适合作为数据共享的通道;
5、ConcurrentSkipListMap:跳表的实现。是⼀个 Map,⽤跳表的数据结构快速查找。

40、CopyOnWriteArrayList

理解:

1、很多场景,读操作远远⼤于写。由于读不会修改原数据,因此每次读加锁是⼀种资源浪费。应该允许多线程同时访问 List 的内部数据,毕竟读取操作是安全的。

2、CopyOnWriteArrayList 类的所有可变操作(add,set等)都是通过创建底层数组的新副本来实现的。当 List 要被修改时,不需要修改原内容,⽽是对原有数据进⾏⼀次复制,将修改的内容写⼊副本。写完后,将改完的副本替换原来的,可保证写不影响读操作了。

3、 CopyOnWriteArrayList 是满⾜ CopyOnWrite 的 ArrayList,CopyOnWrite 就是说:想对内存修改时,不在原内存中写,⽽是将内存拷⻉⼀份,新内存中写操,写后,将指向原来内存指针指向新内存,原内存就可被回收掉了。

4、CopyOnWriteArrayList 读没有同步控制和锁操作,内部数组 array 不会发⽣修改,只会被另外⼀个 array 替换,可保证数据安全。

5、CopyOnWriteArrayList 写⼊操作 add() ⽅法在添加集合的时加了锁,保证了同步,避免多线程写的时候会copy 出多个副本出来。

41、阻塞队列(BlockingQueue)

理解:

阻塞队列(BlockingQueue)⽤在“⽣产者-消费者”问题中,其原因是 BlockingQueue 提供了可阻塞的插⼊和移除的⽅法。1、当队列容器满,⽣产者线程会被阻塞,直到队列未满;2、当队列容器为空,消费者线程会被阻塞,直⾄队列⾮空。

有哪些实现类:

BlockingQueue 是⼀个接⼝,继承⾃ Queue,其实现类也可作为 Queue 的实现来⽤,Queue ⼜继承Collection 接⼝。下⾯是 BlockingQueue 的相关实现类:
img

42、 ConcurrentSkipListMap

ConcurrentSkipListMap 的理解

对于单链表,即使链表有序,想查找某个数据,只能从头到尾遍历链表,效率很低,跳表就不⼀样了,跳表是可以快速查找的数据结构,似于平衡树。可对元素快速的查找。

重要区别:

平衡树的插⼊和删除可能导致平衡树全局的调整。对跳表的插⼊和删除,只需对整个数据结构的局部操作。好处是:⾼并发情况下,你需要⼀个全局锁来保证整个平衡树的线程安全。

跳表,你只需要部分锁即可。在⾼并发环境下,有更好的性能。就查询的性能⽽⾔,跳表的时间复杂度也是 O(logn) 。跳表同时维护了多个链表,并且链表是分层的。

Spring

1、Spring框架好处

1、轻量:Spring 是轻量的,基本的版本⼤约 2MB。
2、控制反转(IOC):Spring 通过控制反转实现了松散耦合,对象给出它们的依赖,⽽不是创建或查找依赖的对象。
3、⾯向切⾯的编程(AOP):Spring ⽀持⾯向切⾯的编程,把应⽤业务逻辑和系统服务分开。
4、容器:Spring 包含管理着应⽤中对象的⽣命周期和配置。
5、MVC框架:Spring 的 Web 框架是个精⼼设计的框架,是 Web 框架的⼀个很好的替代品。
6、事务管理:Spring 提供⼀个持续的事务管理接⼝,可以扩展到上⾄本地事务下⾄全局事务(JTA)。
7、异常处理:Spring 提供⽅便的 API 把具体技术相关的异常(⽐如由 JDBC,Hibernate or JDO 抛出的)转化为⼀致的 unchecked 异常

2、AOP

1、AOP概念

AOP(⾯向⽅⾯编程),可以说是 OOP(⾯向对象编程)的补充和完善。OOP 引⼊封装、继承和多态性等概念来建⽴⼀种对象层次结构,⽤以模拟公共⾏为的⼀个集合。当我们需要为分散的对象引⼊公共⾏为的时候,OOP 则显得⽆能为⼒。也就是说,OOP 允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如⽇志功能。⽇志代码往往⽔平地散布在所有对象层次中,⽽与它所散布到的对象的核⼼功能毫⽆关系。对于其他类型的代码,如:安全性、异常处理和透明的持续性也是如此。这种散布在各处的⽆关的代码被称为横切代码,在 OOP 设计中,它导致了⼤量代码的重复,⽽不利于各个模块的重⽤。

⽽ AOP 技术则恰恰相反,它利⽤⼀种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共⾏为封装到⼀个可重⽤模块,并将其名为“Aspect”,即⽅⾯。所谓“⽅⾯”,简单地说,就是将那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP 代表的是⼀个横向的关系,如果说“对象”是⼀个空⼼的圆柱体,其中封装的是对象的属性和⾏为;那么⾯向⽅⾯编程的⽅法,就仿佛⼀把利刃,将这些空⼼圆柱体剖开,以获得其内部的消息。⽽剖开的切⾯,也就是所谓的“⽅⾯”了。然后它⼜以巧夺天功的妙⼿将这些剖开的切⾯复原,不留痕迹。

使⽤“横切”技术,AOP 把软件系统分为两个部分:核⼼关注点和横切关注点。业务处理的主要流程是核⼼关注点,与之关系不⼤的部分是横切关注点。横切关注点的⼀个特点是,它们经常发⽣在核⼼关注点的多处,⽽各处都基本相似。⽐如:权限认证、⽇志、事务处理。AOP 的作⽤在于分离系统中的各种关注点,将核⼼关注点和横切关注点分离开来。

2、各种基本概念

1、切⾯(Aspect):官⽅的抽象定义为“⼀个关注点的模块化,这个关注点可能会横切多个对象”。
2、连接点(Joinpoint):程序执⾏过程中的某⼀⾏为。
3、通知(Advice):“切⾯”对于某个“连接点”所产⽣的动作。
4、切⼊点(Pointcut):匹配连接点的断⾔,在 AOP 中通知和⼀个切⼊点表达式关联。
5、⽬标对象(Target Object):被⼀个或者多个切⾯所通知的对象。
6、AOP 代理(AOP Proxy):在 Spring AOP 中有两种代理⽅式,JDK 动态代理和 CGLIB 代理。

3、AOP代理⽅式

AOP的代理有哪⼏种⽅式

AOP ⾯向切⾯的编程思想是基于代理模式 ,在 Java 中⼀般⽤ JDK 动态代理模式,JDK 动态代理模式只能代理接⼝不能代理类。

Spring AOP 会按照下⾯两种情况进⾏切换,因为 Spring AOP 同时⽀持CGLIB、ASPECTJ、JDK 动态代理。

1、如果⽬标对象的实现类实现了接⼝,Spring AOP 将会采⽤ JDK 动态代理来⽣成 AOP 代理类;
2、如果⽬标对象的实现类没有实现接⼝,Spring AOP 将会采⽤ CGLIB 来⽣成 AOP 代理类。

4、JDK 动态代理

如何实现 JDK 动态代理?

JDK 动态代理最核⼼的接⼝和⽅法:

1. java.lang.reflect 包中的 InvocationHandler 接⼝:

public interface InvocationHandler {
 public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable; }

被代理类的操作都会由该接⼝中的 invoke ⽅法实现,参数的含义分别是:

1、 proxy:被代理的类的实例;
2、 method:调⽤被代理的类的⽅法;
3、args:该⽅法需要的参数。

使⽤⽅法⾸先要实现该接⼝,可以在 invoke ⽅法中调⽤被代理类的⽅法并获得返回值,也可以在调⽤该⽅法的前后去做⼀些额外的事情,从⽽实现动态代理。

2. java.lang.reflect 包中的 Proxy 类中的 newProxyInstance ⽅法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException

参数含义:

1、loader:被代理的类的类加载器;
2、interfaces:被代理类的接⼝数组;
3、invocationHandler:调⽤处理器类的对象实例

该⽅法会返回⼀个被修改过类的实例,从⽽可以⾃由的调⽤该实例的⽅法

7、通知类型(Advice)型

1、前置通知(Before advice):在某连接点(JoinPoint)之前执⾏的通知,但这个通知不能阻⽌连接点前的执⾏。ApplicationContext 中在 aop:aspect ⾥⾯使⽤ aop:before 元素进⾏声明;

2、后置通知(After advice):当某连接点退出的时候执⾏的通知(不论是正常返回还是异常退出)。ApplicationContext 中在 aop:aspect ⾥⾯使⽤ aop:after 元素进⾏声明。

3、返回后通知(After return advice :在某连接点正常完成后执⾏的通知,不包括抛出异常的情况。ApplicationContext 中在 aop:aspect ⾥⾯使⽤ <> 元素进⾏声明。

4、环绕通知(Around advice):包围⼀个连接点的通知,类似 Web 中 Servlet规范中的 Filter 的 doFilter ⽅法。可以在⽅法的调⽤前后完成⾃定义的⾏为,也可以选择不执⾏。ApplicationContext 中在 aop:aspect ⾥⾯使⽤ aop:around 元素进⾏声明。

5、抛出异常后通知(After throwing advice):在⽅法抛出异常退出时执⾏的通知。ApplicationContext 中在 aop:aspect ⾥⾯使⽤ aop:after-throwing 元素进⾏声明。

8、 IOC

IOC 是 Inversion of Control 的缩写,多数书籍翻译成“控制反转”。简单来说就是把复杂系统分解成相互合作的对

象,这些对象类通过封装以后,内部实现对外部是透明的,从⽽降低了解决问题的复杂度,⽽且可以灵活地被᯿⽤

和扩展。IOC 理论提出的观点⼤体是这样的:借助于“第三⽅”实现具有依赖关系的对象之间的解耦。如下图:

由于引进了中间位置的“第三⽅”,也就是 IOC 容器,使得 A、B、C、D 这 4 个对象没有了耦合关系,⻮轮之间的传

动全部依靠“第三⽅”了,全部对象的控制权全部上缴给“第三⽅”IOC 容器,所以,IOC 容器成了整个系统的关键核

⼼,它起到了⼀种类似“粘合剂”的作⽤,把系统中的所有对象粘合在⼀起发ഀ作⽤,如果没有这个“粘合剂”,对象

与对象之间会彼此失去联系,这就是有⼈把 IOC 容器⽐喻成“粘合剂”的由来。

把上图中间的 IOC 容器拿掉,然后再来看看这套系统:

现在看到的画⾯,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D 这 4 个对象之间已经没

有了耦合关系,彼此毫⽆联系,这样的话,当你在实现 A 的时候,根本⽆须再去考虑 B、C 和 D了,对象之间的依

赖关系已经降低到了最低程度。所以,如果真能实现 IOC 容器,对于系统开发⽽⾔,这将是⼀件多么美好的事情,

参与开发的每⼀成员只要实现⾃⼰的类就可以了,跟别⼈没有任何关系!

我们再来看看,控制反转(IOC)到底为什么要起这么个名字?我们来对⽐⼀下:

软件系统在没有引⼊ IOC 容器之前,对象 A 依赖于对象 B,那么对象 A 在初始化或者运⾏到某⼀点的时候,⾃⼰

必须主动去创建对象 B 或者使⽤已经创建的对象 B。⽆论是创建还是使⽤对象 B,控制权都在⾃⼰⼿上。

软件系统在引⼊ IOC 容器之后,这种情形就完全改变了,由于 IOC 容器的加⼊,对象 A 与对象 B 之间失去了直接

联系,所以,当对象 A 运⾏到需要对象 B 的时候,IOC 容器会主动创建⼀个对象 B 注⼊到对象 A 需要的地⽅。

通过前后的对⽐,我们不难看出来:对象 A 获得依赖对象 B 的过程,由主动⾏为变为了被动⾏为,控制权颠倒过

来了,这就是“控制反转”这个名称的由来。

9、Bean

1、⽣命周期

在传统的 Java 应⽤中,bean 的⽣命周期很简单,使⽤ Java 关键字 new 进⾏ Bean 的实例化,然后 Bean 就能够使⽤了。⼀旦 Bean 不再被使⽤,则由 Java ⾃动进⾏垃圾回收。

Spring 管理 Bean 的⽣命周期复杂,因为Spring 对 Bean的管理可扩展性⾮常强,下⾯展示⼀个 Bean 的构造过程:

1、Spring 启动,查找并加载需要被 Spring 管理的 Bean,进⾏ Bean 的实例化;
2、Bean 实例化后,对 Bean 的引⼊和值注⼊到 Bean 的属性中;
3、如果 Bean 实现了 BeanNameAware 接⼝的话,Spring 将 Bean 的 Id 传递给 setBeanName() ⽅法;
4、如果 Bean 实现了 BeanFactoryAware 接⼝的话,Spring 将调⽤ setBeanFactory() ⽅法,将 BeanFactory容器实例传⼊;
5、如果 Bean 实现了 ApplicationContextAware 接⼝的话,Spring 将调⽤ Bean 的 setApplicationContext() ⽅法,将 Bean 所在应⽤上下⽂引⽤传⼊进来;
6、如果 Bean 实现了 BeanPostProcessor 接⼝,Spring 就将调⽤它们的 postProcessBeforeInitialization() ⽅法;
7、如果 Bean 实现了 InitializingBean 接⼝,Spring 将调⽤它们的 afterPropertiesSet() ⽅法。类似地,如果Bean 使⽤ init-method 声明了初始化⽅法,该⽅法也会被调⽤;
8、如果 Bean 实现了 BeanPostProcessor 接⼝,Spring 就将调⽤它们的 postProcessAfterInitialization() ⽅法;
9、此时,Bean 已经准备就绪,可以被应⽤程序使⽤了。它们将⼀直驻留在应⽤上下⽂中,直到应⽤上下⽂被销毁;
10、 如果 Bean 实现了 DisposableBean 接⼝,Spring 将调⽤它的 destory() 接⼝⽅法,同样,如果 Bean 使⽤了destory-method 声明销毁⽅法,该⽅法也会被调⽤。

2、Bean 作⽤域

1、singleton : 唯⼀ bean 实例,Spring 中的 bean 默认都是单例的;
2、prototype : 每次请求都会创建⼀个新的 bean 实例;
3、request:每⼀次 HTTP 请求都会产⽣⼀个新的 bean,该 bean 仅在当前 HTTP request 内有效;
4、session : 每⼀次 HTTP 请求都会产⽣⼀个新的 bean,该 bean 仅在当前 HTTP session 内有效;
5、global-session:全局 session 作⽤域,仅仅在基于 portlet 的 web 应⽤中才有意义,Spring5 已经没有了。

Portlet 是能够⽣成语义代码(例如:HTML)⽚段的⼩型 Java Web 插件。它们基于 portlet 容器,可以像servlet ⼀样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。

3、单例 Bean 的线程安全问题

单例 bean 存在线程问题,

**主要是因为:**当多个线程操作同⼀个对象的时候,对这个对象的⾮静态成员变量的写操作会存在线程安全问题。

两种常见解决办法:

1、 在 Bean 对象中尽量避免定义可变的成员变量(不太现实)。
2、在类中定义⼀个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的⼀种⽅式)。

12、Spring 事物

1、spring事务

事务是要么都执⾏,要么都不执⾏。

事务特性

原⼦性:事务是最⼩的执⾏单位,不允许分割。事务的原⼦性确保动作要么全部完成,要么完全不起作⽤;
⼀致性:执⾏事务前后,数据保持⼀致;
隔离性:并发访问数据库时,⼀个⽤户的事物不被其他事物所⼲扰,各并发事务之间数据库是独⽴的;
持久性: ⼀个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发⽣故障也不应该对其有任何影响。

Spring 事务管理接⼝

1、PlatformTransactionManager:(平台)事务管理器;
2、 TransactionDefinition:事务定义信息(事务隔离级别、传播⾏为、超时、只读、回滚规则);
3、TransactionStatus:事务运⾏状态;

所谓事务管理,就是:“按照给定的事务规则来执⾏提交或者回滚操作”。

2、事务隔离级别

1、TransactionDefinition 接⼝中定义了五个表示隔离级别的常量:
2、TransactionDefinition.ISOLATION_DEFAULT:使⽤后端数据库默认的隔离级别,MySQL 默认采⽤的
3、REPEATABLE_READ 隔离级别 Oracle 默认采⽤的 READ_COMMITTED 隔离级别;
4、TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读;
5、TransactionDefinition.ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻⽌脏读,但是幻读或不可᯿复读仍有可能发⽣;
6、TransactionDefinition.ISOLATION_REPEATABLE_READ:对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改,可以阻⽌脏读和不可᯿复读,但幻读仍有可能发⽣;
7、TransactionDefinition.ISOLATION_SERIALIZABLE:最⾼的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏读、不可᯿复读以及幻读。但是这将严᯿影响程序的性能。通常情况下也不会⽤到该级别。

3、事物传播⾏为

事务传播⾏为是为了解决业务层⽅法之间互相调⽤的事务问题。当事务⽅法被另⼀个事务⽅法调⽤时,必须指定事务应该如何传播。例如:⽅法可能继续在现有事务中运⾏,也可能开启⼀个新事务,并在⾃⼰的事务中运⾏。在TransactionDefinition 定义中包括了如下⼏个表示传播⾏为的常量:

⽀持当前事务的情况:

1、TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务;
2、TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏;
3、TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。

不⽀持当前事务的情况:

1、TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建⼀个新的事务,如果当前存在事务,则把当前事务挂起;
2、TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
3、TransactionDefinition.PROPAGATION_NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。

其他情况:

TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。

15、注⼊⽅式

Spring 常⽤注⼊⽅式:

1、构造器依赖注⼊:构造器依赖注⼊通过容器触发⼀个类的构造器来实现的,该类有⼀系列参数,每个参数代表⼀个对其他类的依赖。2、Setter ⽅法注⼊:Setter ⽅法注⼊是容器通过调⽤⽆参构造器或⽆参 static ⼯⼚⽅法实例化 bean 之后,调⽤该 bean 的 Setter ⽅法,即实现了基于 Setter 的依赖注⼊。
3、基于注解的注⼊:最好的解决⽅案是⽤构造器参数实现强制依赖,Setter ⽅法实现可选依赖。

16、Spring 设计模式

Spring 框架中⽤到的设计模式:

1、⼯⼚设计模式 : Spring 使⽤⼯⼚模式通过 BeanFactory、ApplicationContext 创建 bean 对象;
2、代理设计模式 : Spring AOP 功能的实现;
3、 单例设计模式 : Spring 中的 Bean 默认都是单例的;
4、 模板⽅法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使⽤到了模板模式;
5、 包装器设计模式 : 我们的项⽬需要连接多个数据库,⽽且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源;
6、 观察者模式:Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤;
7、 适配器模式:Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、SpringMVC 中也是⽤到了适配器模式、适配 Controller。

17、ApplicationContext 通常的实现有哪些

ApplicationContext 通常的实现有哪些

1、FileSystemXmlApplicationContext:此容器从⼀个 XML ⽂件中加载beans 的定义,XML Bean 配置⽂件的全路径名必须提供给它的构造函数。
2、 ClassPathXmlApplicationContext:此容器也从⼀个 XML ⽂件中加载beans 的定义,这⾥,你需要正确设置classpath 因为这个容器将在 classpath ⾥找 bean 配置。
3、 WebXmlApplicationContext:此容器加载⼀个 XML ⽂件,此⽂件定义了⼀个 Web 应⽤的所有 bean。

SpringMVC

1、 MVC 模式

MVC 模式的理解

MVC 是 Model — View — Controler ,是⼀种架构模式,分离了表现与交互。它被分为三个核⼼部件:模型、视图、控制器。

Model(模型):是程序的主体部分,主要包含业务数据和业务逻辑。在模型层,还会涉及到⽤户发布的服务,在服务中会根据不同的业务需求,更新业务模型中的数据。

View(视图):是程序呈现给⽤户的部分,是⽤户和程序交互的接⼝,⽤户会根据具体的业务需求,在 View 视图层输⼊⾃⼰特定的业务数据,并通过界⾯的事件交互,将对应的输⼊参数提交给后台控制器进⾏处理。

Controller(控制器):Controller 是⽤来处理⽤户输⼊数据,以及更新业务模型的部分。控制器中接收了⽤户与界⾯交互时传递过来的数据,并根据数据业务逻辑来执⾏服务的调⽤,更新业务模型的数据和状态。

2、⼯作原理/执⾏流程?

简单来说:客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射HandlerMapping 解析请求对应的 Handler -> HandlerAdapter 会根据 Handler 来调⽤真正的处理器来处理请求,并处理相应的业务逻辑 -> 处理器返回⼀个模型视图 ModelAndView -> 视图解析器进⾏解析 -> 返回⼀个视图对象 -> 前端控制器 DispatcherServlet 渲染数据(Model)-> 将得到视图对象返回给⽤户。

image-20220325205428389

上图⽤于辅助理解,⾯试时可⽤下列 8 步描述 SpringMVC 运⾏流程:

1、 ⽤户向服务器发送请求,请求被 Spring 前端控制Servelt DispatcherServlet 捕获;

2、 DispatcherServlet 对请求 URL 进⾏解析,得到请求资源标识符(URI)。然后根据该 URI,调⽤HandlerMapping 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以 HandlerExecutionChain 对象的形式返回;

3、 DispatcherServlet 根据获得的 Handler,选择⼀个合适的HandlerAdapter;(附注:如果成功获得HandlerAdapter 后,此时将开始执⾏拦截器的 preHandler(…)⽅法)

4、提取 Request 中的模型数据,填充 Handler ⼊参,开始执⾏Handler(Controller)。在填充 Handler 的⼊参过程中,根据你的配置,Spring 将做⼀些额外的⼯作:

(1)HttpMessageConveter:将请求消息(如:Json、xml 等数据)转换成⼀个对象,将对象转换为指定的响应信息;

(2)数据转换:对请求消息进⾏数据转换。如:String 转换成 Integer、Double 等;

(3)数据格式化:对请求消息进⾏数据格式化。如:将字符串转换成格式化数字或格式化⽇期等;

(4)数据验证:验证数据的有效性(⻓度、格式等),验证结果存储到 BindingResult 或 Error 中;

5、 Handler 执⾏完成后,向 DispatcherServlet 返回⼀个 ModelAndView 对象;

6、根据返回的 ModelAndView,选择⼀个适合的 ViewResolver(必须是已经注册到 Spring 容器中的ViewResolver)返回给DispatcherServlet;

7、 ViewResolver 结合 Model 和 View,来渲染视图;

8、 将渲染结果返回给客户端。

3、核⼼组件

1. 前端控制器 DispatcherServlet

作⽤:1、Spring MVC 的⼊⼝函数。接收请求,响应结果,相当于转发器,中央处理器。

        2、有了 DispatcherServlet 减少了其它组件之间的耦合度。⽤户请求到达前端控制器,它就相当于 MVC 模式中的 C,        
              DispatcherServlet 是整个流程控制的中⼼,由它调⽤其它组件处理⽤户的请求,DispatcherServlet 降低了组件间的耦合性。

2. 处理器映射器 HandlerMapping

作⽤:根据请求的 url 查找 Handler。HandlerMapping 负责根据⽤户请求找到 Handler 即处理器(Controller),SpringMVC 提供了不同的映射器,实现不同的映射⽅式,例如:配置⽂件⽅式,实现接⼝⽅式,注解⽅式等。

3. 处理器适配器 HandlerAdapter

作⽤:按照特定规则(HandlerAdapter 要求的规则)去执⾏ Handler。通过 HandlerAdapter 对处理器进⾏执⾏,这是适配器模式的应⽤,通过扩展适配器可以对更多类型的处理器进⾏执⾏。

4. 处理器 Handler

注意:写 Handler 时按照 HandlerAdapter 的要求去做,这样适配器才可以去正确执⾏ Handler。Handler 是继DispatcherServlet 前端控制器的后端控制器,在 DispatcherServlet 的控制下 Handler 对具体的⽤户请求进⾏处理。由于 Handler 涉及到具体的⽤户业务请求,所以⼀般情况需要⼯程师根据业务需求开发 Handler。

5. 视图解析器 View resolver

作⽤:进⾏视图解析,根据逻辑视图名解析成真正的视图(View )。View Resolver 负责将处理结果⽣成 View 视图,View Resolver ⾸先根据逻辑视图名解析成物理视图名即具体的⻚⾯地址,再⽣成 View 视图对象,最后对View 进⾏渲染将处理结果通过⻚⾯展示给⽤户。SpringMVC 框架提供了很多的 View 视图类型,包括:jstlView、freemarkerView、pdfView 等。⼀般情况需要通过⻚⾯标签或⻚⾯模版技术将模型数据通过⻚⾯展示给⽤户,需要由⼯程师根据业务需求开发具体的⻚⾯。

6. 视图 View

View 是⼀个接⼝,实现类⽀持不同的 View 类型(jsp、freemarker…)。

注意:处理器 Handler(也就是我们平常说的 Controller 控制器)以及视图层 View 都是需要⾃⼰⼿动开发的。其他的⼀些组件⽐如:前端控制器 DispatcherServlet、处理器映射器 HandlerMapping、处理器适配器 HandlerAdapter 等等都是框架提供给我们的,不需要⾃⼰⼿动开发。

4、常⽤的注解

1、@RequestMapping:⽤于处理请求 url 映射的注解,可⽤于类或⽅法上。⽤于类上,表示类中的所有响应请求的⽅法,都是以该地址作为⽗路径;

2、@RequestBody:实现接收 HTTP 请求的 json 数据,将 json 转换为 Java 对象;

3、@ResponseBody:实现将 Controller ⽅法,返回对象转化为 json 对象响应给客户。

5、@RequestMapping的作⽤

RequestMapping 是⽤来处理请求地址映射的注解,可⽤于类或⽅法上。⽤于类上,表示类中的所有响应请求的⽅法,都是以该地址作为⽗路径。RequestMapping 注解有六个属性,分成三类。

value、method:

1、value:指定请求的实际地址,指定的地址可以是 URI Template 模式;
2、 method:指定请求的method类型, GET、POST、PUT、DELETE 等;

consumes、produces:

1、consumes:指定处理请求的提交内容类型(Content-Type),例如 application/json、text/html;
2、 produces:指定返回的内容类型,仅当 request 请求头中的(Accept)类型中包含该指定类型才返回;

params、header:

1、 params:指定 request 中必须包含某些参数值是,才让该⽅法处理。
2、 headers:指定 request 中必须包含某些指定的 header 值,才能让该⽅法处理请求。

6、 POST/GET 请求中⽂乱码问题

解决 POST 请求中⽂乱码问题:

1、解决 POST 请求乱码问题:在 web.xml 中配置⼀个 CharacterEncodingFilter 过滤器,设置成 utf-8;

解决 GET 请求中⽂乱码问题:

2、GET 请求中⽂参数出现乱码解决⽅法有两个:

(1)修改 tomcat 配置⽂件添加编码与⼯程编码⼀致,如下:

<ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1"redirectPort

(2)对参数进⾏᯿新编码:

String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")

7、控制器是否单例模式

**是否单例模式:**是单例模式,

**会有什么问题:**所以在多线程访问的时候有线程安全问题。但是不要使⽤同步,会影响性能

**怎么解决:**解决⽅案是在控制器⾥⾯不能写字段。

8、重定向和转发的

SpringMVC 怎么样设定重定向和转发:

1、转发:在返回值前⾯加 “forward:”,譬如:

"forward:user.do?name=method2"

2、重定向:在返回值前⾯加 “redirect:”,譬如:

2. ᯿定向:在返回值前⾯加 "redirect:",譬如:

9、拦截器怎么写

⽅法⼀:实现 HandlerInterceptor 接⼝;

⽅法⼆:继承适配器类,接着在接⼝⽅法当中,实现处理逻辑,然后在 SpringMVC 的配置⽂件中配置拦截器。

10、SpringMVC Struts2

区别有哪些

1、SpringMVC :⼊⼝是⼀个 Servlet 即前端控制器(DispatchServlet);

  Struts2 :⼊⼝是⼀个 filter 过虑器(StrutsPrepareAndExecuteFilter);

2、SpringMVC 是基于⽅法开发(⼀个 url 对应⼀个⽅法),请求参数传递到⽅法的形参,可以设计为单例或多例(建议单例),

  Struts2         是基于类开发,传递参数是通过类的属性,只能设计为多例;

3、 Struts2 采⽤值栈存储请求和响应的数据,通过 OGNL 存取数据;

   SpringMVC 通过参数解析器是将 request请求内容解析,并给⽅法形参赋值,将数据和视图封装成 ModelAndView 对象,最后⼜将 ModelAndView 中的模型数据通过 request 域传输到⻚⾯。jsp 视图解析器默认使⽤ jstl。

MyBatis

1、理解

1、Mybatis是⼀个半ORM(对象关系映射)框架,内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要去处理加载驱动、创建连接、创建 Statement 等过程。程序员直接编写 SQL可以。 严格控制 SQL 执⾏性能,灵活度⾼。
2、MyBatis可⽤ XML 或注解配置和映射原⽣信息,将 POJO 映射成数据库中的记录,避免了JDBC 代码和⼿动设置参数以及获取结果集。
3、通过 XML ⽂件或注解的⽅式将要执⾏的各种 Statement 配置起来,通过 Java 对象和 Statement 中 SQL 的动态参数进⾏映射⽣成最终执⾏的 SQL 语句,最后由 MyBatis 框架执⾏ SQL并将结果映射为 Java 对象并返回。(从执⾏ SQL到返回 Result 的过程)。

2、优缺点

优点:

1、SQL 写在 XML ⾥,把 SQL 与程序代码的解耦,便于统⼀管理;不会对应⽤程序或者数据库造成任何影响,提供XML标签,⽀持编写动态 SQL 语句,可重⽤;

2、和 JDBC 相⽐,减少了代码量,减少了 JDBC ⼤量的代码,不用⼿动开关连接;

3、可以与各种数据库兼容(因为 MyBatis 使⽤ JDBC 来连接数据库,所以只要 JDBC ⽀持的数据库 MyBatis都⽀持);

4、提供映射标签,⽀持对象与数据库的 ORM (对象关系映射)字段关系映射;提供对象关系映射标签,⽀持对象关系组件维护。

缺点:

1、SQL 语句编写⼯作量较⼤,尤其当字段多、关联表多时,对开发⼈员编写 SQL 语句的功底有⼀定要求;

2、SQL 语句依赖于数据库,数据库移植性差,不能随意更换数据库。

3、MyBatis Hibernate

有哪些不同

1、MyBatis 和 Hibernate不同,它不完全是⼀个 ORM (对象关系映射)框架,因为 MyBatis 需要程序员⾃⼰写 SQL 语句;Hibernate 对象/关系映射能⼒强,数据库⽆关性好,对于关系模型要求⾼,如果⽤ Hibernate 开发可以节省很多代码,提⾼效率;

2、MyBatis 直接编写SQL,可严格控制 SQL 执⾏性能,灵活度⾼,适合对关系数据模型要求不⾼的软件开发,因为这类软件需求变化频繁,⼀但需求变化要求迅速输出成果。但是 MyBatis ⽆法做到数据库⽆关性,如果要⽀持多种数据库的软件,需要⾃定义多套 SQL 映射⽂件,⼯作量⼤。

4、#{} ${}

{}是预编译处理,${} 是字符串替换

1、Mybatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 号,调⽤ PreparedStatement 的 set ⽅法来赋值;可以防⽌ SQL 注⼊,提⾼系统安全性;

2、MyBatis 在处理 $ {} 替换成变量的值。

5、MyBatis如何分⻚?分⻚插件原理?

MyBatis如何分⻚:1、内存分⻚:可以⽤ RowBounds 对象进⾏分⻚,它是针对 ResultSet 结果集执⾏的内存分⻚。

​ 2、物理分⻚:可以在SQL 内直接书写带有物理分⻚的参数来完成物理分⻚功能,也可⽤分⻚插件来完成物理分⻚。

分⻚插件的基本原理:⽤ MyBatis 提供的插件接⼝,实现⾃定义插件,在插件的拦截⽅法内拦截待执⾏的SQL,然后重写 SQL,根据 dialect ⽅⾔,添加对应的物理分⻚语句和物理分⻚参数。

6、MyBatis ⼏种分⻚⽅式

1、数组分⻚
2、SQL 分⻚
3、拦截器分⻚
4、 RowBounds 分⻚

7、MyBatis 逻辑分⻚和物理分⻚

区别是什么:

1、物理分⻚速度上并不⼀定快于逻辑分⻚,逻辑分⻚速度上也并不⼀定快于物理分⻚。

2、物理分⻚总是优于逻辑分⻚:没必要将属于数据库端的压⼒加到应⽤端,就算速度上存在优势,它性能上的优点⾜以弥补这个缺点。

8、MyBatis ⽀持延迟加载?实现原理?

Mybatis 仅⽀持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是⼀对⼀,collection 指的就是⼀对多查询。在MyBatis配置⽂件中,可以配置是否启⽤延迟加载

lazyLoadingEnabled=true|false。

延迟加载的基本原理:使⽤ CGLIB 创建⽬标对象的代理对象,当调⽤⽬标⽅法时,进⼊拦截器⽅法,⽐如调⽤a.getB().getName(),拦截器 invoke() ⽅法发现 a.getB() 是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 SQL,把 B 查询上来,然后调⽤ a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() ⽅法的调⽤。

9、⼀级缓存和⼆级缓存

⼀级缓存:基于 PerpetualCache 的 HashMap 本地缓存,其存储作⽤域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开⼀级缓存;

⼆级缓存:与⼀级缓存机制相同,默认也是采⽤ PerpetualCache,HashMap 存储,不同在于其存储作⽤域为Mapper(Namespace),并且可⾃定义存储源,如 Ehcache。默认不打开⼆级缓存,要开启⼆级缓存,要实现 Serializable 序列化接⼝(可⽤来保存对象的状态),可在它的映射⽂件中配置 ;对于缓存数据更新机制,当某⼀个作⽤域(⼀级缓存 Session / ⼆级缓存 Namespaces)的进⾏了 C/U/D 操作后,默认该作⽤域下所有 select 中的缓存将被 clear。

10、执⾏器(Executor)

Mybatis 有 3 种基本的执⾏器(Executor):

1、 SimpleExecutor:每执⾏⼀次 update 或 select,就开启⼀个 Statement 对象,⽤完⽴刻关闭 Statement 对象;

2、 ReuseExecutor:执⾏ update 或 select,以 SQL 作为 key 查找 Statement 对象,存在就使⽤,不存在就创建,⽤完后,不关闭 Statement 对象,放置于 Map 内,供下次使⽤。就是重复使⽤Statement 对象;

3、 BatchExecutor:执⾏ update(没有 select,JDBC 批处理不⽀持select),将所有 SQL 都添加到批处理中(addBatch()),等待统⼀执⾏(executeBatch()),它缓存了多个 Statement 对象,每个Statement对象都是 addBatch() 完毕后,等待逐⼀执executeBatch() 批处理。和 JDBC 批处理相同。

11、动态 SQL

动态 SQL 是做啥的: 动态 SQL 可以让我们在 XML 映射⽂件内,以标签的形式编写动态 SQL,完成逻辑判断和动态拼接SQL 的功能;

哪些动态 **SQL:**MyBatis 提供了 9 种动态 SQL 标签:trim、where、set、foreach、if、choose、when、otherwise、bind;

**执⾏原理:**使⽤ OGNL 从 SQL 参数对象中计算表达式的值,根据表达式的值动态拼接 SQL,以此来完成动态SQL 的功能。

SpringBoot

1、介绍

**快问快答:**Spring 开源组织下的⼦项⽬,是 Spring 组件⼀站式解决⽅案,简化了使⽤Spring 难度,简省了的配置,提供了各种启动器,开发者能快速上⼿。

2、优点

1、独立运行:Spring Boot 内嵌各种 servlet 容器,Tomcat、Jetty 等,不需要打成war 包部署到容器中,打成⼀个可执⾏的 jar 包就能独⽴运⾏,所有依赖包都在 jar 包里。
2、简化配置 :spring-boot-starter-web 启动器能⾃动依赖其他组件,简少了 maven 配置,避免⼤量Maven导⼊,各版本冲突,
3、自动配置:Spring Boot 能根据当前类路径下的类、jar 包⾃动配置 bean,如:添加⼀个 spring-boot-starter web 启动器有 web 功能,不需其他配置。
3、⽆代码⽣成和XML配置: Spring Boot 配置过程中⽆代码⽣成,也不用 XML 配置,都是用条件注解完成配置。
4、简化监控: Spring Boot 提供⼀系列端点可以监控服务及应⽤,做健康检测

3、缺点

⼊⻔简单精通难,功能封装的太好了,原理⽐较难得懂!⽤多了容易产⽣依赖;SpringBoot⼀旦出了错误,内部封装⽐较深,错误调试难度⽐高

4、核⼼配置⽂件:applicationbootstrap

SpringBoot两个核⼼的配置⽂件:

bootstrap(.yml 或者 .properties):1、boostrap 由⽗ ApplicationContext 加载的,⽐applicaton优先加载,配置在应⽤程序上下⽂的引导阶段⽣效。2、在 SpringCloud Config 配置中⼼时或Nacos中会⽤到,添加配置中⼼配置属性加载外部配置信息;3、boostrap⾥的属性不能被覆盖;

application (.yml或者.properties):由ApplicatonContext 加载,⽤于 SpringBoot项⽬的⾃动化配置。

6、配置⽂件格式:.properties 和 .yml

**yml介绍:**是⼀种数据序列化语⾔。通常⽤于配置⽂件。在配置⽂件中加复杂属性时,YAML 可分层配置数据,更加结构化,更少混淆。

**yml优点:**⾮常流⾏的配置⽂件格式,前端还是后端,都可YAML 配置 。和传统的 properties 配置相⽐优势;

             1、 配置有序,在⼀些特殊的场景下,配置有序很关键
             2、 ⽀持数组,数组中的元素可以是基本数据类型也可以是对像
             3、⽐ properties 配置⽂件更加简洁,

YAML缺点:就是不⽀持 @PropertySource 注解导⼊⾃定义的 YAML 配置。

二者不同——主要书写格式不同:

1).properties :app.user.name = javastack

2).yml :app: user: name: javastack ;不⽀持 @PropertySource 注解导⼊配置。

7、核⼼注解是哪个?由哪⼏个注解组成的?

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
 @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
 @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {

主要组合包含了以下 3 个注解:

@SpringBootConfiguration: 组合了 @Configuration 注解,实现配置⽂件的功能。

@EnableAutoConfiguration: 打开⾃动配置的功能,也可以关闭某个⾃动配置的选项,

                                   如:关闭数据源⾃动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。 

@ComponentScan: Spring组件扫描。

8、开启⽅式

快问快答

1)继承spring-boot-starter-parent项⽬

2)导⼊spring-boot-dependencies项⽬依赖

详细介绍启动⽅式

1. 继承spring-boot-starter-parent项⽬

<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>1.5.6.RELEASE</version>
</parent>

2. 导⼊spring-boot-dependencies项⽬依赖

<dependencyManagement>
 <dependencies> 
 <dependency> 
 <groupId>org.springframework.boot</groupId> 
 <artifactId>spring-boot-dependencies</artifactId> 
 <version>1.5.6.RELEASE</version> 
 <type>pom</type> 
 <scope>import</scope> 
 </dependency>
</dependencyManagement>

注意点:

1、 属性覆盖只对继承有效

Spring Boot依赖包⾥⾯的组件的版本都是和当前Spring Boot绑定的,要修改⾥⾯组件的版本,只需要添加属性覆盖即可,但这种⽅式只对继承有效,不能用于导⼊依赖。

<properties>
 <slf4j.version>1.7.25<slf4j.version>
</properties>

如果导⼊的⽅式要实现版本的升级,达到上⾯的效果,这样也可以做到,把要升级的组件依赖放到Spring Boot之前。

修改Spring Boot依赖组件版本可能会造成不兼容的问题。

2、资源⽂件过滤问题

继承Spring Boot时,要⽤Maven resource filter过滤资源⽂件,资源⽂件⾥⾯的占位符为了使${}和Spring Boot区别,要⽤@…@包起来,不然⽆效。@…@占位符在yaml⽂件编辑器中编译报错,⽤继承⽅式有诸多问题。

9、JavaConfig

JavaConfig 是 Spring 社区产品,提供了配置 Spring IoC 容器的纯Java ⽅法。避免⽤XML 配置。

优点/特点/是什么/介绍一下:

⾯向对象的配置: 配置被定义为 JavaConfig 类,利⽤ 充分Java 中⾯向对象功能。⼀个配置类可以继承另⼀个,重写他的@Bean ⽅法。

减少或消除 XML 配置: 基于依赖注⼊的外化配置。不用在 XML和 Java 来回切换。提供了⼀种纯 Java ⽅法来配置与 XML 配置相似的 Spring容器。

类型安全和重构友好:提供了⼀种类型安全的⽅法配置 Spring容器。由于 Java 5.0 对泛型的⽀持,现在可按类型⽽不是按名检索 bean,不需要强制转换或字符串查找。

10、⾃动配置原理

1、SpringBoot启动会加载⼤量的⾃动配置类

2、然后看需要的功能有没有在默认的⾃动配置类中;

3、再来看这个⾃动配置类中配置了哪些组件;(只要我们要⽤的组件存在在其中,我们就不需要再⼿动配置了)

4、给容器中⾃动配置类添加组件的时候,会从properties类中获取某些属性。只需在配置⽂件中指定这些属性的值即可;

​ xx xxAutoConfigurartion:⾃动配置类;给容器中添加组件
​ xxxxProperties:封装配置⽂件中相关属性;

12、启动时做什么

1、SpringBoot启动时从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值

2、将这些值作为⾃动配置类导⼊容器 ,⾃动配置类就⽣效 ,进⾏⾃动配置;

3、整体解决⽅案和⾃动配置都在springboot-autoconfigure的jar包中;

4、它会给容器中导⼊⾮常多⾃动配置类 (xxxAutoConfiguration), 给容器中导⼊这个场景需要的所有组件 ,并配置好这些组件 ;

5、有了⾃动配置类 ,免去了⼿动编写配置注⼊功能组件等等⼯作;

13、要独⽴的容器运⾏吗

不需要,内置了 Tomcat/ Jetty 等容器。

16、是否可⽤XML 配置 ?

Spring Boot⽤ Java 配置⽽⾮ XML 配置,但也可⽤ XML 配置,通过@ImportResource 注解可引⼊XML 配置。

19、Spring Profiles

⽤来区分环境; 允许⽤户根据配置⽂件(dev,test,prod 等)注册 bean。当应⽤程序在开发中运⾏时,只有某些 bean 可以加载, 在PRODUCTION中,其他 bean 可以加载。假设:要求是 Swagger ⽂档仅适⽤于 QA 环境,禁⽤其他⽂档。可⽤配置⽂件完成。Spring Boot 使得使⽤配置⽂件⾮常简单。

20、定义SpringBoot程序运行端口

SpringBoot默认监听的8080端⼝;要在⾃定义端⼝上运⾏ SpringBoot 应⽤程序,可在application.properties 中

server.port = 8888

指定端⼝;将监听的端⼝改为8888。

21、SpringBoot的安全性

为了实现SpringBoot的安全性,⽤spring-boot-starter-security依赖项,添加安全配置。要很少代码。配置类将必须扩展WebSecurityConfigurerAdapter覆盖其⽅法。

22、Spring Security和Shiro

SpringBoot中使⽤Spring Security更加容易,只需要添加⼀个依赖就可以保护所有的接⼝,如果是SpringBoot 项⽬,⼀般选择 Spring Security 。

Shiro和Spring Security相⽐:

1、Spring Security 是重量级安全管理框架;Shiro 是轻量级安全管理框架
2、Spring Security 概念复杂,配置繁琐;Shiro 概念简单、配置简单
3、Spring Security 功能强⼤;Shiro 功能简单

23、SpringBoot如何解决跨域问题

可以在前端通过 JSONP 来解决,但 JSONP 只可发 GET 请求,⽆法发送其他类型请求,推荐在后端通过 (CORS,Cross-origin resource sharing) 来解决跨域问题。在 SSM 框架中,就可通过 CORS 解决跨域问题,之前是在 XML ⽂件中配置 CORS ,现可以通过实现WebMvcConfigurer接⼝然后重写addCorsMappings⽅法解决跨域问题。

@Configuration
public class CorsConfig implements WebMvcConfigurer {
 @Override
 public void addCorsMappings(CorsRegistry registry) {
 registry.addMapping("/**")
 .allowedOrigins("*")
 .allowCredentials(true)
 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
 .maxAge(3600);
 }
}

⽤cookie放⽤户登录的信息,在spring拦截器进⾏权限控制,权限不符合时,直接返回给⽤户固定的json结果。

当⽤户登录后,正常使⽤;⽤户退出登录时或者token过期时,由于拦截器和跨域的顺序有问题,出现了跨域的现象。

我们知道⼀个http请求,先⾛filter,到servlet后进⾏拦截器的处理,把cors放在filter⾥,就可优先于拦截器执⾏。

@Configuration
public class CorsConfig {
 @Bean
 public CorsFilter corsFilter() {
 CorsConfiguration corsConfiguration = new CorsConfiguration();
 corsConfiguration.addAllowedOrigin("*");
     corsConfiguration.addAllowedHeader("*");
 corsConfiguration.addAllowedMethod("*");
 corsConfiguration.setAllowCredentials(true);
 UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new
UrlBasedCorsConfigurationSource();
 urlBasedCorsConfigurationSource.registerCorsConfiguration("/**",
corsConfiguration);
 return new CorsFilter(urlBasedCorsConfigurationSource);
 }
}

24、CSRF 攻击

CSRF 代表跨站请求伪造。是⼀种攻击,让⽤户在身份验证的Web 应⽤程序上执⾏不需要的操作。CSRF 攻击专⻔针对状态改变请求,不是数据窃取,攻击者⽆法查看对伪造请求的响应。

25、 监视器

Spring boot actuator是spring启动框架中的重要功能之⼀。监视器可访问正在运⾏的程序状态。⼏个指标必须在⽣产环境中检查和监控。监视器模块公开了⼀组可直接作为HTTPURL访问的REST端点来检查状态。

26、禁⽤Actuator端点安全性

默认情况下,所有敏感的HTTP端点都是安全的,只有具有ACTUATOR⻆⾊的⽤户才能访问它们。安全性是使⽤标准的 HttpServletRequest.isUserlnRole ⽅法。我们可以使management.security.enabled=false 来禁⽤安全性。只有在执⾏机构端点在防⽕墙后访问时,才建议禁⽤安全性。

27、监视SpringBoot微服务

SpringBoot提供监视器端点以监控各个微服务的度量。这些端点对于获取有关应⽤程序的信息(如它们是否已启动)以及它们的组件(如数据库等)是否正常运⾏很有帮助。但是,使⽤监视器的⼀个主要缺点或困难是,我们必须单独打开应⽤程序的知识点以了解其状态或健康状况。想象⼀下涉及 50 个应⽤程序的微服务,管理员将不得不击中所有 50 个应⽤程序的执⾏终端。为了帮助我们处理这种情况,我们将使⽤位于的开源项⽬。 它建⽴在 SpringBoot Actuator 之上,它提供了⼀个 Web UI,使我们能够可视化多个应⽤程序的度量。

28、WebSockets

WebSocket是计算机通信协议,通过单个TCP连接提供全双⼯通信信道。

1、双向的 ——⽤ WebSocket 客户端或服务器都可发消息。

2、全双⼯的——客户端和服务器通信相互独⽴。

3、 单个TCP连接——初始连接⽤ HTTP,然后将连接升级到基于套接字的连接。然后这个单⼀连接⽤于所有未来通信

4、Light与http相⽐,WebSocket消息数据交换要轻。

29、Spring Data?

Spring Data 是 Spring 的⼀个⼦项⽬。⽤于简化数据库访问,⽀持NoSQL 和 关系数据存储。主要⽬标使数据库的访问⽅便快捷。

特点:

SpringData 项⽬⽀持 NoSQL 存储:

1、MongoDB (⽂档数据库)
2、Neo4j(图形数据库)
3、Redis(键/值存储)
4、Hbase(列族数据库)

SpringData 项⽬⽀持的关系数据存储技术:
1、JDBC
2、 JPA

Spring Data Jpa 减少数据访问层 (DAO) 的开发量. 我们就声明持久层接⼝,其他Spring Data JPA可以完成!Spring Data JPA 通过规范⽅法的名字,根据符合规范的名字确定⽅法要实现什么逻辑。

30、Spring Batch

Spring Boot Batch 提供可重⽤的函数,处理⼤量记录:⽇志/跟踪,事务管理,作业处理统计信息,作业重新启动,跳过和资源管理。

提供先进的技术服务和功能,通过优化和分区技术,可实现极⾼批量和⾼性能批处理。简单的⼤批量批处理可⾼度可扩展的⽅式利⽤框架处理⼤量的信息。

31、FreeMarker

FreeMarker 是基于 Java 的模板引擎,专注于使⽤ MVC 软件架构进⾏动态⽹⻚⽣成。主要优点是表示层和业务层完全分离。程序员可以处理应⽤程序代码,设计⼈员可以处理 html ⻚⾯。最后⽤freemarker 结合起来,最终输出⻚⾯。

32、和ActiveMQ集成

如何集成 SpringBoot和ActiveMQ?

可以⽤依赖关系。 很少的配置,不需要样板代码。

33、 Swagger

Swagger⽤于可视化API,⽤SwaggerUl为前端开发⼈员提供在线沙箱。⽤于⽣成RESTful Web服务的可视化的⼯具,规范和完整框架实现。使⽂档能够以与服务器相同速度更新。通过Swagger 定义时,消费者可以⽤少量的逻辑理解远程服务与其交互。Swagger 消除了调⽤服务时的猜测。

34、前后端分离,如何维护接⼝⽂档 ?

通过Spring Boot 前后端分离开发,前后端分离⼀定会有接⼝⽂档。⼀个⽅法是⽤ word 或者 md 维护接⼝⽂档,但效率太低,接⼝⼀变,所有⼈⼿上⽂档都得变。Spring Boot 中,解决⽅案是 Swagger ,⽤Swagger 可以快速⽣成⼀个接⼝⽂档⽹站,接⼝⼀变,⽂档就⾃动更新,所有⼯程师访问这⼀个在线⽹站就可以获取到最新接⼝⽂档。

35、 项⽬如何热部署

⽤ DEV ⼯具。Spring Boot 有⼀个开发⼯具(DevTools)模块,通过依赖关系,嵌⼊式tomcat 将重新启动。消除每次⼿动部署更改的需要。DevTools 模块完全满⾜开发⼈需求。该模块将在⽣产环境中被禁⽤。提供 H2 数据库控制台更好地测试应⽤程序。

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-devtools</artifactId>
</dependency

36、 starter

SpringBoot 中的starter是什么 ?
基于 Spring 已有功能实现的。
1、提供⾃动化配置类,⼀般命名为 XXXAutoConfiguration ,通过条件注解决定配置是否⽣效(条件注解就是 Spring 中原本就有的),
2、还会提供默认配置,允许根据实际情况⾃定义相关配置,通过类型安全的属性注⼊,新注⼊的属性会代替默认属性。第三⽅框架,只需引⼊依赖就可⽤了。也可⾃定义 Starter

37、spring-boot-starter-parent

创建⼀个 SpringBoot 项⽬,默认都有spring-boot-starter-parent
作⽤:

1、定义了 Java 编译版本为 1.8 。
2、使⽤ UTF-8 格式编码。
3、继承⾃ spring-boot-dependencies,他定义了依赖的版本,因为继承这个依赖,写依赖时不需要写版本号。
4、执⾏打包操作的配置。
5、⾃动化的资源过滤。
6、⾃动化的插件配置。
7、对 application.properties 和 application.yml 资源过滤,通过 profile 定义不同环境的配置⽂件,例如 application-dev.properties 和 application-dev.yml。

38、 SpringBoot 的jar和普通jar区别

SpringBoot 项⽬打成的 jar 是可执⾏的 ,可通过 java -jar xxx.jar 命令运⾏, 不可作为普通 jar 被其他项⽬依赖,依赖了也⽆法⽤其中的类。和普通 jar 的结构不同。普通jar 包,解压就是包名,包⾥就是代码,Spring Boot 打包成的 jar 解压后,在 \BOOT-INF\classes 下才是代码,⽆法被直接引⽤。如果⾮要引⽤,可在 pom.xml 中加配置,将 Spring Boot 项⽬打包成两个 jar ,⼀个可执⾏,⼀个可引⽤。

39、异常处理

通过实现⼀个 ControlerAdvice 类,来处理控制器类抛出的所有异常

40、 分⻚和排序

Spring Boot实现分⻚⾮常简单。使⽤Spring Data-JPA可以实现将可分⻚的org.springframework.data.domain.Pageable 传递给存储库⽅法。

public Page find(Integer page, Integer size) {
 if (null == page) {
 page = 0;
 }
 if (CheckUtils.isEmpty(size)) {
 size = 10;
 }
 PageRequest pageable = PageRequest.of(page, size, Sort.Direction.DESC, &quot;updateTime&quot;);
 Page users = userRepository.findAll(pageable);
 return users; }

43、微服务如何 session 共享

常⻅Session + Redis 来实现 session 共享。所有微服务的 session 保存在 Redis 上,各个微服务对 session有读写操作时,都去操作 Redis 上的 session

44、如何定时任务

1、⽤ Spring 中的 @Scheduled 的⽅式,主要通过 @Scheduled 注解来实现。
2、⽤ 第三⽅框架 QuartzQuartz ,则按照 Quartz 的⽅式,定义 Job 和 Trigger 即可。

三者关系

1、SpringBoot、Spring MVC和Spring

Spring Spring最重要的特征是依赖注⼊。所有Spring Modules不是依赖注⼊就是IOC控制反转。 当我们恰当的使⽤DI或者是IOC的时候,可以开发松耦合应⽤。

Spring MVC 提供分离式的⽅法开发Web应⽤。通过运⽤像DispatcherServelet,MoudlAndView 和 ViewResolver 等,开发 Web 应⽤会变的⾮常简单。

SpringBoot Spring和Spring MVC的问题在于需要配置⼤量的参数。 SpringBoot通过⼀个⾃动配置和启动的项解决这个问题。

MySQL

1、 MySQL 架构了解

MySQL 分为 Server 层存储引擎两部分。

Server 层包括:连接器、查询缓存、分析器、优化器、执⾏器,涵盖 MySQL ⼤多数核⼼功能,还包括内置函数(如:⽇期、时间、数学和加密函数等),跨存储引擎功能都在这⼀层实现,⽐如:存储过程、触发器、视图等等。

存储引擎层:负责数据存储和提取,他的架构是插件式的,⽀持 InnoDB、MyISAM 多个存储引擎。 从MySQL5.5.5 版本开始默认的是InnoDB,但在建表时可以通过 engine = MyISAM 指定存储引擎,不同存储引擎的表数据存取⽅式不同,⽀持的功能也不同。

不同存储引擎共⽤⼀个 Server 层,也就是从连接器到执⾏器的部分。

2、 SQL 语句执⾏流程

1、应⽤程序把查询 SQL 语句发送给服务器执⾏;
2 、查询缓存,如果查询缓存是打开的,服务器在接收到查询请求后,不会直接去数据库查询,⽽是在数据库的查询缓存中找是否有相对应的查询数据,如果存在,直接返回给客户端。
如果缓存不存在,进行下⾯的操作;
3 、查询优化处理,⽣成执⾏计划。这个阶段主要包括解析 SQL、预处理、优化 SQL 执⾏计划;
4、 MySQL 会根据相应的执⾏计划完成整个查询;
5、将查询结果返回给客户端

3、查询优化⽅法

减少请求的数据量

1、 只返回必要的列:不⽤ SELECT * 语句。

2、只返回必要的⾏:⽤ LIMIT 语句限制返回的数据。

3、缓存重复查询的数据:⽤缓存避免在数据库中查询,缓存查询性能提升⾮常明显。

减少服务器端扫描的⾏数

1、⽤索引覆盖查询。

4、索引

1、索引介绍:

索引是为了提⾼数据的查询效率,就像书⽬录⼀样。对数据库的表⽽⾔,索引类似书的“⽬录”。

**索引缺点:**1、创建和维护索引需要耗费时间,随着数据量 增加⽽增加;

               2、占⽤空间,不光要占⽤数据空间,也要占物理空间;
               3、对表增、删、改时索引也要动态维护,降低了数据维护速度。

建索引原则:

1、繁使⽤字段上、缩⼩查询范围的;
2、需要排序的字段上。

不建⽴索引原则:

1、 很少涉的列或重复值较多的列;
2、.特殊数据类型,⽐如:⽂本字段(text)等。

2、索引的数据结构

MySQL中较多索引有 Hash 索引、B+树索引。索引的数据结构和存储引擎有关, InnoDB 存储引擎默认索引为 B+ 树索引。

3、索引用到?语句慢原因?

⽤ Explain 命令查看执⾏计划,MySQL 执⾏某语句前,将语句过⼀遍查询优化器,拿到对语句分析的执⾏计划,包含许多信息。可分析和索引有关信息判断是否命中索引,如:possilbe_key、key、key_len 等字段,说明可能会⽤到索引、实际⽤的索引及索引⻓度。

4、索引失效?不走索引

⼏种不⾛索引的 SQL 语句例子:

1、索引参与表达式计算

SELECT 'sname' FROM 'stu' WHERE 'age' + 10 = 30;

2、 函数运算:

SELECT 'sname' FROM 'stu' WHERE LEFT('date',4) < 1990;

3、%词语%–模糊查询

SELECT * FROM 'manong' WHERE `uname` LIKE '%码农%' -- ⾛索引
SELECT * FROM 'manong' WHERE `uname` LIKE "%码农%" -- 不⾛索引

4、 字符串与数字⽐较不⾛索引:

CREATE TABLE 'a' ('a' char(10));
EXPLAIN SELECT * FROM 'a' WHERE 'a'="1" -- ⾛索引
EXPLAIN SELECT * FROM 'a'WHERE 'a'=1 -- 不⾛索引,同样也是使⽤了函数运算

5、 查询条件中有 or ,有条件带索引也不会⽤。换⾔之,就是要求使⽤的所有字段,都必须建⽴索引:

select * from dept where dname='xxx' or loc='xx' or deptno = 45;

6、正则表达式

7、 MySQL 内部优化器会对 SQL 语句进⾏优化,如果优化器估计使⽤全表扫描要⽐索引快,不使⽤索引。

3、索引的分类

从数据结构⻆度

1、树索引 (O(log(n)))
2、Hash 索引

从物理存储⻆度

1、 聚集索引(clustered index)
2、 ⾮聚集索引(non-clustered index)

从逻辑⻆度

1、普通索引
2、 唯⼀索引
3、主键索引
4、联合索引
5、全⽂索引

6、聚簇索引

对磁盘上数据按指定的⼀个或多个列的值排序。数据的顺序和索引顺序⼀致。主键会默认建聚簇索引,⼀张表允许存在⼀个聚簇索引。

聚簇⾮聚簇索引区别:

聚簇索引叶⼦节点就是数据节点,⾮聚簇索引的叶⼦节点仍是索引节点,只不过有指针指向对应数据块。

7、哈希索引

以 O(1) 时间查找,无序性。⽆法排序分组、只⽀持精确查找,⽆法部分查找和范围查找。

InnoDB 存储引擎中,当索引值被⽤的频繁时,会在 B+ 树索引之上再建哈希索引,就让 B+Tree 索引有哈希索引的优点,⽐如:快速的哈希查找。

8、覆盖索引

⼀个索引包含查询语句字段与条件就叫做覆盖索引。有以下优点:

1、索引⼩于数据⾏的⼤⼩,只读取索引能减少数据访问量。

3、 ⼀些存储引擎(例如:MyISAM)在内存中只缓存索引,数据操作系统来缓存。只访问索引可不使⽤系统调⽤。

4、对InnoDB 引擎,若辅助索引能够覆盖查询,⽆需访问主索引。

5、事务

1、特性ACID

1、原⼦性:事务最⼩执⾏单位,不可分割。要么全完成,要么不起作⽤;
2、⼀致性:事务前后,数据库从⼀个⼀致性状态转换到另⼀个⼀致性状态。
3、隔离性:并发访问数据库时,⼀个事物不被其他事务⼲扰,各并发事务之间数据库是独⽴的;
4、持久性:事务被提交后。对数据库中数据的改变是持久的,数据库发⽣故障也不会有任何影响。

2、隔离级别

1、.READ_UNCOMMITTED(未提交读): 最低隔离级别,允许读尚未提交数据的变更,会导致脏读、幻读或不可重复读;

2、READ_COMMITTED(提交读): 允许读并发事务已经提交的数据,可阻⽌脏读,可能幻读或不可重复读;

3、 REPEATABLE_READ(可重复读): 同⼀字段多次读结果⼀致,除⾮数据被事务⾃⼰修改,可阻⽌脏读和不可重复读,可能幻读;

4、SERIALIZABLE(串⾏化): 最⾼隔离级别,服从 ACID 隔离级别。所有事务依次执⾏,事务之间不可能产⽣⼲扰,可防⽌脏读、不可重复读、幻读。但严重影响程序性能。一般不⽤该级别。

3、脏读、不可重复读、幻读

脏读:

⼀个事务能读另⼀个事务未提交数据。⽐如:事务插⼊记录 A,事务还未提交,另⼀个事务读取到了记录 A。

不可重复读 :

⼀个事务内,多次读同⼀数据。

幻读:

⼀个事务内多次查询返回结果不⼀样。⼀个事务 A 第⼀次查询有 n 条记录,第⼆次同条件查询有 n+1 条记录。

幻读原因:另⼀个事务新增或删除或修改了第⼀个事务结果集的数据,同⼀个记录的数据被修改,所有数据⾏记录会变多或变少。

6、存储引擎

1、MySQL存储引擎介绍

存储引擎层:负责数据存储和提取,他的架构是插件式的,⽀持 InnoDB、MyISAM 多个存储引擎。 从MySQL5.5.5 版本开始默认的是InnoDB,但在建表时可以通过 engine = MyISAM 指定存储引擎,不同存储引擎的表数据存取⽅式不同,⽀持的功能也不同。

2、InnoDB MyISAM

1、事务:MyISAM不⽀持,InnoDB⽀持
2、全⽂索引:MyISAM ⽀持,InnoDB 5.6 之前不⽀持
3、关于 count()MyISAM存总⾏数,InnoDB 不会,要按⾏扫描。意思:对 select count()from table; 数据量⼤时,MyISAM 瞬间返回,InnoDB ⼀⾏⾏扫描;
4、 外键:MyISAM 不⽀持,InnoDB ⽀持
5、 锁:MyISAM 只⽀持表锁,InnoDB 可以⽀持⾏锁

3、InnoDB采⽤B+树

对IO性能影响,B 树的每个节点都存数据, B+ 树只有叶⼦节点存数据,查找相同数据量,B 树IO 更频繁。
索引存在磁盘上,数据量⼤时,不能把整个索引加载到内存,只能逐⼀加载每⼀个磁盘⻚(对应索引树的节点)

4、InnoDB 的锁

1、Record lock:单⾏记录上的锁;
2、Gap lock:间隙锁,锁⼀个范围,不包括记录本身;
3、Next-key lock:record+gap 锁⼀个范围,含记录本身。

7、数据库三范式

1、第⼀范式:强调的是列的原⼦性,数据库表每⼀列都是不可分割的原⼦数据项;
2、第⼆范式:要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字⼀部分的属性;
3、第三范式:任何⾮主属性不依赖于其它⾮主属性。

8、MySQL 默认隔离级别

数据库并发场景:

**1、读-读:**不存在任何问题,不需要并发控制;

**2、读-写:**有线程安全问题,会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读;

**3、写-写:**有线程安全问题,会存在更新丢失问题。多版本并发控制(MVCC)解决读-写冲突的⽆锁并发控制,为事务分配单向增⻓的时间戳,为每个修改保存⼀个版本,版本与事务时间戳关联,读操作只读事务开始前的数据库的快照。

MVCC 为数据库解决问题:

1、并发读写数据库时,可做到读操作时不⽤阻塞写操作,写操作不阻塞读操作,提⾼数据库并发读写性能;
2、可以解决脏读,幻读,不可重复读等事务隔离问题,不能解决更新丢失问题。

9、char varchar

char(n) :⻓度固定,⽐如: char(10),输⼊"abc"三个字符时,占的还是 10 个字节,剩下7 个字节是空。char 优点:效率⾼;缺点:占⽤空间;适⽤场景:固定⻓度的,使⽤char ⾮常合适。比如:存储密码的 md5 值,。
varchar(n) **:**⻓度可变,本身值占⽤的字节再加上记录他⻓度的字节。空间上考虑 varcahr ⽐较合适;效率考虑 char ⽐较合适。

10、varchar(10) varchar(20)

varchar(10)最多放 10 个字符,varchar(10) 和 varchar(20) 存 hello 占空间⼀样,但varchar(20)排序时会消耗更多内存,因为 order by col 采⽤ fixed_length 计算 col ⻓度。

11、B+ 树理解

1、B 树和叶⼦节点顺序访问指针实现,有 B 树平衡性,通过顺序访问指提⾼区间查询性能。

2、B+ 树中,⼀个节点中的 key 从左到右⾮递减排列,如果指针的左右相邻 key 是 key i 和 keyi+1,不为 null,该指针指向节点所有 key ⼤于等于 key i ⼩于等于 key i+1。

3、查找时,在根节点⼆分查找,找到 key 指针,指针指向的节点找到叶⼦节点,叶⼦节点⼆分查找,找出 key 对应的数据

4、插⼊、删除会破坏平衡树的平衡性,插⼊删除后,要对树进⾏分裂、合并、旋转维护平衡性。

13、最左前缀原则

MySQL联合索引时,要满⾜最左前缀原则。举例:

1、 B+ 树的数据项是复合数据结构,⽐如:(name, age, sex) B+ 树按从左到右的顺序搜树的,⽐如:当(⼩明, 22, 男),B+ 树会先⽐较 name ,name 相同⽐较 age 和 sex,得到数据。没有 name 时,B+ 树不知道第⼀步该查哪个节点,因为建⽴搜索树的时候 name 是第⼀个⽐较因⼦,必须根据 name 搜索才能知道下步去哪查。

2、 当 (⼩明, 男) 这样的数据时,B+ 树可以⽤ name 来指定搜索⽅向,但下⼀个字段 age 的没有,只能把名字等于⼩明的数据都找到,再匹配性别是男的数据。

最左前缀的补充:

1、⼀直向右匹配直到遇到范围查询(>、<、between、like)就停⽌,⽐如:a = 1and b = 2 and c > 3 and d = 4 如果建⽴ (a, b, c, d) 顺序的索引,d 是⽤不到索引的。如果建⽴ (a, b, d, c) 的索引则都可以⽤到,a、b、d 的顺序可以任意调整。

2、= 和 in 可以乱序,⽐如:a = 1 and b = 2 and c = 3 建⽴ (a, b ,c) 索引可以任意顺序,MySQL 的优化器会优化

成索引可以识别的形式。

14、⽔平切分和垂直切分

⽔平切分

⼀个表中记录拆到多个结构相同表中。将数据分布到集群不同节点上,减缓单个数据库压⼒。

垂直切分

⼀张表按列切成多个表,按照列的关系密集度切分,经常⽤的列和不经常⽤的列切到不同表中。例如:电商数据库切成商品数据库、⽤户数据库

15、主从复制涉及三个线程

主要涉及三个线程:binlog 线程、I/O 线程和 SQL 线程。

1、binlog 线程 :负责将主服务器上的数据更改写⼊⼆进制⽇志(Binary log)中。
2、I/O 线程 :负责从主服务器上读取⼆进制⽇志,并写⼊从服务器的᯿放⽇志(Relay log)中。
3、SQL 线程 :负责读取᯿放⽇志并᯿放其中的 SQL 语句。

16、主从同步延迟原因和解决办法

延迟的原因:

⼀个服务器开放 N 个连接给客户端,⼤并发的更新操作时, 服务器⾥读取 binlog 的线程仅有⼀个, 某个 SQL 在从服务器上执⾏的时间稍⻓或者某个 SQL 要进⾏锁表会导致主服务器 SQL ⼤量积压,不能同步到从服务器⾥。这就导致主从延迟。

解决办法:

没有根本的解决办法, 可做⼀些缓解措施。(因为所有 SQL 必在从服务器⾥执⾏⼀遍,但主服务器不断有更新操作时,⼀旦有延迟,延迟加重的可能性会越⼤)

1、主服务器要负责更新操作, 对安全性要求⽐从服务器⾼,有些设置可以改,⽐如sync_binlog=1,innodb_flush_log_at_trx_commit = 1 的设置, slave 不需要⾼的数据安全,可以将 sync_binlog 设为 0 或者关闭 binlog、innodb_flushlog、innodb_flush_log_at_trx_commit 也可以设置为 0 来提⾼ SQL 的执⾏效率。

2、增加从服务器,分散读的压⼒,降低服务器负载。

17、数据库读写分离

代理⽅式,代理服务器接收读写请求,决定转到哪个服务器。主服务器处理写操作及实时性要求⾼的读操作,从服务器处理读操作。

读写分离提⾼性能原因:

1、 主从服务器负责各⾃读和写,缓解了锁的争⽤;

2、从服务器可以使⽤ MyISAM,提升查询性能节约系统开销;

3、增加冗余,提⾼可⽤性。

18、MySQL ⾏锁和表锁

MyISAM 只⽀持表锁,InnoDB ⽀持表锁和⾏锁,默认⾏锁。

**表级锁:**开销⼩,加锁快,不出现死锁。锁力度⼤,发⽣锁冲突的概率⾼,并发度低。
**⾏级锁:**开销⼤,加锁慢,会出现死锁。锁⼒度⼩,发⽣锁冲突的概率⼩,并发度⾼。

20、MySQL 问题排查⼿段

1、用show processlist 命令查看当前所有连接信息;

2、⽤ Explain 命令查询 SQL 语句执⾏计划;

3、开启慢查询⽇志,看慢查询的 SQL。

21、MySQL CPU500% 他怎么处理

1、 列出所有进程(show processlist),观察所有进程,多秒没有状态变化的(⼲掉);

2、 查看超时⽇志或者错误⽇志 (⼀般是查询及⼤量插⼊导致 CPU与 I/O 上涨,不排除⽹络状态突然断了,⼀个请求只接受到⼀半。

linux

1.常用命令

rz:文件导入
sz:文件导出
mkdir:创建文件夹
rm:删除文件
, / :执行文件
v i :打开文件
按ESC 然后按Ctrl+:
q :退出
wq:保存退出
wq!:强制退出
su:切换到ROOT用户

2、无法显示本地IP

img

方法1:

输入vi /etc/sysconfig/network-scripts/ifcfg-ens33
img


只需要把ONBOOT=no改为ONBOOT=yes

方法2:

首先查看托管状态:nmcli n

img

若显示disable:
输入命令: nmcli n on 开启就好了

原博客连接:https://www.cnblogs.com/6b7b5fc3/p/14101283.html

3、Navicat无法连接到虚拟机数据库

永久性关闭: systemctl disable firewalld.service

一次性关闭: systemctl stop firewalld.service

Oracle

知识点:

1、登录命令:Sqlplus 用户名/密码 [as sysdba]

如果是超级管理员需要在用户名/密码后面加上 as sysdba,是以系统管理员的身份来登录的,如果是普通用户不需要as sysdba

2、 查看当前连接数据库的用户

​ 使用show user查看当前的用户

img

3. 用户的切换

在登录的状态下输入:conn 用户名/密码 [as sysdba]

如图:

Ø 切换为超级管理员

img

Ø 切换为普通的用户

img

4. 查看表的结构

Desc 表名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0sOwSsI-1649253860378)(C:\Users\admin\Desktop\888.PNG)]

5、增加字段

alter table 表名add 字段名 类型;

比如:
alter table test add stuNum number(2);

遇到的问题:

1、为什么可以查询Oracle自带的表,但是查不到我自己建的表?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iv6zB4ND-1649253860379)(C:\Users\admin\Desktop\捕获3333.PNG)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M6Jls2OG-1649253860379)(C:\Users\admin\Desktop\4444.PNG)]

原因:Oracle建表的表名要用大写,最开始用的小写test所以查不到,后边改成TEST之后就可以查到了

2、Oracle命令行表显示不整齐,很乱[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pexFhPyQ-1649253860380)(C:\Users\admin\Desktop\9897.PNG)]

解决办法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mODc2AoL-1649253860380)(C:\Users\admin\Desktop\7777777777.PNG)]

用这三个命令调整命令行表结构

计算机网络

1、三次握⼿

⾸先很多⼈会先讲下握⼿的过程:

1、第⼀次握⼿:客户端给服务器发送⼀个 SYN 报⽂。

2、第⼆次握⼿:服务器收到 SYN 报⽂之后,会应答⼀个 SYN+ACK 报⽂。

3、第三次握⼿:客户端收到 SYN+ACK 报⽂之后,会回应⼀个 ACK 报⽂。

4、服务器收到 ACK 报⽂之后,三次握⼿建⽴完成。

作⽤是为了确认双⽅的接收与发送能⼒是否正常。

这⾥我顺便解释⼀下为啥只有三次握⼿才能确认双⽅的接受与发送能⼒是否正常,⽽两次却不可以
第⼀次握⼿:客户端发送⽹络包,服务端收到了。这样服务端就能得出结论:客户端的发送能⼒、服务端的接收能⼒是正常的。

第⼆次握⼿:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能⼒,客户端的接收、发送能⼒是正常的。不过此时服务器并不能确认客户端的接收能⼒是否正常。

第三次握⼿:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能⼒正常,服务器⾃⼰的发送、接收能⼒也正常。

因此,需要三次握⼿才能确认双⽅的接收与发送能⼒是否正常。

这样回答其实也是可以的,但我觉得,这个过程的我们应该要描述的更详细⼀点,因为三次握⼿的过程中,双⽅是由很多状态的改变的,⽽这些状态,也是⾯试官可能会问的点。所以我觉得在回答三次握⼿的时候,我们应该要描述的详细⼀点,⽽且描述的详细⼀点意味着可以扯久⼀点。加分的描述我觉得应该是这样:

刚开始客户端处于 closed 的状态,服务端处于 listen 状态。然后

1、第⼀次握⼿:客户端给服务端发⼀个 SYN 报⽂,并指明客户端的初始化序列号 ISN©。此时客户端处于SYN_Send 状态。

2、第⼆次握⼿:服务器收到客户端的 SYN 报⽂之后,会以⾃⼰的 SYN 报⽂作为应答,并且也是指定了⾃⼰的初始化序列号 ISN(s),同时会把客户端的 ISN + 1 作为 ACK 的值,表示⾃⼰已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。

3、第三次握⼿:客户端收到 SYN 报⽂之后,会发送⼀个 ACK 报⽂,当然,也是⼀样把服务器的 ISN + 1 作为 ACK的值,表示已经收到了服务端的 SYN 报⽂,此时客户端处于 establised 状态。

4、服务器收到 ACK 报⽂之后,也处于 establised 状态,此时,双⽅以建⽴起了链接三次握⼿的作⽤三次握⼿的作⽤也是有好多的,多记住⼏个,保证不亏。例如:

1、确认双⽅的接受能⼒、发送能⼒是否正常。

2、指定⾃⼰的初始化序列号,为后⾯的可靠传送做准备。

1、(ISN)是固定的吗

三次握⼿的⼀个᯿要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对⽅知道接下来接收数据的时候如何按序列号组装数据。如果ISN是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态⽣成的。

2、什么是半连接队列

服务器第⼀次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双⽅还没有完全建⽴其连接,服务器会把此种状态下请求连接放在⼀个队列⾥,我们把这种队列称之为半连接队列。当然还有⼀个全连接队列,就是已经完成三次握⼿,建⽴起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

这⾥在补充⼀点关于SYN-ACK 重传次数的问题:服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进⾏⾸次᯿传,等待⼀段时间仍未收到客户确认包,进⾏第⼆次᯿传,如果᯿传次数超 过系统规定的最⼤᯿传次数,系统将该连接信息从半连接队列中删除。注意,每次᯿传等待的时间不⼀定相同,⼀般会是指数增⻓,例如间隔时间为 1s, 2s, 4s, 8s,

3、三次握⼿过程中可以携带数据吗

很多⼈可能会认为三次握⼿都不能携带数据,其实第三次握⼿的时候,是可以携带数据的。也就是说,第⼀次、第⼆次握⼿不可以携带数据,⽽第三次握⼿是可以携带数据的。

为什么这样呢?⼤家可以想⼀个问题,假如第⼀次握⼿可以携带数据的话,如果有⼈要恶意攻击服务器,那他每次都在第⼀次握⼿中的 SYN 报⽂中放⼊⼤量的数据,因为攻击者根本就不理服务器的接收、发送能⼒是否正常,然后疯狂着᯿复发 SYN 报⽂的话,这会让服务器花费很多时间、内存空间来接收这些报⽂。也就是说,第⼀次握⼿可以放数据的话,其中⼀个简单的原因就是会让服务器更加容易受到攻击了。

⽽对于第三次的话,此时客户端已经处于 established 状态,也就是说,对于客户端来说,他已经建⽴起连接了,并且也已经知道服务器的接收、发送能⼒是正常的了,所以能携带数据⻚没啥⽑病。

2、说⼀说四次挥⼿

四次ഀ⼿也⼀样,千万不要对⽅⼀个 FIN 报⽂,我⽅⼀个 ACK 报⽂,再我⽅⼀个 FIN 报⽂,我⽅⼀个 ACK 报⽂。然后结束,最好是说的详细⼀点,例如想下⾯这样就差不多了,要把每个阶段的状态记好,我上次⾯试就被问了⼏个了,呵呵。我答错了,还以为⾃⼰答对了,当时还解释的头头是道,呵呵。

刚开始双⽅都处于 establised 状态,假如是客户端先发起关闭请求,则:

1、第⼀次ഀ⼿:客户端发送⼀个 FIN 报⽂,报⽂中会指定⼀个序列号。此时客户端处于CLOSED_WAIT1状态。

2、第⼆次握⼿:服务端收到 FIN 之后,会发送 ACK 报⽂,且把客户端的序列号值 + 1 作为 ACK 报⽂的序列号值,表明已经收到客户端的报⽂了,此时服务端处于 CLOSE_WAIT2状态。

3、第三次ഀ⼿:如果服务端也想断开连接了,和客户端的第⼀次ഀ⼿⼀样,发给 FIN 报⽂,且指定⼀个序列号。此时服务端处于 LAST_ACK 的状态。

4、第四次ഀ⼿:客户端收到 FIN 之后,⼀样发送⼀个 ACK 报⽂作为应答,且把服务端的序列号值 + 1 作为⾃⼰ACK 报⽂的序列号值,此时客户端处于 TIME_WAIT 状态。需要过⼀阵⼦以确保服务端收到⾃⼰的 ACK 报⽂之后才会进⼊ CLOSED 状态

5、服务端收到 ACK 报⽂之后,就处于关闭连接了,处于 CLOSED 状态。这⾥特别需要主要的就是TIME_WAIT这个状态了,这个是⾯试的⾼频考点,就是要理解,为什么客户端发送 ACK之后不直接关闭,⽽是要等⼀阵⼦才关闭。这其中的原因就是,要确保服务器是否已经收到了我们的 ACK 报⽂,如果没有收到的话,服务器会᯿新发 FIN 报⽂给客户端,客户端再次收到 FIN 报⽂之后,就知道之前的 ACK 报⽂丢失了,然后再次发送 ACK 报⽂。

⾄于 TIME_WAIT 持续的时间⾄少是⼀个报⽂的来回时间。⼀般会设置⼀个计时,如果过了这个计时没有再次收到FIN 报⽂,则代表对⽅成功就是 ACK 报⽂,此时处于 CLOSED 状态。这⾥我给出每个状态所包含的含义,有兴趣的可以看看。

LISTEN - 侦听来⾃远⽅TCP端⼝的连接请求;

SYN-SENT -在发送连接请求后等待匹配的连接请求;

SYN-RECEIVED - 在收到和发送⼀个连接请求后等待对连接请求的确认;ESTABLISHED- 代表⼀个打开的连接,数据可以传送给⽤户;

FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;

FIN-WAIT-2 - 从远程TCP等待连接中断请求;

CLOSE-WAIT - 等待从本地⽤户发来的连接中断请求;

CLOSING -等待远程TCP对连接中断的确认;

LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;

TIME-WAIT -等待⾜够的时间以确保远程TCP接收到连接中断请求的确认;

CLOSED - 没有任何连接状态;

3、POST与GET区别

使⽤场景

GET ⽤于获取资源,⽽ POST ⽤于传输实体主体。

参数

GET 和 POST 的请求都能使⽤额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,⽽ POST 的参数存储在实体主体中。不能因为 POST 参数存储在实体主体中就认为它的安全性更⾼,因为照样可以通过⼀些抓包⼯具(Fiddler)查看。

因为 URL 只⽀持 ASCII 码,因此 GET 的参数中如果存在中⽂等字符就需要先进⾏编码。例如 中⽂ 会转换为%E4%B8%AD%E6%96%87 ,⽽空格会转换为 %20 。POST 参数⽀持标准字符集。

安全性

安全的 HTTP ⽅法不会改变服务器状态,也就是说它只是可读的。

GET ⽅法是安全的,⽽ POST 却不是,因为 POST 的⽬的是传送实体主体内容,这个内容可能是⽤户上传的表单数据,上传成功之后,服务器可能把这个数据存储到数据库中,因此状态也就发⽣了改变。

安全的⽅法除了 GET 之外还有:HEAD、OPTIONS。

不安全的⽅法除了 POST 之外还有 PUT、DELETE。

幂等性

幂等的 HTTP ⽅法,同样的请求被执⾏⼀次与连续执⾏多次的效果是⼀样的,服务器的状态也是⼀样的。换句话说就是,幂等⽅法不应该具有副作⽤(统计⽤途除外)。

所有的安全⽅法也都是幂等的。

在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等⽅法都是幂等的,⽽ POST ⽅法不是。

GET /pageX HTTP/1.1 是幂等的,连续调⽤多次,客户端接收到的结果都是⼀样的:

GET /pageX HTTP/1.1

GET /pageX HTTP/1.1

GET /pageX HTTP/1.1

GET /pageX HTTP/1.1Copy to clipboardErrorCopied

POST /add_row HTTP/1.1 不是幂等的,如果调⽤多次,就会增加多⾏记录:

POST /add_row HTTP/1.1 -> Adds a 1nd row

POST /add_row HTTP/1.1 -> Adds a 2nd row

POST /add_row HTTP/1.1 -> Adds a 3rd rowCopy to clipboardErrorCopied

DELETE /idX/delete HTTP/1.1 是幂等的,即使不同的请求接收到的状态码不⼀样:

DELETE /idX/delete HTTP/1.1 -> Returns 200 if idX exists

DELETE /idX/delete HTTP/1.1 -> Returns 404 as it just got deleted

DELETE /idX/delete HTTP/1.1 -> Returns 404Copy to clipboardErrorCopied


可缓存

如果要对响应进⾏缓存,需要满⾜以下条件:

请求报⽂的 HTTP ⽅法本身是可缓存的,包括 GET 和 HEAD,但是 PUT 和 DELETE 不可缓存,POST 在多数情况下不可缓存的。

响应报⽂的状态码是可缓存的,包括:200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501。

响应报⽂的 Cache-Control ⾸部字段没有指定不进⾏缓存。

XMLHttpRequest

为了阐述 POST 和 GET 的另⼀个区别,需要先了解 XMLHttpRequest:

XMLHttpRequest 是⼀个 API,它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了⼀个通过 URL 来获取数据的简单⽅式,并且不会使整个⻚⾯刷新。这使得⽹⻚只更新⼀部分⻚⾯⽽不会打扰到⽤户。XMLHttpRequest 在 AJAX 中被⼤量使⽤。

在使⽤ XMLHttpRequest 的 POST ⽅法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做,例如⽕狐就不会。

⽽ GET ⽅法 Header 和 Data 会⼀起发送。

4、说⼀说*TCP与UDP的区别

TCP****协议的主要特点

(1)TCP是⾯向连接的运输层协议;所谓⾯向连接就是双⽅传输数据之前,必须先建⽴⼀条通道,例如三次握⼿就是建议通道的⼀个过程,⽽四次ഀ⼿则是结束销毁通道的⼀个其中过程。

(2)每⼀条TCP连接只能有两个端点(即两个套接字),只能是点对点的;

(3)TCP提供可靠的传输服务。传送的数据⽆差错、不丢失、不᯿复、按序到达;

(4)TCP提供全双⼯通信。允许通信双⽅的应⽤进程在任何时候都可以发送数据,因为两端都设有发送缓存和接受缓存;

(5)⾯向字节流。虽然应⽤程序与TCP交互是⼀次⼀个⼤⼩不等的数据块,但TCP把这些数据看成⼀连串⽆结构的字节流,它不保证接收⽅收到的数据块和发送⽅发送的数据块具有对应⼤⼩关系,例如,发送⽅应⽤程序交给发送⽅的TCP10个数据块,但就受访的TCP可能只⽤了4个数据块久保收到的字节流交付给上层的应⽤程序,但字节流完全⼀样。

TCP的可靠性原理

可靠传输有如下两个特点:

a.传输信道⽆差错,保证传输数据正确;

b.不管发送⽅以多快的速度发送数据,接收⽅总是来得及处理收到的数据;

(1)⾸先,采⽤三次握⼿来建⽴TCP连接,四次握⼿来释放TCP连接,从⽽保证建⽴的传输信道是可靠的。

(2)其次,TCP采⽤了连续ARQ协议(回退N,Go-back-N;超时⾃动᯿传)来保证数据传输的正确性,使⽤滑动窗⼝协议来保证接⽅能够及时处理所接收到的数据,进⾏流量控制。

(3)最后,TCP使⽤慢开始、拥塞避免、快᯿传和快恢复来进⾏拥塞控制,避免⽹络拥塞。

UDP协议特点

(1)UDP是⽆连接的传输层协议;

(2)UDP使⽤尽最⼤努⼒交付,不保证可靠交付;

(3)UDP是⾯向报⽂的,对应⽤层交下来的报⽂,不合并,不拆分,保留原报⽂的边界;

(4)UDP没有拥塞控制,因此即使⽹络出现拥塞也不会降低发送速率;

(5)UDP⽀持⼀对⼀ ⼀对多 多对多的交互通信;

(6)UDP的⾸部开销⼩,只有8字节.

TCP和UDP的区别

(1)TCP是可靠传输,UDP是不可靠传输;

(2)TCP⾯向连接,UDP⽆连接;

(3)TCP传输数据有序,UDP不保证数据的有序性;

(4)TCP不保存数据边界,UDP保留数据边界;(5)TCP传输速度相对UDP较慢;

(6)TCP有流量控制和拥塞控制,UDP没有;

(7)TCP是᯿量级协议,UDP是轻量级协议;

(8)TCP⾸部较⻓20字节,UDP⾸部较短8字节;

基于TCP和UDP的常⽤协议

HTTP、HTTPS、FTP、TELNET、SMTP(简单邮件传输协议)协议基于可靠的TCP协议。TFTP、DNS、DHCP、TFTP、SNMP(简单⽹络管理协议)、RIP基于不可靠的UDP协议

TCP 和 UDP 的常⽤场景,这个问的好挺多的,例如我当时⾯试时,就被问过:QQ 登录的过程中,⽤到了 TCP 和UDP,QQ 通话呢?

5、HTTP1.0,1.1,2.0区别

http1.0:不能⻓连接,只能短连接

http1.1:可以⻓连接,也就是说,⼀个TCP连接可以发送多个HTTP请求,不过服务器只能按照顺序⼀个⼀个响应。不过规定了⽤Pipelining来试图解决这个问题,不过该功能默认是关闭的。Pipelining的意思就是说,可以在⼀个连接中发送多个请求(不需要等待响应),不过服务器端必须要按照收到的顺序

```

connetion:keep-alive | closed;

```

默认情况下浏览器不开启该功能。

http2.0:采⽤了多路复⽤,它把HTTP报⽂分解成更⼩的⼆进制帧来传送,不同的HTTP请求报⽂可以混合在⼀个TCP连接上传输,服务器收到后,在根据⼆进制帧⾥⾯存放的ID来进⾏分类,拼接。采⽤这种⽅式,相当于服务器可以同时处理⼏个不同的HTTP请求,也就是说,不需要吧整个HTTP请求处理完,就可以去处理其他的HTTP请求了。

并且HTTP还对请求头部进⾏了压缩。

6、什么是SQL注⼊?举个例⼦?

SQL注⼊就是通过把SQL命令插⼊到Web表单提交或输⼊域名或⻚⾯请求的查询字符串,最终达到欺骗服务器执⾏恶意的SQL命令。

1). SQL注⼊攻击的总体思路

(1). 寻找到SQL注⼊的位置 (2). 判断服务器类型和后台数据库类型 (3). 针对不通的服务器和数据库特点进⾏SQL注⼊攻击

2). SQL注⼊攻击实例

⽐如,在⼀个登录界⾯,要求输⼊⽤户名和密码,可以这样输⼊实现免帐号登录:

⽤户名: ‘or 1 = 1 --

密 码:

⽤户⼀旦点击登录,如若没有做特殊处理,那么这个⾮法⽤户就很得意的登陆进去了。这是为什么呢?

下⾯我们分析⼀下:从理论上说,后台认证程序中会有如下的SQL语句:

String sql = “select * from user_table where username=’ “+userName+” ’ and password=’
“+password+” ‘”;

因此,当输⼊了上⾯的⽤户名和密码,上⾯的SQL语句变成:

SELECT * FROM user_table WHERE username=’’or 1 = 1 –- and password=’’

分析上述SQL语句我们知道,username=‘ or 1=1 这个语句⼀定会成功;然后后⾯加两个-,这意味着注释,它将后⾯的语句注释,让他们不起作⽤。这样,上述语句永远都能正确执⾏,⽤户轻易骗过系统,获取合法身份。

3). 应对⽅法

(1). 参数绑定

使⽤预编译⼿段,绑定参数是最好的防SQL注⼊的⽅法。⽬前许多的ORM框架及JDBC等都实现了SQL预编译和参数绑定功能,攻击者的恶意SQL会被当做SQL的参数⽽不是SQL命令被执⾏。在mybatis的mapper⽂件中,对于传递的参数我们⼀般是使⽤#和 时,变量就是直接追加在sql中,⼀般会有sql注⼊问题。

(2). 使⽤正则表达式过滤传⼊的参数

**7、**XSS 攻击,举个例⼦?

XSS是⼀种经常出现在web应⽤中的计算机安全漏洞,与SQL注⼊⼀起成为web中最主流的攻击⽅式。

XSS是指恶意攻击者利⽤⽹站没有对⽤户提交数据进⾏转义处理或者过滤不⾜的缺点,进⽽添加⼀些脚本代码嵌⼊到web⻚⾯中去,使别的⽤户访问都会执⾏相应的嵌⼊代码,从⽽盗取⽤户资料、利⽤⽤户身份进⾏某种动作或者对访问者进⾏病毒侵害的⼀种攻击⽅式。

1). XSS攻击的危害

盗取各类⽤户帐号,如机器登录帐号、⽤户⽹银帐号、各类管理员帐号

控制企业数据,包括读取、篡改、添加、删除企业敏感数据的能⼒

盗窃企业᯿要的具有商业价值的资料

⾮法转账

强制发送电⼦邮件

⽹站挂⻢

控制受害者机器向其它⽹站发起攻击

2). 原因解析

主要原因:过于信任客户端提交的数据!

解决办法:不信任任何客户端提交的数据,只要是客户端提交的数据就应该先进⾏相应的过滤处理然后⽅可进⾏下⼀步的操作。

进⼀步分析细节:客户端提交的数据本来就是应⽤所需要的,但是恶意攻击者利⽤⽹站对客户端提交数据的信任,在数据中插⼊⼀些符号以及javascript代码,那么这些数据将会成为应⽤代码中的⼀部分了,那么攻击者就可以肆⽆忌惮地展开攻击啦,因此我们绝不可以信任任何客户端提交的数据!!!

3). XSS 攻击分类

(1). 反射性XSS攻击 (⾮持久性XSS攻击)

漏洞产⽣的原因是攻击者注⼊的数据反映在响应中。⼀个典型的⾮持久性XSS攻击包含⼀个带XSS攻击向量的链接

(即每次攻击需要⽤户的点击),例如,正常发送消息:

http://www.test.com/message.php?send=Hello,World!

接收者将会接收信息并显示Hello,World;但是,⾮正常发送消息:

http://www.test.com/message.php?send=<script>alert(‘foolish!’)</script>!

接收者接收消息显示的时候将会弹出警告窗⼝!

(2). 持久性XSS攻击 (留⾔板场景)

XSS攻击向量(⼀般指XSS攻击代码)存储在⽹站数据库,当⼀个⻚⾯被⽤户打开的时候执⾏。也就是说,每当⽤户使⽤

浏览器打开指定⻚⾯时,脚本便执⾏。

与⾮持久性XSS攻击相⽐,持久性XSS攻击危害性更⼤。从名字就可以了解到,持久性XSS攻击就是将攻击代码存⼊数据

库中,然后客户端打开时就执⾏这些攻击代码。

例如,留⾔板表单中的表单域:

•```html

<input type="text" name="content" value="这⾥是⽤户填写的数据">

正常操作流程是:⽤户是提交相应留⾔信息 —— 将数据存储到数据库 —— 其他⽤户访问留⾔板,应⽤去数据并显示;⽽⾮正常操作流程是攻击者在value填写:

并将数据提交、存储到数据库中;当其他⽤户取出数据显示的时候,将会执⾏这些攻击性代码。

4). 修复漏洞⽅针

漏洞产⽣的根本原因是 太相信⽤户提交的数据,对⽤户所提交的数据过滤不⾜所导致的,因此解决⽅案也应该从这个⽅⾯⼊⼿,具体⽅案包括:

将᯿要的cookie标记为http only, 这样的话Javascript 中的document.cookie语句就不能获取到cookie了(如果在cookie中设置了HttpOnly属性,那么通过js脚本将⽆法读取到cookie信息,这样能有效的防⽌XSS攻击);

表单数据规定值的类型,例如:年龄应为只能为int、name只能为字⺟数字组合。。。。

对数据进⾏Html Encode 处理

过滤或移除特殊的Html标签,例如: ,

, < for <, > for>, " for

过滤JavaScript 事件的标签,例如 “οnclick=”, “onfocus” 等等。

需要注意的是,在有些应⽤中是允许html标签出现的,甚⾄是javascript代码出现。因此,我们在过滤数据的时候需要仔细分析哪些数据是有特殊要求(例如输出需要html代码、javascript代码拼接、或者此表单直接允许使⽤等等),然后区别处理!

8、在交互过程中如果数据传送完了,还不想断开连接怎么办,怎么维持?

在 HTTP 中响应体的 Connection 字段指定为 keep-alive

9、GET请求中URL编码的意义

我们知道,在GET请求中会对URL中⾮⻄⽂字符进⾏编码,这样做的⽬的就是为了 避免歧义。看下⾯的例⼦,

针对“name1=value1&name2=value2”的例⼦,我们来谈⼀下数据从客户端到服务端的解析过程。⾸先,上述字符串在计算机中⽤ASCII吗表示为:

connetion:keep-alive;
 6E616D6531 3D 76616C756531 26 6E616D6532 3D 76616C756532
 6E616D6531:name1
 3D:=
 76616C756531:value1
 26:&
 6E616D6532:name2
 3D:=
 76616C756532:value2

服务端在接收到该数据后就可以遍历该字节流,⼀个字节⼀个字节的吃,当吃到3D这字节后,服务端就知道前⾯吃得字节表示⼀个key,再往后吃,如果遇到26,说明从刚才吃的3D到26⼦节之间的是上⼀个key的value,以此类推就可以解析出客户端传过来的参数。

现在考虑这样⼀个问题,如果我们的参数值中就包含 = 或 & 这种特殊字符的时候该怎么办?⽐如,“name1=value1”,其中value1的值是“va&lu=e1”字符串,那么实际在传输过程中就会变成这样“name1=va&lu=e1”。这样,我们的本意是只有⼀个键值对,但是服务端却会解析成两个键值对,这样就产⽣了歧义。

那么,如何解决上述问题带来的歧义呢?解决的办法就是对参数进⾏URL编码:例如,我们对上述会产⽣歧义的字符进⾏URL编码后结果:“name1=va%26lu%3D”,这样服务端会把紧跟在“%”后的字节当成普通的字节,就是不会把它当成各个参数或键值对的分隔符

10**、**HTTP 哪些常⽤的状态码及使⽤场景?

状态码分类

1xx:表示⽬前是协议的中间状态,还需要后续请求

2xx:表示请求成功

3xx:表示᯿定向状态,需要᯿新请求

4xx:表示请求报⽂错误

5xx:服务器端错误

常⽤状态码

101 切换请求协议,从 HTTP 切换到 WebSocket

200 请求成功,有响应体

301 永久᯿定向:会缓存

302 临时᯿定向:不会缓存

304 协商缓存命中

403 服务器禁⽌访问

404 资源未找到

400 请求错误

500 服务器端错误

503 服务器繁忙

**11、HTTP **如何实现⻓连接?在什么时候会超时?

通过在头部(请求和响应头)设置 Connection: keep-alive,HTTP1.0协议⽀持,但是默认关闭,从HTTP1.1协议以后,连接默认都是⻓连接

1、HTTP ⼀般会有 httpd 守护进程,⾥⾯可以设置 keep-alive timeout,当 tcp 链接闲置超过这个时间就会关闭,也可以在 HTTP 的 header ⾥⾯设置超时时间

2、TCP 的 keep-alive 包含三个参数,⽀持在系统内核的 net.ipv4 ⾥⾯设置:当 TCP 链接之后,闲置了tcp_keepalive_time,则会发⽣侦测包,如果没有收到对⽅的 ACK,那么会每隔 tcp_keepalive_intvl 再发⼀次,直到发送了 tcp_keepalive_probes,就会丢弃该链接。

(1)tcp_keepalive_intvl = 15 (2)tcp_keepalive_probes = 5 (3)tcp_keepalive_time = 1800

实际上 HTTP 没有⻓短链接,只有 TCP 有,TCP ⻓连接可以复⽤⼀个 TCP 链接来发起多次 HTTP 请求,这样可以减少资源消耗,⽐如⼀次请求 HTML,可能还需要请求后续的 JS/CSS/图⽚等

12、HTTP状态码301和302的区别,都有哪些⽤途?

⼀. 301重定向的概念

301᯿定向(301 Move Permanently),指⻚⾯永久性转移,表示为资源或⻚⾯永久性地转移到了另⼀个位置。301是HTTP协议中的⼀种状态码,当⽤户或搜索引擎向服务器发出浏览请求时,服务器返回的HTTP数据流中头信息(header)中包含状态码 301 ,表示该资源已经永久改变了位置。

301᯿定向是⼀种⾮常᯿要的"⾃动转向“技术,⽹址᯿定向最为可⾏的⼀种⽅法。

⼆.哪些情况需要做301重定向?

⽹⻚开发过程中,时常会遇到⽹站⽬录结构的调整,将⻚⾯转移到⼀个新地址;⽹⻚扩展名的改变,这些变化都会导致⽹⻚地址发⽣改变,此时⽤户收藏夹和搜索引擎数据库中的旧地址是⼀个错误的地址,访问之后会出现404⻚⾯,直接导致⽹站流量的损失。或者是我们需要多个域名跳转⾄同⼀个域名,例如本站主站点域名为www.conimi.com ,⽽还有⼀个域名 www.nico.cc,由于对该域名设置了301᯿定向,当输⼊www.nico.cc 时,⾃动跳转⾄ www.conimi.com 。

三. 301重定向有什么优点?

有利于⽹站⾸选域的确定,对于同⼀资源⻚⾯多条路径的301᯿定向有助于URL权᯿的集中。例如www.conimi.com和 conimi.com 是两个不同的域名,但是指向的内容完全相同,搜索引擎会对两个域名收录情况不同,这样导致⽹站权᯿和排名被分散;对conimi.com 做301᯿定向跳转⾄www.conimi.com 后,权᯿和排名集中到www.conimi.com,从⽽提升⾃然排名。

四. 302重定向⼜是什么⻤?

302᯿定向(302 Move Temporarily),指⻚⾯暂时性转移,表示资源或⻚⾯暂时转移到另⼀个位置,常被⽤作⽹址劫持,容易导致⽹站降权,严᯿时⽹站会被封掉,不推荐使⽤。

五. 301与302的区别

301᯿定向是⻚⾯永久性转移,搜索引擎在抓取新内容的同时也将旧的⽹址替换成᯿定向之后的⽹址;

302᯿定向是⻚⾯暂时性转移,搜索引擎会抓取新的内容⽽保存旧的⽹址并认为新的⽹址只是暂时的。

13、IP地址有哪些分类?

A类地址(1~126):⽹络号占前8位,以0开头,主机号占后24位。

B类地址(128~191):⽹络号占前16位,以10开头,主机号占后16位。

C类地址(192~223):⽹络号占前24位,以110开头,主机号占后8位。

D类地址(224~239):以1110开头,保留位多播地址。

E类地址(240~255):以1111开头,保留位今后使⽤

14、简单说下每⼀层对应的⽹络协议有哪些?

计算机五层⽹络体系中涉及的协议⾮常多,下⾯就常⽤的做了列举:

15**、ARP** 协议的⼯作原理?

⽹络层的 ARP 协议完成了 IP 地址与物理地址的映射。⾸先,每台主机都会在⾃⼰的 ARP 缓冲区中建⽴⼀个 ARP列表,以表示 IP 地址和 MAC 地址的对应关系。当源主机需要将⼀个数据包要发送到⽬的主机时,会⾸先检查⾃⼰ARP 列表中是否存在该 IP 地址对应的 MAC 地址:如果有,就直接将数据包发送到这个 MAC 地址;如果没有,就向本地⽹段发起⼀个 ARP 请求的⼴播包,查询此⽬的主机对应的 MAC 地址。

此 ARP 请求数据包⾥包括源主机的 IP 地址、硬件地址、以及⽬的主机的 IP 地址。⽹络中所有的主机收到这个 ARP请求后,会检查数据包中的⽬的 IP 是否和⾃⼰的 IP 地址⼀致。如果不相同就忽略此数据包;如果相同,该主机⾸先将发送端的 MAC 地址和 IP 地址添加到⾃⼰的 ARP 列表中,如果 ARP 表中已经存在该 IP 的信息,则将其覆盖,然后给源主机发送⼀个 ARP 响应数据包,告诉对⽅⾃⼰是它需要查找的 MAC 地址;源主机收到这个 ARP 响应数据包后,将得到的⽬的主机的 IP 地址和 MAC 地址添加到⾃⼰的 ARP 列表中,并利⽤此信息开始数据的传输。如果源主机⼀直没有收到 ARP 响应数据包,表示 ARP 查询失败

16、TCP 的主要特点是什么?

1、 TCP 是⾯向连接的。(就好像打电话⼀样,通话前需要先拨号建⽴连接,通话结束后要挂机释放连接);

2、 每⼀条 TCP 连接只能有两个端点,每⼀条 TCP 连接只能是点对点的(⼀对⼀);

3、TCP 提供可靠交付的服务。通过 TCP 连接传送的数据,⽆差错、不丢失、不᯿复、并且按序到达;

4、TCP 提供全双⼯通信。TCP 允许通信双⽅的应⽤进程在任何时候都能发送数据。TCP 连接的两端都设有发送缓存和接收缓存,⽤来临时存放双⽅通信的数据;

5、 ⾯向字节流。TCP 中的“流”(Stream)指的是流⼊进程或从进程流出的字节序列。“⾯向字节流”的含义是:虽然应⽤程序和 TCP 的交互是⼀次⼀个数据块(⼤⼩不等),但 TCP 把应⽤程序交下来的数据仅仅看成是⼀连串的⽆结构的字节流。

17**、**UDP 的主要特点是什么?

1、 UDP 是⽆连接的;

2、UDP 使⽤尽最⼤努⼒交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态(这⾥⾯有许多参数);

3、 UDP 是⾯向报⽂的;

4、UDP 没有拥塞控制,因此⽹络出现拥塞不会使源主机的发送速率降低(对实时应⽤很有⽤,如 直播,实时视频会议等);

5、 UDP ⽀持⼀对⼀、⼀对多、多对⼀和多对多的交互通信;

6、UDP 的⾸部开销⼩,只有 8 个字节,⽐ TCP 的 20 个字节的⾸部要短。

**18、TCP **和 UDP 分别对应的常⻅应⽤层协议有哪些?

1. TCP 对应的应⽤层协议

FTP:定义了⽂件传输协议,使⽤ 21 端⼝。常说某某计算机开了 FTP 服务便是启动了⽂件传输服务。下载⽂件,上传主⻚,都要⽤到 FTP 服务。

Telnet:它是⼀种⽤于远程登陆的端⼝,⽤户可以以⾃⼰的身份远程连接到计算机上,通过这种端⼝可以提供⼀种基于 DOS 模式下的通信服务。如以前的 BBS 是-纯字符界⾯的,⽀持 BBS 的服务器将 23 端⼝打开,对外提供服务。

SMTP:定义了简单邮件传送协议,现在很多邮件服务器都⽤的是这个协议,⽤于发送邮件。如常⻅的免费邮件服务中⽤的就是这个邮件服务端⼝,所以在电⼦邮件设置-中常看到有这么 SMTP 端⼝设置这个栏,服务器开放的是25 号端⼝。

POP3:它是和 SMTP 对应,POP3 ⽤于接收邮件。通常情况下,POP3 协议所⽤的是 110 端⼝。也是说,只要你有相应的使⽤ POP3 协议的程序(例如 Fo-xmail 或 Outlook),就可以不以 Web ⽅式登陆进邮箱界⾯,直接⽤邮件程序就可以收到邮件(如是163 邮箱就没有必要先进⼊⽹易⽹站,再进⼊⾃⼰的邮-箱来收信)。

HTTP:从 Web 服务器传输超⽂本到本地浏览器的传送协议。

2. UDP 对应的应⽤层协议

DNS:⽤于域名解析服务,将域名地址转换为 IP 地址。DNS ⽤的是 53 号端⼝。

SNMP:简单⽹络管理协议,使⽤ 161 号端⼝,是⽤来管理⽹络设备的。由于⽹络设备很多,⽆连接的服务就体现

出其优势。

TFTP(Trival File Transfer Protocal):简单⽂件传输协议,该协议在熟知端⼝ 69 上使⽤ UDP 服务。

19、为什么 TIME-WAIT 状态必须等待 2MSL 的时间呢?

1、为了保证 A 发送的最后⼀个 ACK 报⽂段能够到达 B。这个 ACK 报⽂段有可能丢失,因⽽使处在 LAST-ACK 状态的 B 收不到对已发送的 FIN + ACK 报⽂段的确认。B 会超时᯿传这个 FIN+ACK 报⽂段,⽽ A 就能在 2MSL 时间内(超时 + 1MSL 传输)收到这个᯿传的 FIN+ACK 报⽂段。接着 A ᯿传⼀次确认,᯿新启动 2MSL 计时器。最后,A 和 B 都正常进⼊到 CLOSED 状态。如果 A 在 TIME-WAIT 状态不等待⼀段时间,⽽是在发送完 ACK 报⽂段后⽴即释放连接,那么就⽆法收到 B ᯿传的 FIN + ACK 报⽂段,因⽽也不会再发送⼀次确认报⽂段,这样,B 就⽆法按照正常步骤进⼊ CLOSED 状态。

2、 防⽌已失效的连接请求报⽂段出现在本连接中。A 在发送完最后⼀个 ACK 报⽂段后,再经过时间 2MSL,就可以使本连接持续的时间内所产⽣的所有报⽂段都从⽹络中消失。这样就可以使下⼀个连接中不会出现这种旧的连接请求报⽂段。

20、保活计时器的作⽤?

除时间等待计时器外,TCP 还有⼀个保活计时器(keepalive timer)。设想这样的场景:客户已主动与服务器建⽴了 TCP 连接。但后来客户端的主机突然发⽣故障。显然,服务器以后就不能再收到客户端发来的数据。因此,应当有措施使服务器不要再⽩⽩等待下去。这就需要使⽤保活计时器了。

服务器每收到⼀次客户的数据,就᯿新设置保活计时器,时间的设置通常是两个⼩时。若两个⼩时都没有收到客户端的数据,服务端就发送⼀个探测报⽂段,以后则每隔 75 秒钟发送⼀次。若连续发送 10个 探测报⽂段后仍然⽆客户端的响应,服务端就认为客户端出了故障,接着就关闭这个连接。

**21、TCP **协议是如何保证可靠传输的?

  1. 数据包校验:⽬的是检测数据在传输过程中的任何变化,若校验出包有错,则丢弃报⽂段并且不给出响应,这时 TCP 发送数据端超时后会᯿发数据;
  2. 对失序数据包᯿排序:既然 TCP 报⽂段作为 IP 数据报来传输,⽽ IP 数据报的到达可能会失序,因此 TCP 报⽂段的到达也可能会失序。TCP 将对失序数据进⾏᯿新排序,然后才交给应⽤层;
  3. 丢弃᯿复数据:对于᯿复数据,能够丢弃᯿复数据;
  4. 应答机制:当 TCP 收到发⾃ TCP 连接另⼀端的数据,它将发送⼀个确认。这个确认不是⽴即发送,通常将推迟⼏分之⼀秒;
  5. 超时᯿发:当 TCP 发出⼀个段后,它启动⼀个定时器,等待⽬的端确认收到这个报⽂段。如果不能及时收到⼀个确认,将᯿发这个报⽂段;
  6. 流量控制:TCP 连接的每⼀⽅都有固定⼤⼩的缓冲空间。TCP 的接收端只允许另⼀端发送接收端缓冲区所能接纳的数据,这可以防⽌较快主机致使较慢主机的缓冲区溢出,这就是流量控制。TCP 使⽤的流量控制协议是可变⼤⼩的滑动窗⼝协议。

22、谈谈你对停⽌等待协议的理解?

停⽌等待协议是为了实现可靠传输的,它的基本原理就是每发完⼀个分组就停⽌发送,等待对⽅确认。在收到确认后再发下⼀个分组;在停⽌等待协议中,若接收⽅收到᯿复分组,就丢弃该分组,但同时还要发送确认。主要包括以下⼏种情况:⽆差错情况、出现差错情况(超时᯿传)、确认丢失和确认迟到、确认丢失和确认迟到。

23**、谈谈你对** ARQ 协议的理解?

⾃动重传请求 ARQ 协议

停⽌等待协议中超时᯿传是指只要超过⼀段时间仍然没有收到确认,就᯿传前⾯发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完⼀个分组需要设置⼀个超时计时器,其᯿传时间应⽐数据在分组传输的平均往返时间更⻓⼀些。这种⾃动᯿传⽅式常称为⾃动᯿传请求 ARQ。

连续 ARQ 协议

连续 ARQ 协议可提⾼信道利⽤率。发送⽅维持⼀个发送窗⼝,凡位于发送窗⼝内的分组可以连续发送出去,⽽不需要等待对⽅确认。接收⽅⼀般采⽤累计确认,对按序到达的最后⼀个分组发送确认,表明到这个分组为⽌的所有分组都已经正确收到了。

24、谈谈你对滑动窗⼝的了解?

TCP 利⽤滑动窗⼝实现流量控制的机制。滑动窗⼝(Sliding window)是⼀种流量控制技术。早期的⽹络通信中,通信双⽅不会考虑⽹络的拥೿情况直接发送数据。由于⼤家不知道⽹络拥塞状况,同时发送数据,导致中间节点阻塞掉包,谁也发不了数据,所以就有了滑动窗⼝机制来解决此问题。

TCP 中采⽤滑动窗⼝来进⾏传输控制,滑动窗⼝的⼤⼩意味着接收⽅还有多⼤的缓冲区可以⽤于接收数据。发送⽅可以通过滑动窗⼝的⼤⼩来确定应该发送多少字节的数据。当滑动窗⼝为 0 时,发送⽅⼀般不能再发送数据报,但有两种情况除外,⼀种情况是可以发送紧急数据,例如,允许⽤户终⽌在远端机上的运⾏进程。另⼀种情况是发送⽅可以发送⼀个 1 字节的数据报来通知接收⽅᯿新声明它希望接收的下⼀字节及发送⽅的滑动窗⼝⼤⼩。

25、谈下你对流量控制的理解?

TCP 利⽤滑动窗⼝实现流量控制。流量控制是为了控制发送⽅发送速率,保证接收⽅来得及接收。接收⽅发送的确认报⽂中的窗⼝字段可以⽤来控制发送⽅窗⼝⼤⼩,从⽽影响发送⽅的发送速率。将窗⼝字段设置为 0,则发送⽅不能发送数据。

26**、谈下你对** TCP 拥塞控制的理解?使⽤了哪些算法?

拥塞控制和流量控制不同,前者是⼀个全局性的过程,⽽后者指点对点通信量的控制。在某段时间,若对⽹络中某⼀资源的需求超过了该资源所能提供的可⽤部分,⽹络的性能就要变坏。这种情况就叫拥塞。

拥塞控制就是为了防⽌过多的数据注⼊到⽹络中,这样就可以使⽹络中的路由器或链路不致于过载。拥塞控制所要做的都有⼀个前提,就是⽹络能够承受现有的⽹络负荷。拥塞控制是⼀个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低⽹络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。

为了进⾏拥塞控制,TCP 发送⽅要维持⼀个拥塞窗⼝(cwnd) 的状态变量。拥塞控制窗⼝的⼤⼩取决于⽹络的拥塞程度,并且动态变化。发送⽅让⾃⼰的发送窗⼝取为拥塞窗⼝和接收⽅的接受窗⼝中较⼩的⼀个。

TCP 的拥塞控制采⽤了四种算法,即:慢开始、拥塞避免、快᯿传和快恢复。在⽹络层也可以使路由器采⽤适当的分组丢弃策略(如:主动队列管理 AQM),以减少⽹络拥塞的发⽣。

慢开始:

慢开始算法的思路是当主机开始发送数据时,如果⽴即把⼤量数据字节注⼊到⽹络,那么可能会引起⽹络阻塞,因为现在还不知道⽹络的符合情况。经验表明,较好的⽅法是先探测⼀下,即由⼩到⼤逐渐增⼤发送窗⼝,也就是由⼩到⼤逐渐增⼤拥塞窗⼝数值。cwnd 初始值为 1,每经过⼀个传播轮次,cwnd 加倍。

拥塞避免:

拥塞避免算法的思路是让拥塞窗⼝ cwnd 缓慢增⼤,即每经过⼀个往返时间 RTT 就把发送⽅的 cwnd 加 1。

快重传与快恢复:

在 TCP/IP 中,快速᯿传和快恢复(fast retransmit and recovery,FRR)是⼀种拥塞控制算法,它能快速恢复丢失的数据包。

没有 FRR,如果数据包丢失了,TCP 将会使⽤定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到⼀个不按顺序的数据段,它会⽴即给发送机发送⼀个᯿复确认。如果发送机接收到三个᯿复确认,它会假定确认件指出的数据段丢失了,并⽴即᯿传这些丢失的数据段。

有了 FRR,就不会因为᯿传时要求的暂停被耽误。当有单独的数据包丢失时,快速᯿传和快恢复(FRR)能最有效地⼯作。当有多个数据信息包在某⼀段很短的时间内丢失时,它则不能很有效地⼯作。

27、什么是粘包?

在进⾏ Java NIO 学习时,可能会发现:如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在⼀起的情况。

  1. TCP 是基于字节流的,虽然应⽤层和 TCP 传输层之间的数据交互是⼤⼩不等的数据块,但是 TCP 把这些数据块仅仅看成⼀连串⽆结构的字节流,没有边界;
  2. 从 TCP 的帧结构也可以看出,在 TCP 的⾸部没有表示数据⻓度的字段。

基于上⾯两点,在使⽤ TCP 传输数据时,才有粘包或者拆包现象发⽣的可能。⼀个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。

接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来⼀块,这种情况即发⽣了拆包和粘包。拆包和粘包的问题导致接收端在处理的时候会⾮常困难,因为⽆法区分⼀个完整的数据包。

**28、TCP **黏包是怎么产⽣的?

发送⽅产⽣粘包

采⽤ TCP 协议传输数据的客户端与服务器经常是保持⼀个⻓连接的状态(⼀次连接发⼀次数据不存在粘包),双⽅在连接不断开的情况下,可以⼀直传输数据。但当发送的数据包过于的⼩时,那么 TCP 协议默认的会启⽤ Nagle算法,将这些较⼩的数据包进⾏合并发送(缓冲区数据发送是⼀个堆压的过程);这个合并过程就是在发送缓冲区中进⾏的,也就是说数据发送出来它已经是粘包的状态了。

接收⽅产⽣粘包

接收⽅采⽤ TCP 协议接收数据时的过程是这样的:数据到接收⽅,从⽹络模型的下⽅传递⾄传输层,传输层的TCP 协议处理是将其放置接收缓冲区,然后由应⽤层来主动获取(C 语⾔⽤ recv、read 等函数);这时会出现⼀个问题,就是我们在程序中调⽤的读取数据函数不能及时的把缓冲区中的数据拿出来,⽽下⼀个数据⼜到来并有⼀部分放⼊的缓冲区末尾,等我们读取数据时就是⼀个粘包。(放数据的速度 > 应⽤层拿数据速度)

29、怎么解决拆包和粘包

分包机制⼀般有两个通⽤的解决⽅法:

  1. 特殊字符控制;
  2. 在包头⾸都添加数据包的⻓度。

如果使⽤ netty 的话,就有专⻔的编码器和解码器解决拆包和粘包问题了。

tips:UDP 没有粘包问题,但是有丢包和乱序。不完整的包是不会有的,收到的都是完全正确的包。传送的数据单位协议是 UDP 报⽂或⽤户数据报,发送的时候既不合并,也不拆分。

**30、forward **和 redirect 的区别?

Forward 和 Redirect 代表了两种请求转发⽅式:直接转发和间接转发。

直接转发⽅式(Forward):客户端和浏览器只发出⼀次请求,Servlet、HTML、JSP 或其它信息资源,由第⼆个信息资源响应该请求,在请求对象 request 中,保存的对象对于每个信息资源是共享的。

间接转发⽅式(Redirect):实际是两次 HTTP 请求,服务器端在响应第⼀次请求的时候,让浏览器再向另外⼀个URL 发出请求,从⽽达到转发的⽬的。

举个通俗的例⼦:

直接转发就相当于:“A 找 B 借钱,B 说没有,B 去找 C 借,借到借不到都会把消息传递给 A”;

间接转发就相当于:“A 找 B 借钱,B 说没有,让 A 去找 C 借”。

31、HTTP⽅法有哪些?

客户端发送的 请求报⽂ 第⼀⾏为请求⾏,包含了⽅法字段。

  1. GET:获取资源,当前⽹络中绝⼤部分使⽤的都是 GET;
  2. HEAD:获取报⽂⾸部,和 GET ⽅法类似,但是不返回报⽂实体主体部分;
  3. POST:传输实体主体
  4. PUT:上传⽂件,由于⾃身不带验证机制,任何⼈都可以上传⽂件,因此存在安全性问题,⼀般不使⽤该⽅法。
  5. PATCH:对资源进⾏部分修改。PUT 也可以⽤于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。
  6. OPTIONS:查询指定的 URL ⽀持的⽅法;
  7. CONNECT:要求在与代理服务器通信时建⽴隧道。使⽤ SSL(Secure Sockets Layer,安全套接层)和TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经⽹络隧道传输。
  8. TRACE:追踪路径。服务器会将通信路径返回给客户端。发送请求时,在 Max-Forwards ⾸部字段中填⼊数值,每经过⼀个服务器就会减 1,当数值为 0 时就停⽌传输。通常不会使⽤ TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪)。

32、在浏览器中输⼊ URL 地址到显示主⻚的过程?

  1. DNS 解析:浏览器查询 DNS,获取域名对应的 IP 地址:具体过程包括浏览器搜索⾃身的 DNS 缓存、搜索操作系统的 DNS 缓存、读取本地的 Host ⽂件和向本地 DNS 服务器进⾏查询等。对于向本地 DNS 服务器进⾏查询,如果要查询的域名包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析(此解析具有权威性);如果要查询的域名不由本地 DNS 服务器区域解析,但该服务器已缓存了此⽹址映射关系,则调⽤这个 IP 地址映射,完成域名解析(此解析不具有权威性)。如果本地域名服务器并未缓存该⽹址映射关系,那么将根据其设置发起递归查询或者迭代查询;
  2. TCP 连接:浏览器获得域名对应的 IP 地址以后,浏览器向服务器请求建⽴链接,发起三次握⼿;
  3. 发送 HTTP 请求:TCP 连接建⽴起来后,浏览器向服务器发送 HTTP 请求;
  4. 服务器处理请求并返回 HTTP 报⽂:服务器接收到这个请求,并根据路径参数映射到特定的请求处理器进⾏处理,并将处理结果及相应的视图返回给浏览器;
  5. 浏览器解析渲染⻚⾯:浏览器解析并渲染视图,若遇到对 js ⽂件、css ⽂件及图⽚等静态资源的引⽤,则᯿复上述步骤并向服务器请求这些资源;浏览器根据其请求到的资源、数据渲染⻚⾯,最终向⽤户呈现⼀个完整的⻚⾯。
  6. 连接结束。

33、DNS的解析过程?

  1. 主机向本地域名服务器的查询⼀般都是采⽤递归查询。所谓递归查询就是:如果主机所询问的本地域名服务器不知道被查询的域名的 IP 地址,那么本地域名服务器就以 DNS 客户的身份,向根域名服务器继续发出查询请求报⽂(即替主机继续查询),⽽不是让主机⾃⼰进⾏下⼀步查询。因此,递归查询返回的查询结果或者是所要查询的 IP 地址,或者是报错,表示⽆法查询到所需的 IP 地址。
  2. 本地域名服务器向根域名服务器的查询的迭代查询。迭代查询的特点:当根域名服务器收到本地域名服务器发出的迭代查询请求报⽂时,要么给出所要查询的 IP 地址,要么告诉本地服务器:“你下⼀步应当向哪⼀个域名服务器进⾏查询”。然后让本地服务器进⾏后续的查询。根域名服务器通常是把⾃⼰知道的顶级域名服务器的IP 地址告诉本地域名服务器,让本地域名服务器再向顶级域名服务器查询。顶级域名服务器在收到本地域名服务器的查询请求后,要么给出所要查询的 IP 地址,要么告诉本地服务器下⼀步应当向哪⼀个权限域名服务器进⾏查询。最后,本地域名服务器得到了所要解析的 IP 地址或报错,然后把这个结果返回给发起查询的主机。

34、谈谈你对域名缓存的了解?

为了提⾼ DNS 查询效率,并减轻服务器的负荷和减少因特⽹上的 DNS 查询报⽂数量,在域名服务器中⼴泛使⽤了⾼速缓存,⽤来存放最近查询过的域名以及从何处获得域名映射信息的记录。

由于名字到地址的绑定并不经常改变,为保持⾼速缓存中的内容正确,域名服务器应为每项内容设置计时器并处理超过合理时间的项(例如:每个项⽬两天)。当域名服务器已从缓存中删去某项信息后⼜被请求查询该项信息,就必须᯿新到授权管理该项的域名服务器绑定信息。当权限服务器回答⼀个查询请求时,在响应中都指明绑定有效存在的时间值。增加此时间值可减少⽹络开销,⽽减少此时间值可提⾼域名解析的正确性。

不仅在本地域名服务器中需要⾼速缓存,在主机中也需要。许多主机在启动时从本地服务器下载名字和地址的全部数据库,维护存放⾃⼰最近使⽤的域名的⾼速缓存,并且只在从缓存中找不到名字时才使⽤域名服务器。维护本地域名服务器数据库的主机应当定期地检查域名服务器以获取新的映射信息,⽽且主机必须从缓存中删除⽆效的项。由于域名改动并不频繁,⼤多数⽹点不需花精⼒就能维护数据库的⼀致性。

**35、谈下你对 **HTTP ⻓连接和短连接的理解?分别应⽤于哪些场景?

在 HTTP/1.0 中默认使⽤短连接。也就是说,客户端和服务器每进⾏⼀次 HTTP 操作,就建⽴⼀次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类型的 Web ⻚中包含有其他的 Web 资源(如:JavaScript⽂件、图像⽂件、CSS ⽂件等),每遇到这样⼀个 Web 资源,浏览器就会᯿新建⽴⼀个 HTTP 会话。

⽽从 HTTP/1.1 起,默认使⽤⻓连接,⽤以保持连接特性。使⽤⻓连接的 HTTP 协议,会在响应头加⼊这⾏代码:

Connection:keep-alive

在使⽤⻓连接的情况下,当⼀个⽹⻚打开完成后,客户端和服务器之间⽤于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使⽤这⼀条已经建⽴的连接。

Keep-Alive 不会永久保持连接,它有⼀个保持时间,可以在不同的服务器软件(如:Apache)中设定这个时间。实现⻓连接需要客户端和服务端都⽀持⻓连接。

**36、HTTPS **的⼯作过程?

1、 客户端发送⾃⼰⽀持的加密规则给服务器,代表告诉服务器要进⾏连接了;

2、 服务器从中选出⼀套加密算法和 hash 算法以及⾃⼰的身份信息(地址等)以证书的形式发送给浏览器,证书中包含服务器信息,加密公钥,证书的办法机构;

3、客户端收到⽹站的证书之后要做下⾯的事情:

验证证书的合法性;

果验证通过证书,浏览器会⽣成⼀串随机数,并⽤证书中的公钥进⾏加密;

⽤约定好的 hash 算法计算握⼿消息,然后⽤⽣成的密钥进⾏加密,然后⼀起发送给服务器。

4、服务器接收到客户端传送来的信息,要做下⾯的事情:

4.1 ⽤私钥解析出密码,⽤密码解析握⼿消息,验证 hash 值是否和浏览器发来的⼀致;

4.2 使⽤密钥加密消息;

5、如果计算法 hash 值⼀致,握⼿成功。

**37、HTTP **和 HTTPS 的区别?

  1. 开销:HTTPS 协议需要到 CA 申请证书,⼀般免费证书很少,需要交费;
  2. 资源消耗:HTTP 是超⽂本传输协议,信息是明⽂传输,HTTPS 则是具有安全性的 ssl 加密传输协议,需要消耗更多的 CPU 和内存资源;
  3. 端⼝不同:HTTP 和 HTTPS 使⽤的是完全不同的连接⽅式,⽤的端⼝也不⼀样,前者是 80,后者是 443;
  4. 安全性:HTTP 的连接很简单,是⽆状态的;HTTPS 协议是由 TSL+HTTP 协议构建的可进⾏加密传输、身份认证的⽹络协议,⽐ HTTP 协议安全

**38、HTTPS **的优缺点?

优点:

  1. 使⽤ HTTPS 协议可认证⽤户和服务器,确保数据发送到正确的客户机和服务器;
  2. HTTPS 协议是由 SSL + HTTP 协议构建的可进⾏加密传输、身份认证的⽹络协议,要⽐ HTTP 协议安全,可防⽌数据在传输过程中不被窃取、改变,确保数据的完整性;Connection:keep-alive
    1. HTTPS 是现⾏架构下最安全的解决⽅案,虽然不是绝对安全,但它⼤幅增加了中间⼈攻击的成本。

缺点:

  1. HTTPS 协议握⼿阶段⽐较费时,会使⻚⾯的加载时间延⻓近 50%,增加 10% 到 20% 的耗电;
  2. HTTPS 连接缓存不如 HTTP ⾼效,会增加数据开销和功耗,甚⾄已有的安全措施也会因此⽽受到影响;
  3. SSL 证书需要钱,功能越强⼤的证书费⽤越⾼,个⼈⽹站、⼩⽹站没有必要⼀般不会⽤;
  4. SSL 证书通常需要绑定 IP,不能在同⼀ IP 上绑定多个域名,IPv4 资源不可能⽀撑这个消耗;
  5. HTTPS 协议的加密范围也⽐较有限,在⿊客攻击、拒绝服务攻击、服务器劫持等⽅⾯⼏乎起不到什么作⽤。最关键的,SSL 证书的信⽤链体系并不安全,特别是在某些国家可以控制 CA 根证书的情况下,中间⼈攻击⼀样可⾏。

39、什么是数字签名?

为了避免数据在传输过程中被替换,⽐如⿊客修改了你的报⽂内容,但是你并不知道,所以我们让发送端做⼀个数字签名,把数据的摘要消息进⾏⼀个加密,⽐如 MD5,得到⼀个签名,和数据⼀起发送。然后接收端把数据摘要进⾏ MD5 加密,如果和签名⼀样,则说明数据确实是真的。

40、什么是数字证书

对称加密中,双⽅使⽤公钥进⾏解密。虽然数字签名可以保证数据不被替换,但是数据是由公钥加密的,如果公钥也被替换,则仍然可以伪造数据,因为⽤户不知道对⽅提供的公钥其实是假的。所以为了保证发送⽅的公钥是真的,CA 证书机构会负责颁发⼀个证书,⾥⾯的公钥保证是真的,⽤户请求服务器时,服务器将证书发给⽤户,这个证书是经由系统内置证书的备案的

操作系统

1、简单说下你对并发和并⾏的理解?

  1. 并⾏是指两个或者多个事件在同⼀时刻发⽣;⽽并发是指两个或多个事件在同⼀时间间隔发⽣;
  2. 并⾏是在不同实体上的多个事件,并发是在同⼀实体上的多个事件;

2、同步、异步、阻塞、⾮阻塞的概念

同步:当⼀个同步调⽤发出后,调⽤者要⼀直等待返回结果。通知后,才能进⾏后续的执⾏。

异步:当⼀个异步过程调⽤发出后,调⽤者不能⽴刻得到返回结果。实际处理这个调⽤的部件在完成后,通过状态、通知和回调来通知调⽤者。

阻塞:是指调⽤结果返回前,当前线程会被挂起,即阻塞。

⾮阻塞:是指即使调⽤结果没返回,也不会阻塞当前线程。

3、进程和线程的基本概念

进程:进程是系统进⾏资源分配和调度的⼀个独⽴单位,是系统中的并发执⾏的单位。

线程:线程是进程的⼀个实体,也是 CPU 调度和分派的基本单位,它是⽐进程更⼩的能独⽴运⾏的基本单位,有时⼜被称为轻权进程或轻量级进程。

4、进程与线程的区别?

  1. 进程是资源分配的最⼩单位,⽽线程是 CPU 调度的最⼩单位;
  2. 创建进程或撤销进程,系统都要为之分配或回收资源,操作系统开销远⼤于创建或撤销线程时的开销;
  3. 不同进程地址空间相互独⽴,同⼀进程内的线程共享同⼀地址空间。⼀个进程的线程在另⼀个进程内是不可⻅的;
  4. 进程间不会相互影响,⽽⼀个线程挂掉将可能导致整个进程挂掉;

5、为什么有了进程,还要有线程呢?

进程可以使多个程序并发执⾏,以提⾼资源的利⽤率和系统的吞吐量,但是其带来了⼀些缺点:

  1. 进程在同⼀时间只能⼲⼀件事情;
  2. 进程在执⾏的过程中如果阻塞,整个进程就会被挂起,即使进程中有些⼯作不依赖与等待的资源,仍然不会执⾏。基于以上的缺点,操作系统引⼊了⽐进程粒度更⼩的线程,作为并发执⾏的基本单位,从⽽减少程序在并发执⾏时所付出的时间和空间开销,提⾼并发性能。

6、进程的状态转换

进程包括三种状态:就绪态、运⾏态和阻塞态。

  1. 就绪 —> 执⾏:对就绪状态的进程,当进程调度程序按⼀种选定的策略从中选中⼀个就绪进程,为之分配了处理机后,该进程便由就绪状态变为执⾏状态;
  2. 执⾏ —> 阻塞:正在执⾏的进程因发⽣某等待事件⽽⽆法执⾏,则进程由执⾏状态变为阻塞状态,如进程提出输⼊/输出请求⽽变成等待外部设备传输信息的状态,进程申请资源(主存空间或外部设备)得不到满⾜时变成等待资源状态,进程运⾏中出现了故障(程序出错或主存储器读写错等)变成等待⼲预状态等等;
  3. 阻塞 —> 就绪:处于阻塞状态的进程,在其等待的事件已经发⽣,如输⼊/输出完成,资源得到满⾜或错误处理完毕时,处于等待状态的进程并不⻢上转⼊执⾏状态,⽽是先转⼊就绪状态,然后再由系统进程调度程序在适当的时候将该进程转为执⾏状态;
  4. 执⾏ —> 就绪:正在执⾏的进程,因时间⽚⽤完⽽被暂停执⾏,或在采⽤抢先式优先级调度算法的系统中,当有更⾼优先级的进程要运⾏⽽被迫让出处理机时,该进程便由执⾏状态转变为就绪状态。

7、进程间的通信⽅式有哪些?

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC 的⽅式通常有管道(包括⽆名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams 等。其中 Socket 和 Streams ⽀持不同主机上的两个进程 IPC。

管道

  1. 它是半双⼯的,具有固定的读端和写端;
  2. 它只能⽤于⽗⼦进程或者兄弟进程之间的进程的通信;
  3. 它可以看成是⼀种特殊的⽂件,对于它的读写也可以使⽤普通的 read、write 等函数。但是它不是普通的⽂件,并不属于其他任何⽂件系统,并且只存在于内存中。

命名管道

  1. FIFO 可以在⽆关的进程之间交换数据,与⽆名管道不同;
  2. FIFO 有路径名与之相关联,它以⼀种特殊设备⽂件形式存在于⽂件系统中。

消息队列

  1. 消息队列,是消息的链接表,存放在内核中。⼀个消息队列由⼀个标识符 ID 来标识;
  2. 消息队列是⾯向记录的,其中的消息具有特定的格式以及特定的优先级;
  3. 消息队列独⽴于发送与接收进程。进程终⽌时,消息队列及其内容并不会被删除;
  4. 消息队列可以实现消息的随机查询,消息不⼀定要以先进先出的次序读取,也可以按消息的类型读取。

信号量

  1. 信号量(semaphore)是⼀个计数器。⽤于实现进程间的互斥与同步,⽽不是⽤于存储进程间通信数据;
  2. 信号量⽤于进程间同步,若要在进程间传递数据需要结合共享内存;
  3. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原⼦操作;4. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,⽽且可以加减任意正整数;
  4. ⽀持信号量组。

共享内存

  1. 共享内存(Shared Memory),指两个或多个进程共享⼀个给定的存储区;
  2. 共享内存是最快的⼀种 IPC,因为进程是直接对内存进⾏存取。

8 、进程的调度算法有哪些?

调度算法是指:根据系统的资源分配策略所规定的资源分配算法。常⽤的调度算法有:先来先服务调度算法、时间⽚轮转调度法、短作业优先调度算法、最短剩余时间优先、⾼响应⽐优先调度算法、优先级调度算法等等。

先来先服务调度算法

先来先服务调度算法是⼀种最简单的调度算法,也称为先进先出或严格排队⽅案。当每个进程就绪后,它加⼊就绪队列。当前正运⾏的进程停⽌执⾏,选择在就绪队列中存在时间最⻓的进程运⾏。该算法既可以⽤于作业调度,也可以⽤于进程调度。先来先去服务⽐较适合于常作业(进程),⽽不利于段作业(进程)。

时间⽚轮转调度算法

时间⽚轮转调度算法主要适⽤于分时系统。在这种算法中,系统将所有就绪进程按到达时间的先后次序排成⼀个队列,进程调度程序总是选择就绪队列中第⼀个进程执⾏,即先来先服务的原则,但仅能运⾏⼀个时间⽚。

短作业优先调度算法

短作业优先调度算法是指对短作业优先调度的算法,从后备队列中选择⼀个或若⼲个估计运⾏时间最短的作业,将它们调⼊内存运⾏。 短作业优先调度算法是⼀个⾮抢占策略,他的原则是下⼀次选择预计处理时间最短的进程,因此短进程将会越过⻓作业,跳⾄队列头。

最短剩余时间优先调度算法

最短剩余时间是针对最短进程优先增加了抢占机制的版本。在这种情况下,进程调度总是选择预期剩余时间最短的进程。当⼀个进程加⼊到就绪队列时,他可能⽐当前运⾏的进程具有更短的剩余时间,因此只要新进程就绪,调度程序就能可能抢占当前正在运⾏的进程。像最短进程优先⼀样,调度程序正在执⾏选择函数是必须有关于处理时间的估计,并且存在⻓进程饥饿的危险。

⾼响应⽐优先调度算法

⾼响应⽐优先调度算法主要⽤于作业调度,该算法是对 先来先服务调度算法和短作业优先调度算法的⼀种综合平衡,同时考虑每个作业的等待时间和估计的运⾏时间。在每次进⾏作业调度时,先计算后备作业队列中每个作业的响应⽐,从中选出响应⽐最⾼的作业投⼊运⾏。

优先级调度算法

优先级调度算法每次从后备作业队列中选择优先级最髙的⼀个或⼏个作业,将它们调⼊内存,分配必要的资源,创建进程并放⼊就绪队列。在进程调度中,优先级调度算法每次从就绪队列中选择优先级最⾼的进程,将处理机分配给它,使之投⼊运⾏。

9、什么是死锁?

死锁,是指多个进程在运⾏过程中因争夺资源⽽造成的⼀种僵局,当进程处于这种僵持状态时,若⽆外⼒作⽤,它们都将⽆法再向前推进。 如下图所示:如果此时有⼀个线程 A,已经持有了锁 A,但是试图获取锁 B,线程 B 持有锁 B,⽽试图获取锁 A,这种情况下就会产⽣死锁。

10、产⽣死锁的原因?

由于系统中存在⼀些不可剥夺资源,⽽当两个或两个以上进程占有⾃身资源,并请求对⽅资源时,会导致每个进程都⽆法向前推进,这就是死锁。

竞争资源

例如:系统中只有⼀台打印机,可供进程 A 使⽤,假定 A 已占⽤了打印机,若 B 继续要求打印机打印将被阻塞。

系统中的资源可以分为两类:

  1. 可剥夺资源:是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU 和主存均属于可剥夺性资源;
  2. 不可剥夺资源,当系统把这类资源分配给某进程后,再不能强⾏收回,只能在进程⽤完后⾃⾏释放,如磁带机、打印机等。

进程推进顺序不当

例如:进程 A 和 进程 B 互相等待对⽅的数据。

11、死锁产⽣的必要条件?

  1. 互斥条件:进程要求对所分配的资源进⾏排它性控制,即在⼀段时间内某资源仅为⼀进程所占⽤。

  2. 请求和保持条件:当进程因请求资源⽽阻塞时,对已获得的资源保持不放。

  3. 不剥夺条件:进程已获得的资源在未使⽤完之前,不能剥夺,只能在使⽤完时由⾃⼰释放。

  4. 环路等待条件:在发⽣死锁时,必然存在⼀个进程–资源的环形链。

    12、解决死锁的基本⽅法?

  5. 预防死锁

  6. 避免死锁

  7. 检测死锁

  8. 解除死锁

13、怎么预防死锁?

  1. 破坏请求条件:⼀次性分配所有资源,这样就不会再有请求了;
  2. 破坏请保持条件:只要有⼀个资源得不到分配,也不给这个进程分配其他的资源:
  3. 破坏不可剥夺条件:当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源;
  4. 破坏环路等待条件:系统给每类资源赋予⼀个编号,每⼀个进程按编号递增的顺序请求资源,释放则相反。

14、怎么避免死锁?

1. 安全状态

图 a 的第⼆列 Has 表示已拥有的资源数,第三列 Max 表示总共需要的资源数,Free 表示还有可以使⽤的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源(图 b),运⾏结束后释放 B,此时 Free 变为 5(图 c);接着以同样的⽅式运⾏ C 和 A,使得所有进程都能成功运⾏,因此可以称图 a 所示的状态时安全的。

定义:如果没有死锁发⽣,并且即使所有进程突然请求对资源的最⼤需求,也仍然存在某种调度次序能够使得每⼀个进程运⾏完毕,则称该状态是安全的。

安全状态的检测与死锁的检测类似,因为安全状态必须要求不能发⽣死锁。下⾯的银⾏家算法与死锁检测算法⾮常类似,可以结合着做参考对⽐。

2. 单个资源的银⾏家算法

⼀个⼩城镇的银⾏家,他向⼀群客户分别承诺了⼀定的贷款额度,算法要做的是判断对请求的满⾜是否会进⼊不安全状态,如果是,就拒绝请求;否则予以分配。上图 c 为不安全状态,因此算法会拒绝之前的请求,从⽽避免进⼊图 c 中的状态。

3. 多个资源的银⾏家算法

上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P

以及 A 分别表示:总资源、已分配资源以及可⽤资源,注意这三个为向量,⽽不是具体数值,例如 A=(1020),表

示 4 个资源分别还剩下 1/0/2/0。

检查⼀个状态是否安全的算法如下:

查找右边的矩阵是否存在⼀⾏⼩于等于向量 A。如果不存在这样的⾏,那么系统将会发⽣死锁,状态是不安全

的。

假若找到这样⼀⾏,将该进程标记为终⽌,并将其已分配资源加到 A 中。

᯿复以上两步,直到所有进程都标记为终⽌,则状态时安全的。

如果⼀个状态不是安全的,需要拒绝进⼊这个状态。

15、怎么解除死锁?

  1. 资源剥夺:挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他死锁进程(但应该防⽌被挂起的进程⻓时间得不到资源);
  2. 撤销进程:强制撤销部分、甚⾄全部死锁进程并剥夺这些进程的资源(撤销的原则可以按进程优先级和撤销进程代价的⾼低进⾏);
  3. 进程回退:让⼀个或多个进程回退到⾜以避免死锁的地步。进程回退时⾃愿释放资源⽽不是被剥夺。要求系统保持进程的历史信息,设置还原点。

16、什么是缓冲区溢出?有什么危害?

缓冲区为暂时置放输出或输⼊资料的内存。缓冲区溢出是指当计算机向缓冲区填充数据时超出了缓冲区本身的容量,溢出的数据覆盖在合法数据上。造成缓冲区溢出的主要原因是程序中没有仔细检查⽤户输⼊是否合理。计算机中,缓冲区溢出会造成的危害主要有以下两点:程序崩溃导致拒绝服务和跳转并且执⾏⼀段恶意代码。

17、分⻚与分段的区别?

  1. 段是信息的逻辑单位,它是根据⽤户的需要划分的,因此段对⽤户是可⻅的 ;⻚是信息的物理单位,是为了管理主存的⽅便⽽划分的,对⽤户是透明的;
  2. 段的⼤⼩不固定,有它所完成的功能决定;⻚⼤⼤⼩固定,由系统决定;
  3. 段向⽤户提供⼆维地址空间;⻚向⽤户提供的是⼀维地址空间;
  4. 段是信息的逻辑单位,便于存储保护和信息的共享,⻚的保护和共享受到限制。

18、物理地址、逻辑地址、虚拟内存的概念

  1. 物理地址:它是地址转换的最终地址,进程在运⾏时执⾏指令和访问数据最后都要通过物理地址从主存中存取,是内存单元真正的地址。
  2. 逻辑地址:是指计算机⽤户看到的地址。例如:当创建⼀个⻓度为 100 的整型数组时,操作系统返回⼀个逻辑上的连续空间:指针指向数组第⼀个元素的内存地址。由于整型元素的⼤⼩为 4 个字节,故第⼆个元素的地址时起始地址加 4,以此类推。事实上,逻辑地址并不⼀定是元素存储的真实地址,即数组元素的物理地址(在内存条中所处的位置),并⾮是连续的,只是操作系统通过地址映射,将逻辑地址映射成连续的,这样更符合⼈们的直观思维。
  3. 虚拟内存:是计算机系统内存管理的⼀种技术。它使得应⽤程序认为它拥有连续的可⽤的内存(⼀个连续完整的地址空间),⽽实际上,它通常是被分隔成多个物理内存碎⽚,还有部分暂时存储在外部磁盘存储器上,在需要时进⾏数据交换。

19、⻚⾯置换算法有哪些?

请求调⻚,也称按需调⻚,即对不在内存中的“⻚”,当进程执⾏时要⽤时才调⼊,否则有可能到程序结束时也不会调⼊。⽽内存中给⻚⾯留的位置是有限的,在内存中以帧为单位放置⻚⾯。为了防⽌请求调⻚的过程出现过多的内存⻚⾯错误(即需要的⻚⾯当前不在内存中,需要从硬盘中读数据,也即需要做⻚⾯的替换)⽽使得程序执⾏效率下降,我们需要设计⼀些⻚⾯置换算法,⻚⾯按照这些算法进⾏相互替换时,可以尽量达到较低的错误率。常⽤的⻚⾯置换算法如下:

先进先出置换算法(FIFO)

先进先出,即淘汰最早调⼊的⻚⾯。

最佳置换算法(OPT)

选未来最远将使⽤的⻚淘汰,是⼀种最优的⽅案,可以证明缺⻚数最⼩。

最近最久未使⽤(LRU)算法

即选择最近最久未使⽤的⻚⾯予以淘汰

时钟(Clock)置换算法

时钟置换算法也叫最近未⽤算法 NRU(Not RecentlyUsed)。该算法为每个⻚⾯设置⼀位访问位,将内存中的所有⻚⾯都通过链接指针链成⼀个循环队列。

20、谈谈你对动态链接库和静态链接库的理解?

静态链接就是在编译链接时直接将需要的执⾏代码拷⻉到调⽤处,优点就是在程序发布的时候就不需要的依赖库,也就是不再需要带着库⼀块发布,程序可以独⽴执⾏,但是体积可能会相对⼤⼀些。

动态链接就是在编译的时候不直接拷⻉可执⾏代码,⽽是通过记录⼀系列符号和参数,在程序运⾏或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运⾏到指定的代码时,去共享执⾏内存中已经加载的动态库可执⾏代码,最终达到运⾏时连接的⽬的。优点是多个程序可以共享同⼀段代码,⽽不需要在磁盘上存储多个拷⻉,缺点是由于是运⾏时加载,可能会影响程序的前期执⾏性能

21、外中断和异常有什么区别?

外中断是指由 CPU 执⾏指令以外的事件引起,如 I/O 完成中断,表示设备输⼊/输出处理已经完成,处理器能够发送下⼀个输⼊/输出请求。此外还有时钟中断、控制台中断等。

⽽异常时由 CPU 执⾏指令的内部事件引起,如⾮法操作码、地址越界、算术溢出等。

22、⼀个程序从开始运⾏到结束的完整过程,你能说出来多少?

四个过程:

(1)预编译 主要处理源代码⽂件中的以“#”开头的预编译指令。处理规则⻅下

1、删除所有的#define,展开所有的宏定义。

2、处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。

3、处理“#include”预编译指令,将⽂件内容替换到它的位置,这个过程是递归进⾏的,⽂件中包含其他 ⽂件。

4、删除所有的注释,“//”和“/**/”。

5、保留所有的#pragma 编译器指令,编译器需要⽤到他们,如:#pragma once 是为了防⽌有⽂件被᯿ 复引⽤。

6、添加⾏号和⽂件标识,便于编译时编译器产⽣调试⽤的⾏号信息,和编译时产⽣编译错误或警告是 能够显示⾏号。

(2)编译 把预编译之后⽣成的xxx.i或xxx.ii⽂件,进⾏⼀系列词法分析、语法分析、语义分析及优化后,⽣成相应

的汇编代码⽂件。

1、词法分析:利⽤类似于“有限状态机”的算法,将源代码程序输⼊到扫描机中,将其中的字符序列分割成⼀系列的记号。

2、语法分析:语法分析器对由扫描器产⽣的记号,进⾏语法分析,产⽣语法树。由语法分析器输出的语法树是⼀种以表达式为节点的树。

3、语义分析:语法分析器只是完成了对表达式语法层⾯的分析,语义分析器则对表达式是否有意义进⾏判断,其分析的语义是静态语义——在编译期能分期的语义,相对应的动态语义是在运⾏期才能确定的语义。

4、优化:源代码级别的⼀个优化过程。

5、⽬标代码⽣成:由代码⽣成器将中间代码转换成⽬标机器代码,⽣成⼀系列的代码序列——汇编语⾔表示。

6、⽬标代码优化:⽬标代码优化器对上述的⽬标机器代码进⾏优化:寻找合适的寻址⽅式、使⽤位移来替代乘法运算、删除多余的指令等。

(3)汇编

将汇编代码转变成机器可以执⾏的指令(机器码⽂件)。 汇编器的汇编过程相对于编译器来说更简单,没有复杂的语法,也没有语义,更不需要做指令优化,只是根据汇编指令和机器指令的对照表⼀⼀翻译过来,汇编过程有汇编器as完成。

经汇编之后,产⽣⽬标⽂件(与可执⾏⽂件格式⼏乎⼀样)xxx.o(Linux下)、xxx.obj(Windows下)。

(4)链接

将不同的源⽂件产⽣的⽬标⽂件进⾏链接,从⽽形成⼀个可以执⾏的程序。链接分为静态链接和动态链接:

1、静态链接: 函数和数据被编译进⼀个⼆进制⽂件。在使⽤静态库的情况下,在编译链接可执⾏⽂件时,链接器从库中复制这些函数和数据并把它们和应⽤程序的其它模块组合起来创建最终的可执⾏⽂件。 空间浪费:因为每个可执⾏程序中对所有需要的⽬标⽂件都要有⼀份副本,所以如果多个程序对同⼀个⽬标⽂件都有依赖,会出现同⼀个⽬标⽂件都在内存存在多个副本; 更新困难:每当库函数的代码修改了,这个时候就需要᯿新进⾏编译链接形成可执⾏程序。

运⾏速度快:但是静态链接的优点就是,在可执⾏程序中已经具备了所有执⾏程序所需要的任何东⻄,在执⾏的时候运⾏速度快。

2、动态链接: 动态链接的基本思想是把程序按照模块拆分成各个相对独⽴部分,在程序运⾏时才将它们链接在⼀起形成⼀个完整的程序,⽽不是像静态链接⼀样把所有程序模块都链接成⼀个单独的可执⾏⽂件。

共享库:就是即使需要每个程序都依赖同⼀个库,但是该库不会像静态链接那样在内存中存在多份副本,⽽是这多个程序在执⾏时共享同⼀份副本;

更新⽅便:更新时只需要替换原来的⽬标⽂件,⽽⽆需将所有的程序再᯿新链接⼀遍。当程序下⼀次运⾏时,新版本的⽬标⽂件会被⾃动加载到内存并且链接起来,程序就完成了升级的⽬标。

性能损耗:因为把链接推迟到了程序运⾏时,所以每次执⾏程序都需要进⾏链接,所以性能会有⼀定损失。

23、介绍⼀下⼏种典型的锁?

读写锁

多个读者可以同时进⾏读

写者必须互斥(只允许⼀个写者写,也不能读者写者同时进⾏)

写者优先于读者(⼀旦有写者,则后续读者必须等待,唤醒时优先考虑写者)

互斥锁

⼀次只能⼀个线程拥有互斥锁,其他线程只有等待

互斥锁是在抢锁失败的情况下主动放弃CPU进⼊睡眠状态直到锁的状态改变时再唤醒,⽽操作系统负责线程调度,为了实现锁的状态发⽣改变时唤醒阻塞的线程或者进程,需要把锁交给操作系统管理,所以互斥锁在加锁操作时涉及上下⽂的切换。互斥锁实际的效率还是可以让⼈接受的,加锁的时间⼤概100ns左右,⽽实际上互斥锁的⼀种可能的实现是先⾃旋⼀段时间,当⾃旋的时间超过阀值之后再将线程投⼊睡眠中,因此在并发运算中使⽤互斥锁(每次占⽤锁的时间很短)的效果可能不亚于使⽤⾃旋锁

条件变量

互斥锁⼀个明显的缺点是他只有两种状态:锁定和⾮锁定。⽽条件变量通过允许线程阻塞和等待另⼀个线程发送信号的⽅法弥补了互斥锁的不⾜,他常和互斥锁⼀起使⽤,以免出现竞态条件。当条件不满⾜时,线程往往解开相应的互斥锁并阻塞线程然后等待条件发⽣变化。⼀旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒⼀个或多个正被此条件变量阻塞的线程。总的来说互斥锁是线程间互斥的机制,条件变量则是同步机制。

⾃旋锁

如果进线程⽆法取得锁,进线程不会⽴刻放弃CPU时间⽚,⽽是⼀直循环尝试获取锁,直到获取为⽌。如果别的线程⻓时期占有锁,那么⾃旋就是在浪费CPU做⽆⽤功,但是⾃旋锁⼀般应⽤于加锁时间很短的场景,这个时候效率⽐较⾼。

24、什么是⽤户态和内核态

⽤户态和内核态是操作系统的两种运⾏状态。

内核态 :处于内核态的 CPU 可以访问任意的数据,包括外围设备,⽐如⽹卡、硬盘等,处于内核态的 CPU可以从⼀个程序切换到另外⼀个程序,并且占⽤ CPU 不会发⽣抢占情况,⼀般处于特权级 0 的状态我们称之为内核态。

⽤户态 :处于⽤户态的 CPU 只能受限的访问内存,并且不允许访问外围设备,⽤户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。

那么为什么要有⽤户态和内核态呢?

这个主要是访问能⼒的限制的考量,计算机中有⼀些⽐较危险的操作,⽐如设置时钟、内存清理,这些都需要在内核态下完成,如果随意进⾏这些操作,那你的系统得崩溃多少次。

25、⽤户态和内核态是如何切换的?

所有的⽤户进程都是运⾏在⽤户态的,但是我们上⾯也说了,⽤户程序的访问能⼒有限,⼀些⽐较᯿要的⽐如从硬盘读取数据,从键盘获取数据的操作则是内核态才能做的事情,⽽这些数据却⼜对⽤户程序来说⾮常᯿要。所以就涉及到两种模式下的转换,即⽤户态 -> 内核态 -> ⽤户态,⽽唯⼀能够做这些操作的只有 系统调⽤ ,⽽能够执⾏系统调⽤的就只有 操作系统 。

⼀般⽤户态 -> 内核态的转换我们都称之为 trap 进内核,也被称之为 陷阱指令(trap instruction) 。

他们的⼯作流程如下:

1、⾸先⽤户程序会调⽤ glibc 库,glibc 是⼀个标准库,同时也是⼀套核⼼库,库中定义了很多关键 API。

2、glibc 库知道针对不同体系结构调⽤ 系统调⽤ 的正确⽅法,它会根据体系结构应⽤程序的⼆进制接⼝设置⽤户进程传递的参数,来准备系统调⽤。

3、然后,glibc 库调⽤ 软件中断指令(SWI) ,这个指令通过更新 CPSR 寄存器将模式改为超级⽤户模式,然后跳转到地址 0x08 处。

4、到⽬前为⽌,整个过程仍处于⽤户态下,在执⾏ SWI 指令后,允许进程执⾏内核代码,MMU 现在允许内核虚拟内存访问

5、从地址 0x08 开始,进程执⾏加载并跳转到中断处理程序,这个程序就是 ARM 中的 vector_swi() 。

6、在 vector_swi() 处,从 SWI 指令中提取系统调⽤号 SCNO,然后使⽤ SCNO 作为系统调⽤表sys_call_table 的索引,调转到系统调⽤函数。

7、执⾏系统调⽤完成后,将还原⽤户模式寄存器,然后再以⽤户模式执⾏。

26、进程终⽌的⽅式

进程的终⽌

进程在创建之后,它就开始运⾏并做完成任务。然⽽,没有什么事⼉是永不停歇的,包括进程也⼀样。进程早晚会发⽣终⽌,但是通常是由于以下情况触发的

正常退出(⾃愿的)

错误退出(⾃愿的)

严᯿错误(⾮⾃愿的)被其他进程杀死(⾮⾃愿的)

正常退出

多数进程是由于完成了⼯作⽽终⽌。当编译器完成了所给定程序的编译之后,编译器会执⾏⼀个系统调⽤告诉操作系统它完成了⼯作。这个调⽤在 UNIX 中是 exit ,在 Windows 中是 ExitProcess 。⾯向屏幕中的软件也⽀持⾃愿终⽌操作。字处理软件、Internet 浏览器和类似的程序中总有⼀个供⽤户点击的图标或菜单项,⽤来通知进程删除它锁打开的任何临时⽂件,然后终⽌。

错误退出

进程发⽣终⽌的第⼆个原因是发现严᯿错误,例如,如果⽤户执⾏如下命令为了能够编译 foo.c 但是该⽂件不存在,于是编译器就会发出声明并退出。在给出了错误参数时,⾯向屏幕的交互式进程通常并不会直接退出,因为这从⽤户的⻆度来说并不合理,⽤户需要知道发⽣了什么并想要进⾏᯿试,所以这时候应⽤程序通常会弹出⼀个对话框告知⽤户发⽣了系统错误,是需要᯿试还是退出。

严重错误

进程终⽌的第三个原因是由进程引起的错误,通常是由于程序中的错误所导致的。例如,执⾏了⼀条⾮法指令,引⽤不存在的内存,或者除数是 0 等。在有些系统⽐如 UNIX 中,进程可以通知操作系统,它希望⾃⾏处理某种类型的错误,在这类错误中,进程会收到信号(中断),⽽不是在这类错误出现时直接终⽌进程。

被其他进程杀死

第四个终⽌进程的原因是,某个进程执⾏系统调⽤告诉操作系统杀死某个进程。在 UNIX 中,这个系统调⽤是kill。在 Win32 中对应的函数是 TerminateProcess (注意不是系统调⽤)。

27、 守护进程、僵⼫进程和孤⼉进程

守护进程

指在后台运⾏的,没有控制终端与之相连的进程。它独⽴于控制终端,周期性地执⾏某种任务。Linux的⼤多数服务器就是⽤守护进程的⽅式实现的,如web服务器进程http等

创建守护进程要点:

(1)让程序在后台执⾏。⽅法是调⽤fork()产⽣⼀个⼦进程,然后使⽗进程退出。

(2)调⽤setsid()创建⼀个新对话期。控制终端、登录会话和进程组通常是从⽗进程继承下来的,守护进程要摆

脱它们,不受它们的影响,⽅法是调⽤setsid()使进程成为⼀个会话组⻓。setsid()调⽤成功后,进程成为新

的会话组⻓和进程组⻓,并与原来的登录会话、进程组和控制终端脱离。

(3)禁⽌进程᯿新打开控制终端。经过以上步骤,进程已经成为⼀个⽆终端的会话组⻓,但是它可以᯿新申请打

开⼀个终端。为了避免这种情况发⽣,可以通过使进程不再是会话组⻓来实现。再⼀次通过fork()创建新的⼦进

程,使调⽤fork的进程退出。

(4)关闭不再需要的⽂件描述符。⼦进程从⽗进程继承打开的⽂件描述符。如不关闭,将会浪费系统资源,造成

进程所在的⽂件系统⽆法卸下以及引起⽆法预料的错误。⾸先获得最⾼⽂件描述符值,然后⽤⼀个循环程序,关闭

0到最⾼⽂件描述符值的所有⽂件描述符。

(5)将当前⽬录更改为根⽬录。

(6)⼦进程从⽗进程继承的⽂件创建屏蔽字可能会拒绝某些许可权。为防⽌这⼀点,使⽤unmask(0)将屏蔽字

清零。

(7)处理SIGCHLD信号。对于服务器进程,在请求到来时往往⽣成⼦进程处理请求。如果⼦进程等待⽗进程捕获状态,则⼦进程将成为僵⼫进程(zombie),从⽽占⽤系统资源。如果⽗进程等待⼦进程结束,将增加⽗进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。这样,⼦进程结束时不会产⽣僵⼫进程。

孤⼉进程

如果⽗进程先退出,⼦进程还没退出,那么⼦进程的⽗进程将变为init进程。(注:任何⼀个进程都必须有⽗进程)。⼀个⽗进程退出,⽽它的⼀个或多个⼦进程还在运⾏,那么那些⼦进程将成为孤⼉进程。孤⼉进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集⼯作。

僵⼫进程

如果⼦进程先退出,⽗进程还没退出,那么⼦进程必须等到⽗进程捕获到了⼦进程的退出状态才真正结束,否则这个时候⼦进程就成为僵⼫进程。

设置僵⼫进程的⽬的是维护⼦进程的信息,以便⽗进程在以后某个时候获取。这些信息⾄少包括进程ID,进程的终⽌状态,以及该进程使⽤的CPU时间,所以当终⽌⼦进程的⽗进程调⽤wait或waitpid时就可以得到这些信息。如果⼀个进程终⽌,⽽该进程有⼦进程处于僵⼫状态,那么它的所有僵⼫⼦进程的⽗进程ID将被᯿置为1(init进程)。继承这些⼦进程的init进程将清理它们(也就是说init进程将wait它们,从⽽去除它们的僵⼫状态)。

28、如何避免僵⼫进程?

1、通过signal(SIGCHLD, SIG_IGN)通知内核对⼦进程的结束不关⼼,由内核回收。如果不想让⽗进程挂起,可以在⽗进程中加⼊⼀条语句:signal(SIGCHLD,SIG_IGN);表示⽗进程忽略SIGCHLD信号,该信号是⼦进程退出的时候向⽗进程发送的。

2、⽗进程调⽤wait/waitpid等函数等待⼦进程结束,如果尚⽆⼦进程退出wait会导致⽗进程阻塞。waitpid可以通过传递WNOHANG使⽗进程不阻塞⽴即返回。

3、如果⽗进程很忙可以⽤signal注册信号处理函数,在信号处理函数调⽤wait/waitpid等待⼦进程退出。

4、通过两次调⽤fork。⽗进程⾸先调⽤fork创建⼀个⼦进程然后waitpid等待⼦进程退出,⼦进程再fork⼀个孙进程后退出。这样⼦进程退出后会被⽗进程等待回收,⽽对于孙⼦进程其⽗进程已经退出所以孙进程成为⼀个孤⼉进程,孤⼉进程由init进程接管,孙进程结束后,init会等待回收。

第⼀种⽅法忽略SIGCHLD信号,这常⽤于并发服务器的性能的⼀个技巧因为并发服务器常常fork很多⼦进程,⼦进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理⽅式设为忽略,可让内核把僵⼫⼦进程转交给init进程去处理,省去了⼤量僵⼫进程占⽤系统资源。

29、常⻅内存分配内存错误

(1)内存分配未成功,却使⽤了它。

编程新⼿常犯这种错误,因为他们没有意识到内存分配会不成功。常⽤解决办法是,在使⽤内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的⼊⼝处⽤assert(p!=NULL)进⾏检查。如果是⽤malloc或new来申请内存,应该⽤if(p==NULL) 或if(p!=NULL)进⾏防错处理。

(2)内存分配虽然成功,但是尚未初始化就引⽤它。犯这种错误主要有两个起因:⼀是没有初始化的观念;⼆是误以为内存的缺省初值全为零,导致引⽤初值错误(例如数组)。内存的缺省初值究竟是什么并没有统⼀的标准,尽管有些时候为零值,我们宁可信其⽆不可信其有。所以⽆论⽤何种⽅式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

(3)内存分配成功并且已经初始化,但操作越过了内存的边界。

例如在使⽤数组时经常发⽣下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组

操作越界。

(4)忘记了释放内存,造成内存泄露。

含有这种错误的函数每被调⽤⼀次就丢失⼀块内存。刚开始时系统的内存充⾜,你看不到错误。终有⼀次程序突然挂掉,系统出现提示:内存耗尽。动态内存的申请与释放必须配对,程序中malloc与free的使⽤次数⼀定要相同,否则肯定有错误(new/delete同理)。

(5)释放了内存却继续使⽤它。常⻅于以下有三种情况:

1、程序中的对象调⽤关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该᯿新设计数据结构,从根本上解决对象管理的混乱局⾯。

2、函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引⽤”,因为该内存在函数体结束时被⾃动销毁。

3、使⽤free或delete释放了内存后,没有将指针设置为NULL。导致产⽣“ᰀ指针”。

30、内存交换中,被换出的进程保存在哪⾥?

保存在磁盘中,也就是外存中。具有对换功能的操作系统中,通常把磁盘空间分为⽂件区和对换区两部分。⽂件区主要⽤于存放⽂件,主要追求存储空间的利⽤率,因此对⽂件区空间的管理采⽤离散分配⽅式;对换区空间只占磁盘空间的⼩部分,被换出的进程数据就存放在对换区。由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换⼊换出速度,因此通常对换区采⽤连续分配⽅式(学过⽂件管理章节后即可理解)。总之,对换区的I/O速度⽐⽂件区的更快。

31、原⼦操作的是如何实现的

**处理器使⽤基于对缓存加锁或总线加锁的⽅式来实现多处理器之间的原⼦操作。**⾸先处理器会⾃动保证基本的内存操作的原⼦性。处理器保证从系统内存中读取或者写⼊⼀个字节是原⼦的,意思是当⼀个处理器读取⼀个字节时,其他处理器不能访问这个字节的内存地址。Pentium 6和最新的处理器能⾃动保证单处理器对同⼀个缓存⾏⾥进⾏16/32/64位的操作是原⼦的,但是复杂的内存操作处理器是不能⾃动保证其原⼦性的,⽐如跨总线宽度、跨多个缓存⾏和跨⻚表的访问。但是,处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原⼦性。

(1)使⽤总线锁保证原⼦性 第⼀个机制是通过总线锁保证原⼦性。如果多个处理器同时对共享变量进⾏读改写操作(i++就是经典的读改写操作),那么共享变量就会被多个处理器同时进⾏操作,这样读改写操作就不是原⼦的,操作完之后共享变量的值会和期望的不⼀致。举个例⼦,如果i=1,我们进⾏两次i++操作,我们期望的结果是3,但是有可能结果是2,如图下图所示。

CPU1 CPU2

i=1 i=1 

i+1 i+1 

i=2 i=2

原因可能是多个处理器同时从各⾃的缓存中读取变量i,分别进⾏加1操作,然后分别写⼊系统内存中。那么,想要保证读改写共享变量的操作是原⼦的,就必须保证CPU1读改写共享变量的时候,CPU2不能操作缓存了该共享变量内存地址的缓存。

处理器使⽤总线锁就是来解决这个问题的。所谓总线锁就是使⽤处理器提供的⼀个LOCK#信号,当⼀个处理器在****总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占共享内存。

(2)使⽤缓存锁保证原⼦性 第⼆个机制是通过缓存锁定来保证原⼦性。在同⼀时刻,我们只需保证对某个内存地址的操作是原⼦性即可,但总线锁定把****CPU和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销⽐较⼤,⽬前处理器在某些场合下使⽤缓存锁定代替总线锁定来进⾏优化。

频繁使⽤的内存会缓存在处理器的L1、L2和L3⾼速缓存⾥,那么原⼦操作就可以直接在处理器内部缓存中进⾏,并不需要声明总线锁,在Pentium 6和⽬前的处理器中可以使⽤“缓存锁定”的⽅式来实现复杂的原⼦性。

所谓“缓存锁定”是指内存区域如果被缓存在处理器的缓存⾏中,并且在Lock操作期间被锁定,那么当它执⾏锁操作回写到内存时,处理器不在总线上声⾔LOCK#信号,⽽是修改内部的内存地址,并允许它的缓存⼀致性机制来保证操作的原⼦性,因为缓存⼀致性机制会阻⽌同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存⾏的数据时,会使缓存⾏⽆效,在如上图所示的例⼦中,当CPU1修改缓存⾏中的i时使⽤了缓存锁*定,那么CPU2就不能使⽤同时缓存i的缓存⾏。

但是有两种情况下处理器不会使⽤缓存锁定。 第⼀种情况是:当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存⾏(cache line)时,则处理器会调⽤总线锁定。 第⼆种情况是:有些处理器不⽀持缓存锁定。对于Intel 486和Pentium处理器,就算锁定的内存区域在处理器的缓存⾏中也会调⽤总线锁定。

32、抖动你知道是什么吗?它也叫颠簸现象

刚刚换出的⻚⾯⻢上⼜要换⼊内存,刚刚换⼊的⻚⾯⻢上⼜要换出外存,这种频繁的⻚⾯调度⾏为称为抖动,或颠簸。产⽣抖动的主要原因是进程频繁访问的⻚⾯数⽬⾼于可⽤的物理块数(分配给进程的物理块不够)

为进程分配的物理块太少,会使进程发⽣抖动现象。为进程分配的物理块太多,⼜会降低系统整体的并发度,降低某些资源的利⽤率 为了研究为应该为每个进程分配多少个物理块,Denning 提出了进程⼯作集” 的概念

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值