Java面试

 Java基础

优秀面试题

代码随想录

Java 面试指南 | JavaGuide

Java超高频面试题汇总!_笔经面经_牛客网

离职在家学习Java的第一天

常见概念与常识

1.JDK、JRE、JVM的关系(⭐⭐⭐⭐⭐)

(1) JDK(Java Development Kit)是Java开发工具包,包括了JRE以及用于Java开发的工具,如编译器(javac)、调试器(jdb)、打包工具(jar)等。

(2) JRE(Java Runtime Environment)是Java运行环境,包括了JVM以及Java程序运行所需的类库等。

(3) JVM(Java Virtual Machine)是运行Java字节码的虚拟机。

3.Java语言有哪些特点 (已面)(⭐⭐⭐⭐⭐)

(1)平台无关性( Java 虚拟机实现平台无关性)

(2)支持多线程

(3)面向对象(封装,继承,多态)

(4)支持网络编程并且很方便

(5)安全性和可靠性

(6)解释与编译并存

5.Java和C++的区别

虽然,Java 和 C++ 都是面向对象的语言,都支持封装、继承和多态,但是,它们还是有挺多不相同的地方:

  • Java 不提供指针来直接访问内存,程序内存更加安全
  • Java 的类是单继承的,C++ 支持多重继承;
  • Java 有自动垃圾回收机制(GC),无需手动释放内存。
  • C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载、

6.字符常量和字符串常量的区别?

  1. 形式 : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符。
  2. 含义 : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)。
  3. 占内存大小 :字符常量只占 2 个字节; 字符串常量占若干个字节。

7.静态方法为什么不能调用非静态成员?

  1. 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
  2. 在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。

8.静态方法和实例方法有何不同?

java静态方法和非静态方法的区别是什么-Java基础-PHP中文网

9.重载和重写的区别(已面)(⭐⭐⭐⭐⭐)

        重载

        发生在同一个类中,参数必须不同

方法的重写要遵循“两同两小一大”

  • “两同”即方法名相同、形参列表相同;
  • “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
  • “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。

11.Java 中的几种基本数据类型了解么?(已面)(⭐⭐⭐⭐⭐)

Java 中有 8 种基本数据类型,分别为:

整数型:byteshortintlong

浮点型:floatdouble

字符型:char

布尔型:boolean

这 8 种基本数据类型的默认值以及所占空间的大小如下:

 基本数据类型的取值范围

Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析。

float直接赋值时必须在数字后加上f或F,否则将作为double解析。

double赋值时可以加d或D也可以不加。

12.包装类型的常量池技术了解么?

java中int与Integer用==比较详解_integer和int用==比较-CSDN博客

包装类型的常量池技术了解么?

13.自动装箱与拆箱了解吗?原理是什么?

自动装箱与拆箱了解吗?原理是什么?

什么是自动拆装箱?

  • 装箱:将基本类型用它们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型;

装箱其实就是调用了 包装类的valueOf()方法,拆箱其实就是调用了 xxxValue()方法

因此,

  • Integer i = 10 等价于 Integer i = Integer.valueOf(10)
  • int n = i 等价于 int n = i.intValue();

注意:如果频繁拆装箱的话,也会严重影响系统的性能。

14.成员变量和局部变量的区别?(⭐⭐⭐⭐⭐)

  • 语法形式 :成员变量是属于实例对象的,而局部变量是属于代码块或方法的;
  • 成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制       修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
  • 生存时间 :从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
  • 默认值 :从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值,而局部变量则不会自动赋值。

15.对象的相等与指向他们的引用相等,两者有什么不同?

  • 对象的相等一般比较的是内存中存放的内容是否相等。
  • 引用相等一般比较的是他们指向的内存地址是否相等。

16.构造方法有哪些特点,能否被override?(⭐⭐⭐⭐⭐)

构造方法特点如下:

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

构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。

17.面向对象三大特征?(已面)(⭐⭐⭐⭐⭐)

(封装/继承/多态)

        封装:封装是指把一个对象的属性私有化,不允许外部对象直接访问这些私有属性,但是可以提供一些可以被外部访问的方法来操作这些属性。
        继承:继承是子类继承父类的非私有属性和方法。子类可以拥有自己属性和方法,即子类可以对父类进行扩展。一个子类只能拥有一个父类,子类可以对父类的方法进行重写。
        多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。

  • 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
  • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
  • 多态不能调用“只在子类存在但在父类不存在”的方法;
  • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

        多态有哪些实现方式?

18.值传递和引用传递的区别?

Java 值传递详解 | JavaGuide

19.接口和抽象类有什么共同点和区别? (⭐⭐⭐⭐⭐)

共同点 :

  • 都不能被实例化。
  • 都可以包含抽象方法。
  • 都可以有默认实现的方法(Java 8 可以用 default 关键在接口中定义默认方法)。

区别 :

  • 一个类只能继承一个抽象类,但是可以实现多个接口。
  • 接口中的成员变量只能是 public static final 类型的,而抽象类的成员变量可以是各种类型的。
  • 接口方法默认是public abstract(Java 8 可以用 default 关键在接口中定义默认方法),抽象类方法的访问修饰符默认是protected,抽象类可以有非抽象的方法。

20.深拷贝和浅拷贝(不是强引用和弱引用!)的区别了解吗?什么是引用拷贝?  (⭐⭐⭐⭐⭐)

深拷贝和浅拷贝区别了解吗?什么是引用拷贝?

21.static的作用?(⭐⭐⭐⭐⭐)

22.final的作用?可以修饰哪些东西?在什么情况下使用呢?(已面) (⭐⭐⭐⭐⭐)

答:1、被final修饰的类不可以被继承;2、被final修饰的方法不可以被重写;3、被final修饰的变量必须赋予初始值,并且不可以被改变。类,变量,方法。不希望当前修饰的元素被改动或重载的时候使用。


 

30.Java创建对象的几种方式?(已面) (⭐⭐⭐⭐⭐)

java中提供了以下五种创建对象的方式:

java 创建对象的5中方式_constructor.newinstance-CSDN博客

  • new创建新对象
  • 通过反射机制 2、3属于反射机制
  • 采用clone机制
  • 通过序列化和反序列化机制
  • ioc:容器创建对象 (spring中的)

31.静态变量和实例变量的区别?(⭐⭐⭐⭐⭐)

 静态变量通过类名或者对象名访问。

32.(⭐⭐⭐⭐⭐)

33.Integer和int的区别?(⭐⭐⭐⭐⭐)

Java基础常见面试题总结(上) | JavaGuide

34.super

Java中super关键字及super()的使用_java super-CSDN博客

        如果构造方法的第一行代码不是 this() 和 super(),则系统会默认添加 super()。

        在继承关系中,由于在子类的构造方法中第一条语句默认为调用父类的无参构造方法(即默认为 super(),一般这行代码省略了)。所以当在父类中定义了有参构造方法,但是没有定义无参构造方法时,编译器会强制要求我们定义一个相同参数类型的构造方法。 super()只能出现在构造方法的第一行

作用:

1.使用super调用父类构造方法

2.调用父类的成员变量或者方法

this 关键字的用法:

  • this()只能出现在构造方法的第一行,通过当前的构造方法去调用“本类”中的对应的构造方法,目的是:代码复用。
  • this.属性名:表示当前对象的属性
  • this.方法名(参数):表示调用当前对象的方法

35.访问修饰符 (⭐⭐⭐⭐⭐)

 36.final   finally  finalize区别

37.float f=3.14正确吗?

【Java面试题】用float型定义变量:float = 3.14;,是否正确?_Jock.Liu的博客-CSDN博客_用float定义变量

40.Integer i=1与int i=1的区别?(⭐⭐⭐⭐⭐)

java中int与Integer用==比较详解_integer和int用==比较-CSDN博客

42.Integer和Long类型数据大小比较?

他们的equals方法都会先判断类型然后再比较大小,类型不同直接返回false

44.Integer跟int有什么关系?Integer a=1发生了什么?

Java中的常用类

Object

1.Object 类的常见方法有哪些?

Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:

public final native Class<?> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。

public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。

public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。

protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。

public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。

public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。

public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。

public final native void wait(long timeout) throws InterruptedException//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。

public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。

public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念

protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作

2、== 和 equals() 的区别 (⭐⭐⭐⭐⭐)

== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。

equals() 方法存在两种使用情况:

  • 类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Objectequals()方法。
  • 类重写了 equals()方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;

2.为什么要有hashCode?

为什么重写equals时必须重写hashcode方法

Java基础常见面试题总结(中) | JavaGuide

String

String、StringBuffer、StringBuilder 的区别?String 为什么是不可变的?(已面)

可变性

        String不可变,StringBuilder/StringBuffer可变

        简单的来说:

  • 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法,所以String对象是不可变的. 
  • String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];
	//...
}

  StringBuilderStringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 finalprivate 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。 

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    char[] value;
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
  	//...
}

线程安全性

   String 中的对象是不可变的,也就可以理解为常量,线程安全StringBuffer 对方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法加同步锁,所以是非线程安全的。

性能

        StringBuilder>StringBuffer>String

        每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用, StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险.

对于三者使用的总结:

  1. 操作少量的数据: 用 String
  2. 单线程操作大量数据: 用 StringBuilder
  3. 多线程操作大量数据: 用 StringBuffer

3.字符串拼接用“+” 还是 StringBuilder?(知道就行)

字符串拼接用“+” 还是 StringBuilder?

        Java 语言 “+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的元素符。

对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。

String str1 = "he";
String str2 = "llo";
String str3 = "world";
String str4 = str1 + str2 + str3;

4.String#equals() 和 Object#equals() 有何区别?

  String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。 Objectequals 方法是比较的对象的内存地址。

 5.String为什么是不可变的?(⭐⭐⭐⭐⭐)

为什么String要设计成不可变的?_铁锚的博客-CSDN博客_string为什么设计成不可变的

6.String s1 = new String("abc");这句话创建了几个字符串对象?

String s1 = new String("abc");这句话创建了几个字符串对象?

7.String类的常见⽅法有哪些?

八股文第五版本

反射

2020 最新Java反射面试题_一枚小小菜鸟的博客-CSDN博客_java反射面试题

f​​​​​​Java基础之—反射(非常重要)_敬业小码哥的博客-CSDN博客_jvav

1.反射的作用和机制,介绍(已面) (⭐⭐⭐⭐⭐)

        反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

2.Java反射创建对象效率高还是通过new创建对象的效率高?

        通过new创建对象的效率比较高。通过反射时,先找查找类资源,使用类加载器创建,过程比较繁琐,所以效率较低。

3.反射的应用场景?

        Spring/Spring Boot、MyBatis 等等框架中都⼤量使⽤了反射机制。这些框架中也⼤量使⽤了动态代理,⽽动态代理的实现也依赖反射。

        jdbc就是典型的反射

        Class.forName('com.mysql.jdbc.Driver.class');//加载MySQL的驱动类

4.反射的优缺点?

优点:

1)代码更加灵活

2)为各种框架提供开箱即用的功能提供了便利

缺点:

1)使用反射性能稍差。

2)增加了安全问题,比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)

5.获取Class的四种方式?

Java 反射机制详解 | JavaGuide

6.利用反射动态创建对象实例?

7.反射使用步骤?

(1)获取想要操作的类的 Class 对象。

(2)通过Class对象操作组件。获取构造器(Constructor): class.getConstructor,获取方法(Method):Class.getMethod,获取字段(Field):class.getField

(3)操作组件实例。创建对象实例constructor.newInstance,调用方法,访问/修改字段

2020 最新Java反射面试题_java 反射实例化工具类 2020-CSDN博客

异常

 

 1.Exception和Error有什么区别?

        Exception和Error都有一个父类 Throwable

  • Exception :程序可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • ErrorError 是程序无法处理的错误 ,我们没办法通过 catch 来进行捕获 。例如JVM运行错误(Virtual MachineError)、栈溢出错误、类定义错误(NoClassDefFoundError)等 。错误发生时,JVM一般会选择终止线程。

2.Checked Exception 和 Unchecked Exception 有什么区别?

        Checked Exception 即受检查异常,Java 代码在编译过程中,如果受检查异常没有被 catch/throw 处理的话,就没办法通过编译 。

        除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。(常见的受检查异常有: IOException、FileNotFoundException、ClassNotFoundException 、SQLException...。)

Unchecked Exception不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。

   RuntimeException 及其子类都统称为非受检查异常,例如:NullPointerExceptionNumberFormatException(字符串转换为数字)、ArrayIndexOutOfBoundsException(数组越界)、ClassCastException(类型转换异常)、ArithmeticException(算术错误)等。

3.Throwable类常用方法有哪些?

  • String getMessage(): 返回异常发生时的简要描述
  • String toString(): 返回异常发生时的详细信息
  • String getLocalizedMessage(): 返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
  • void printStackTrace(): 在控制台上打印 Throwable 对象封装的异常信息

4.try-catch-finally 如何使用?

  • try块:执行可能产生异常的代码。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
  • catch块: 用于捕获并处理try 语句块产生的异常。
  • finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句不会被执行。

finally中的不一定会执行的!

就比如说 finally 之前虚拟机被终止运行的话,finally 中的代码就不会被执行。

在以下 3 种特殊情况下,finally 块的代码也不会被执行:

  1. 程序所在的线程死亡。
  2. 关闭 CPU。
  3. 虚拟机终止运行.
  4. 在 try 块中发⽣了死循环.

5.throw和throws的区别?

可以看看

Throw和Throws的区别_思远:的博客-CSDN博客_throw throws区别

I/O

1.IO流基本认识

IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。

Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

2.如何读取文件,并写入到另外一个路径(已面)

3.fileReader和FileInputStream有什么区别?

`FileReader` 和 `FileInputStream` 是 Java 中用于读取文件的两个不同类,它们分别属于字符流(Character Stream)和字节流(Byte Stream)体系。

1. **FileReader**:
   - 类型:`FileReader` 是 `java.io.Reader` 抽象类的子类。
   - 功能:专门用来读取字符数据,即文本文件。它以字符为单位从文件中读取数据,默认使用系统默认的字符集将字节转换成字符。
   - 用法:通常用于处理纯文本文件,提供诸如 `read()`、`read(char[] cbuf)` 等方法直接读取字符数据。

2. **FileInputStream**:
   - 类型:`FileInputStream` 是 `java.io.InputStream` 抽象类的子类。
   - 功能:用于读取任何类型的二进制数据,包括文本文件和其他非文本格式的数据。它以字节为单位读取原始数据,并不关心数据的实际含义或字符编码。
   - 用法:适合处理任何形式的文件,尤其是二进制文件,例如图片、音频、视频等,也适用于需要逐字节控制读取的文本文件,提供如 `read()`、`read(byte[] b)` 等方法读取原始字节数据。

总结起来,`FileReader` 更适合于简单地读取已知编码的文本文件,而 `FileInputStream` 则提供了更底层的原始数据访问能力,对于需要对二进制内容进行操作或者需要自行处理文本编码的情况更为灵活。

4.IO模型

Java IO 模型详解 | JavaGuide

八股文第五版

5.BIO/NIO/AIO的区别

        八股文第五版本

6.Java序列化和反序列化介绍

        如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。

简单来说:

  • 序列化:将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

        若对象要⽀持序列化机制,则它的类需要实现Serializable接⼝,该接⼝是⼀个标记接⼝,它没有提供任何⽅法,只 是标明该类是可以序列化的,Java的很多类已经实现了Serializable接⼝,如包装类、String、Date等。

        若要实现序列化,则需要使⽤对象流ObjectInputStream和ObjectOutputStream。其中,在序列化时需要调⽤ ObjectOutputStream对象的writeObject()⽅法,以输出对象序列。在反序列化时需要调⽤ObjectInputStream对 象的readObject()⽅法,将对象序列恢复为对象。

泛型

1.泛型是什么?有什么好处?

        Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

        泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

2.泛型的使用

        八股文第五版

3.泛型擦除

        Java 泛型擦除是指在编译时期,Java 编译器会将泛型信息从泛型代码中移除,以便保持与 Java 早期版本的兼容性。这个过程称为类型擦除。例如List<String>在运行时仅用一个List来表示。

4.E V T K ? 这些是什么

答:

        E——Element 表示元素 特性是一种枚举T——Type 类,是指Java类型K—— Key 键V——Value 值?——在使用中表示不确定类型。

5.什么是泛型中的限定通配符和非限定通配符 ?

  这是另一个非常流行的Java泛型面试题。限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。更多信息请参阅我的文章泛型中限定通配符和非限定通配符之间的区别。

6.使用泛型的好处

1、类型安全。
​         把运行时期的问题提前到了编译期间,编译期间确定类型,保证类型安全,放的是什么,取的也是什么,不用担心抛出 ClassCastException 异常。

2、 提高代码可读性和可维护性。
        ​泛型可以增加代码的可读性,因为使用泛型可以清晰地指定代码的意图和目的。它还可以减少代码的冗余和重复,提高代码的可维护性。

3、避免了强制类型转换和重复代码
​ 泛型可以减少手动进行类型转换的需求,并减少因为不同类型需要编写类似的代码而导致的代码冗余(在没有指定类型是类型转换必须使用 instanceof 关键字来进行判定)。

4、允许库的设计者在实现时限制类型
        ​ 通过使用泛型,实现集合类的设计者可以指定可以使用的类型。这样可以在设计时就避免类被误用。例如ArrayList中T必须是引用类型,如果试图用基本类型会编译报错。

​         泛型的最大好处就是在编译期实现类型安全检查,这个好处远远大于泛型可能引入的复杂性。合理使用泛型可以在大幅度提高代码质量和健壮性的同时,减少运行时出现的ClassCastException。

7.你可以把List<String>传递给一个接受List<Object>参数的方法吗?

  对任何一个不太熟悉泛型的人来说,这个Java泛型题目看起来令人疑惑,因为乍看起来String是一种Object,所以List<String>应当可以用在需要List<Object>的地方,但是事实并非如此。真这样做的话会导致编译错误。如果你再深一步考虑,你会发现Java这样做是有意义的,因为List<Object>可以存储任何类型的对象包括String, Integer等等,而List<String>却只能用来存储Strings。

List<Object> objectList;
List<String> stringList;
     
objectList = stringList;  //compilation error incompatible types

集合

1.集合概览(已问)(⭐⭐⭐⭐⭐)

        Java 集合概览

2.说说 List, Set, Queue, Map 四者的区别?(⭐⭐⭐⭐⭐)

  • List(对付顺序的好帮手): 存储的元素是有序的、可重复的。
  • Set(注重独一无二的性质): 存储的元素是不可重复的。
  • Queue(实现排队功能的叫号机): 元素先进先出,存储的元素是有序的、可重复的。
  • Map(用 key 来搜索的专家): 存储键值对(key-value),key 是无序的 不可重复的,value 是无序的、可重复的,每个键只能映射到一个值。

3.集合框架底层的数据结构总结

集合框架底层数据结构总结

4.为什么要使用集合?(集合的优点)(⭐⭐⭐⭐⭐)

集合和数组的区别

        存储多个类型相同的数据时, 我们可以采用数组来保存数据,但是数组存在一些弊端

             (1)数组的缺点是一旦声明之后,长度就不可变了;

             (2)数组存储的数据是有序的、可重复的,特点单一;

        但是集合提高了数据存储的灵活性,集合不仅可以用来存储不同类型不同数量的对象,决定元素的唯一性,具有丰富的插入删除元素的操作方法,还可以保存具有映射关系的数据。

5.如何选择集合?

如何选用集合

6.ArrayLIst和Array(数组)的区别?

ArrayList 和 Array(数组)的区别?

7.ArrayList和LinkedList的区别?(⭐⭐⭐⭐⭐)

Java集合常见面试题总结(上) | JavaGuide

        ArrayList在末尾进行插入或者删除元素时间复杂度为O(1),在其他位置进行插入删除时间复杂度为O(n);Linkedlist在首尾进行插入删除操作时间复杂度为O(1),在中间位置进行插入或者删除时间复杂度为O(n)。(已问过通义千问)

8.ArrayList的扩容机制?(⭐⭐⭐⭐⭐)

总的来说就是分两步:

1、扩容

​ 把原来的数组复制到另一个内存空间更大的数组中

2、添加元素

​ 把新元素添加到扩容以后的数组中

(1)ArrayList底层是使用Object[]数组存储数据的。

ArrayList定义了两个空数组,以及默认的初始容量10,以及实际存放数据的数组elementData

简单看看

//定义一个空数组以供使用
private static final Object[] EMPTY_ELEMENTDATA = {};
//也是一个空数组,跟上边的空数组不同之处在于,这个是在默认构造器时返回的,扩容时需要用到这个作判断,后面会讲到
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放数组中的元素,注意此变量是transient修饰的,不参与序列化
transient Object[] elementData;
//数组的长度,此参数是数组中实际的参数,区别于elementData.length,后边会说到
private int size;
 /**
     * 默认初始容量大小
     */
private static final int DEFAULT_CAPACITY = 10;

(2)ArrayList有三个构造函数,不同的构造函数会影响后边的扩容机制判断
1.默认的无参构造

public ArrayList() {
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

可以看到,调用此构造函数,elementData赋值为一个空数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA),此数组长度为0.

2.给定初始容量的构造函数

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

逻辑很简单,就是构造一个具有指定长度的空数组,当initialCapacity为0时,elementData赋值为一个空数组
3.包含特定集合元素的构造函数

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

把传入的集合转换为数组并赋值给elementData,然后判断elementData的大小,若为0,elementData赋值为一个空数组。

四.扩容机制
扩容开始于集合添加元素方法,添加元素有两种方法

一个方法是add方法传入对象,一个方法是add方法传入下标和对象。

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
public void add(int index, E element) {
    rangeCheckForAdd(index);

   ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    elementData[index] = element;
    size++;
}

可以看到两个方法都调用了ensureCapacityInternal(size + 1)方法,把数组长度加1以确保能存下下一个数据。

 //得到最小扩容量
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              // 获取默认的容量和传入参数的较大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);

size+1作为参数传入,被当成最小容量(minCapacity)。

如果在添加的时候数组是空的,那么最小容量(minCapacity)赋值为默认的初始容量10和minCapacity两者中的最大值;

然后调用ensureExplicitCapacity(minCapacity)方法,传入minCapacity.

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

通过这个地方是真正的增加长度,当需要最小的长度(也就是minCapacity)大于原来数组长度的时候就需要扩容了,相反的则不需要扩容

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

(要背就背这里)扩容会调用grow方法,将创建一个int newCapacity,值为elementData长度的1.5倍,然后检查newCapacity是否大于minCapacity(新容量是否大于最小需要容量),

若newCapacity小于minCapacity,newCapacity=minCapacity,将原来elementData数组中的数据copy到新数组中,elementData=新数组,长度为newCapacity,也就是elementData=Arrays.copyOf(elementData,newCapacity)语句。

9.Comparable 和 Comparator 的区别

Comparable自然排序,在实体类中实现,在java.lang包下

        Comparable 可以让实现它的类的对象进行比较,具体的比较规则是按照 compareTo 方法中的规则进行。

(选背:compareTo 方法的返回值有三种情况:

  • e1.compareTo(e2) > 0 即 e1 > e2
  • e1.compareTo(e2) = 0 即 e1 = e2
  • e1.compareTo(e2) < 0 即 e1 < e2)

(选背:实现了Comparable接口的List集合或者数组就能调用Collections.sort() 或者 Arrays.sort())

实现了 Comparable 接口的对象能够直接被用作 SortedMap (SortedSet) 的 key)

// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列
// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他
// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了
public  class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    /**
     * T重写compareTo方法实现按年龄来排序
     */
    @Override
    public int compareTo(Person o) {
        //下面的语句等价与 
        //return this.age-o.getAge(); 升序排序
        if (this.age > o.getAge()) {
            return 1;
        }
        if (this.age < o.getAge()) {
            return -1;
        }
        return 0;
    }
}
 public static void main(String[] args) {
        TreeMap<Person, String> pdata = new TreeMap<Person, String>();
        pdata.put(new Person("张三", 30), "zhangsan");
        pdata.put(new Person("李四", 20), "lisi");
        pdata.put(new Person("王五", 10), "wangwu");
        pdata.put(new Person("小红", 5), "xiaohong");
        // 得到key的值的同时得到key所对应的值
        Set<Person> keys = pdata.keySet();
        for (Person key : keys) {
            System.out.println(key.getAge() + "-" + key.getName());

        }
    }
5-小红
10-王五
20-李四
30-张三

Comparator定制排序

Comparator在Java.util包下面,Comparator则是在外部制定排序规则,然后作为排序策略参数传递给排序类的某个方法,

(选背:比如 Collections.sort(), Arrays.sort(), 或者一些内部有序的集合(比如 SortedSet,SortedMap 等)。)

使用方式主要分三步:

  1. new 一个Comparable接口(可以不背:里面使用的是匿名内部类),
    • 并且在大括号内重写compare(Object o1,Object o2) 方法中针对自定义类的排序规则
  2. 然后将 其 作为参数传递给 排序类的某个方法
  3. 向排序类中添加 compare 方法中使用的自定义类
ArrayList<Integer> arrayList = new ArrayList<Integer>();
        arrayList.add(-1);
        arrayList.add(3);
        arrayList.add(3);
        arrayList.add(-5);
        arrayList.add(7);
        arrayList.add(4);
        arrayList.add(-9);
        arrayList.add(-7);
        System.out.println("原始数组:");
        System.out.println(arrayList);
        // void reverse(List list):反转
        Collections.reverse(arrayList);
        System.out.println("Collections.reverse(arrayList):");
        System.out.println(arrayList);

        // void sort(List list),按自然排序的升序排序
        Collections.sort(arrayList);
        System.out.println("Collections.sort(arrayList):");
        System.out.println(arrayList);
        // 定制排序的用法
        Collections.sort(arrayList, new Comparator<Integer>() {

            @Override
            public int compare(Integer o1, Integer o2) {
                //等价于o2-o1   降序排序
                return o2.compareTo(o1);
                
                
                //return o1-o2;升序排序
            }
        });
        System.out.println("定制排序后:");
        System.out.println(arrayList);
原始数组:
[-1, 3, 3, -5, 7, 4, -9, -7]
Collections.reverse(arrayList):
[-7, -9, 4, 7, -5, 3, 3, -1]
Collections.sort(arrayList):
[-9, -7, -5, -1, 3, 3, 4, 7]
定制排序后:
[7, 4, 3, 3, -1, -5, -7, -9]

10.(Set)无序性和不可重复性的含义是什么?

1、什么是无序性?无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的。

2、什么是不可重复性?不可重复性是指添加的元素按照 equals()判断时 ,返回 false,需要同时重写 equals()方法和 HashCode()方法。

11.比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同?

  • HashSetLinkedHashSet 和 TreeSet 都是 Set 接口的实现类,都能保证元素唯一,并且都不是线程安全的。
  • 八股文第五版

Collection 子接口之 Queue

Queue

背1、2

Map接口

12.HashMap 和 Hashtable 的区别(⭐⭐⭐⭐⭐)

  1. 线程是否安全: HashMap 是非线程安全的,Hashtable 是线程安全的(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
  2. 效率: HashMap 要比 Hashtable 效率高一点。另外,Hashtable 基本被淘汰,不要在代码中使用它;
  3. 对 Null key 和 Null value 的支持: HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许有 null 键和 null 值,否则会抛出 NullPointerException
  4. 初始容量大小和每次扩充容量大小的不同: ① 创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。)
  5. 底层数据结构:JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8), 如果当前数组的长度小于64,那么会进行数组扩容,否则,会将链表转化为红黑树,以减少搜索时间。            Hashtable 没有这样的机制。Hashtable数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的

13.HashMap 和 HashSet 区别(⭐⭐⭐⭐⭐)

如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(不背:HashSet 的源码非常非常少,因为除了 clone()writeObject()readObject()是 HashSet 自己不得不实现之外),HashSet 的源码非常少,除了少量方法之外,其他方法都是直接调用 HashMap 中的方法。

HashMap 允许键和值都为 null。 HashSet 允许存储⼀个 null 元素

HashMap ⽤于存储键值对,其中每个键都唯⼀,每个键关联⼀个值。 HashSet ⽤于存储唯⼀的元素,不允许重复。

14.HashMap 和 TreeMap 区别
(1)HashMap通常比TreeMap快(树和哈希表的数据结构使然)

(2)一般使用HashMap,在需要排序的Map时候才用TreeMap.

(3)HashMap和TreeMap都继承自AbstractMap,但是TreeMap还实现了SortedMap接口,这个接口让TreeMap具有对集合中的元素根据键排序的能力。
(4)HashMap和TreeMap都是非线程安全

(5)HashMap底层数据结构是Object数组和链表或者红黑树,而TreeMap的底层数据结构是红黑树。

(6)HashMap 可以存储 null 的 key和value;TreeMap的键(key)不允许为null,TreeMap的值(value)可以为null

15.HashMap和ConcurrentHashMap的区别

  • HashMap不是线程安全的,而ConcurrentHashMap是线程安全的。
  • HashMap无内在锁机制,需要用户自行处理线程同步(如使用Collections.synchronizedMap()包装)。ConcurrentHashMap它使⽤了分段锁(Segment Locking)的机制,将整个数据结构分成多个段(Segment),每个段都有⾃⼰的锁,不同的线程可以同时访问不同的段;1.8版本之后摒弃了Segment结构,转而使用CAS和Synchronized来保证并发安全性
  • 在 HashMap 中,如果在迭代过程中有其他线程对其进⾏修改,可能抛出 ConcurrentModificationException 异常。 ConcurrentHashMap 允许在迭代时进⾏并发的插⼊和删除操作,⽽不会抛出异常。但是,它并不保证 迭代器的顺序,因为不同的段可能会以不同的顺序完成操作。
  • HashMap的key和value可以为null,ConcurrentHashMap不允许,否则会报运行时异常NullPointerException。
  • HashMap自JDK 1.2版本开始引入,ConcurrentHashMap是在JDK 1.5版本中引入

16.hashMap为什么是线程不安全的?如何实现线程安全?(⭐⭐⭐⭐⭐)

  • 并发修改:当⼀个线程进⾏写操作(插⼊、删除等)时,另⼀个线程进⾏读操作,可能会导致读取到不⼀致的 数据,甚⾄抛出 ConcurrentModificationException 异常。

  • 多线程下扩容死循环。JDK1.7中的HashMap使用头插法插入元素,在多线程的环境下,扩容的时候有可能导致环形链表的出现,形成死循环。因此JDK1.8使用尾插法插入元素,在扩容时会保持链表元素原本的顺序,不会出现环形链表的问题

  • 多线程的put可能导致元素的丢失。多线程同时执行put操作,如果计算出来的索引位置是相同的,那会造成前一个key被后一个key覆盖,从而导致元素的丢失。此问题在JDK1.7和JDK1.8中都存在

  • 如何实现线程安全:八股文第五版

17.HashMap扩容(⭐⭐⭐⭐⭐)

18.HashMap底层(⭐⭐⭐⭐⭐)

19.一般用什么作为HashMap的Key?

HashMap夺命14问,你能坚持到第几问?

Java集合常见面试题总结(下) | JavaGuide

20.map的遍历方式

Java集合常见面试题总结(下) | JavaGuide

21.HashMap 的长度为什么是 2 的幂次方

        为了能让 HashMap 存取高效,尽量较少哈希碰撞,也就是要尽量把数据分配均匀。

        Hash值是一个int值,范围从2^-31至2^31-1,

        所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。也就是hash%length。

        HashMap长度为2的幂次方时,hash%length等价于(length-1)&hash,采用二进制位操作 &,相对于%能够提高运算效率

22.HashMap的loadFactor 加载因子

        loadFactor 负载因子是控制数组存放数据的疏密程度,loadFactor 越趋近于 1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor 越小,也就是趋近于 0,数组中存放的数据(entry)也就越少,也就越稀疏。

        loadFactor 太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor 的默认值为 0.75f 是官方给出的一个比较好的临界值

给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。

当size>loadFactor*Capacity时,就会考虑对数组进行扩容。

23.HashMap的put方法

以jdk8为例,简要流程如下:

  1. 先判断数组是否为空,若数组为空则进⾏扩容

  2. 计算键的哈希值,计算键值对在数组中的索引;

  3. 如果没有哈希冲突直接直接插⼊数据

  4. 如果冲突了,且key已经存在,就覆盖掉value

  5. 如果冲突后是链表结构,就判断该链表是否大于8,如果大于8并且数组容量小于64,就进行扩容;如果链表节点数量大于8并且数组的容量大于64,则将这个结构转换成红黑树;否则,链表插入键值对,若key存在,就覆盖掉value。

  6. 如果冲突后,发现该节点是红黑树,就将这个节点挂在树上,如果键已存在,则会替换掉值。

  7. 如果数组中元素个数(size)超过threshold(阈值),则进⾏扩容操作。(图中缺失这句)

24.HashSet如何检查重复?

HashSet 如何检查重复?

        当你把对象加入HashSet时,HashSet 会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的 hashcode 值作比较,如果没有相符的 hashcodeHashSet 会假设对象没有重复出现,将对象加入。

        但是如果发现有相同 hashcode 值的对象,这时会调用equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让加入操作成功。

25.ConcurrentHashMap 线程安全的具体实现方式/底层具体实现  (如何实现线程安全)(⭐⭐⭐⭐⭐)

ConcurrentHashMap 线程安全的具体实现方式/底层具体实现

        分段锁(Segment Locking)的机制,将整个数据结构分成多个段(Segment),每个段都有⾃⼰的锁,不同的线程可以同时访问不同的段。

26.ConcurrentHashMap 和 Hashtable 的区别

ConcurrentHashMap 和 Hashtable 的区别

 ConcurrentHashMap 图在上面

27.遍历list和map和set的方式有哪些?

set除了不能fori遍历,不能自带迭代器遍历,其余和list一样。

public static void main(String[] args) {
    List<Integer> list = Lists.newArrayList();
    list.add(1);
    list.add(2);
    list.add(3);

    //方法一 普通for循环遍历
    System.out.println("普通for循环遍历");
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }

    //方法二 增强for (也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。
    //内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
    System.out.println("增强for");
    for (int i : list) {
        System.out.println(i);
    }

    //方法三 Lambda
    System.out.println("Lambda");
    list.forEach(e -> {
        System.out.println(e);
    });

    list.stream().forEach(e -> {
        System.out.println(e);
    });

    list.parallelStream().forEach(e -> {
        System.out.println(e); 
    });


    //方法四 迭代器遍历
    System.out.println("迭代器遍历");
    Iterator<Integer> it = list.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }

    //方法五 List集合自带迭代器
    System.out.println("List集合自带迭代器");
    ListIterator<Integer> listIterator = list.listIterator();
    while(listIterator.hasNext()){
        System.out.println(listIterator.next());
    }
}

28.数组和list之间怎么转换?

Java集合使用注意事项总结 | JavaGuide

集合转换为数组:

        s=list.toArray(new String[0]);

数组转换为集合:

        myList=Arrays.asList(myArray);

并发编程 

根据Java面试指南和代码随想录重写

看我的有道云笔记里面的多线程内容    多线程类型的题目!

1.进程线程是什么  两者区别

进程是程序的一次执行过程,是系统运行程序的基本单位。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。


线程
        进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

        与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

请简要描述线程与进程的关系,区别及优缺点?

2.为什么要用多线程?

    ① 为了更好的利用cpu的资源,如果只有一个线程,则第二个任务必须等到第一个任务结束后才能进行,如果使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待;

 ② 进程之间不能共享数据,线程可以;

 ③ 系统创建进程需要为该进程重新分配系统资源,创建线程代价比较小;

 ④ Java语言内置了多线程功能支持,简化了java多线程编程。

3.线程的生命周期?(⭐⭐⭐⭐⭐)

说说线程的生命周期和状态?

4.说说并发和并行的区别?

并发与并行的区别

5.多线程的创建方式?(⭐⭐⭐⭐⭐)

方式1:继承于Thread类
1.创建一个集成于Thread类的子类
2.重写Thread类的run()方法
3.创建Thread子类的对象
4.通过此对象调用start()方法

稍微看看:{

start与run方法的区别:
        start方法的作用:1.启动当前线程 2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
        调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。
        run方法的作用:在主线程中start()调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)

        }

例子:火车站买票

	package com.example.paoduantui.Thread;
	
	import android.view.Window;
	
	/**
	 *
	 * 创建三个窗口卖票,总票数为100张,使用继承自Thread方式
	 * 用静态变量保证三个线程的数据独一份
	 * 
	 * 存在线程的安全问题,有待解决
	 *
	 * */
	
	public class ThreadDemo extends Thread{
	
	    public static void main(String[] args){
	        window t1 = new window();
	        window t2 = new window();
	        window t3 = new window();
	
	        t1.setName("售票口1");
	        t2.setName("售票口2");
	        t3.setName("售票口3");
	
	        t1.start();
	        t2.start();
	        t3.start();
	    }
	
	}
	
	class window extends Thread{
	    private static int ticket = 100; //将其加载在类的静态区,所有线程共享该静态变量
	
	    @Override
	    public void run() {
	        while(true){
	            if(ticket>0){
	//                try {
	//                    sleep(100);
	//                } catch (InterruptedException e) {
	//                    e.printStackTrace();
	//                }
	                System.out.println(getName()+"当前售出第"+ticket+"张票");
	                ticket--;
	            }else{
	                break;
	            }
	        }
	    }
	}

方式2:实现Runable接口方式
1.创建一个实现了Runable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()

	package com.example.paoduantui.Thread;
	
	public class ThreadDemo01 {
	    
	    public static  void main(String[] args){
	        window1 w = new window1();
	        
	        //虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票
	        
	        Thread t1=new Thread(w);
	        Thread t2=new Thread(w);
	        Thread t3=new Thread(w);
	
	        t1.setName("窗口1");
	        t2.setName("窗口2");
	        t3.setName("窗口3");
	        
	        t1.start();
	        t2.start();
	        t3.start();
	    }
	}
	
	class window1 implements Runnable{
	    
	    private int ticket = 100;
	
	    @Override
	    public void run() {
	        while(true){
	            if(ticket>0){
	//                try {
	//                    sleep(100);
	//                } catch (InterruptedException e) {
	//                    e.printStackTrace();
	//                }
	                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
	                ticket--;
	            }else{
	                break;
	            }
	        }
	    }
	}

新增的两种创建多线程方式
实现callable接口方式:
与使用runnable方式相比,callable功能更强大些:
runnable重写的run方法不如callable的call方法强大,call方法可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果

package com.example.paoduantui.Thread;


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 创建线程的方式三:实现callable接口。---JDK 5.0新增
 *是否多线程?否,就一个线程
 *
 * 比runable多一个FutureTask类,用来接收call方法的返回值。
 * 适用于需要从线程中接收返回值的形式
 * 
 * //callable实现新建线程的步骤:
 * 1.创建一个实现callable的实现类
 * 2.实现call方法,将此线程需要执行的操作声明在call()中
 * 3.创建callable实现类的对象
 * 4.将callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
 * 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法启动(通过FutureTask的对象调用方法get获取线程中的call的返回值)
 * 
 * */


//实现callable接口的call方法
class NumThread implements Callable{

    private int sum=0;//

    //可以抛出异常
    @Override
    public Object call() throws Exception {
        for(int i = 0;i<=100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName()+":"+i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {

    public static void main(String[] args){
        //new一个实现callable接口的对象
        NumThread numThread = new NumThread();

        //通过futureTask对象的get方法来接收futureTask的值
        FutureTask futureTask = new FutureTask(numThread);

        Thread t1 = new Thread(futureTask);
        t1.setName("线程1");
        t1.start();

        try {
            //get返回值即为FutureTask构造器参数callable实现类重写的call的返回值
           Object sum = futureTask.get();
           System.out.println(Thread.currentThread().getName()+":"+sum);
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程池方式

 代码如下:

package com.example.paoduantui.Thread;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 创建线程的方式四:使用线程池(批量使用线程)
 *1.需要创建实现runnable或者callable接口方式的对象
 * 2.创建executorservice线程池
 * 3.将创建好的实现了runnable接口类的对象放入executorService对象的execute方法中执行。
 * 4.关闭线程池
 *
 * */

class NumberThread implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i<=100;i++){
            if (i % 2 ==0 )
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i<100; i++){
            if(i%2==1){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args){

        //创建固定线程个数为十个的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //new一个Runnable接口的对象
        NumberThread number = new NumberThread();
        NumberThread1 number1 = new NumberThread1();

        //执行线程,最多十个
        executorService.execute(number1);
        executorService.execute(number);//适合适用于Runnable

        //executorService.submit();//适合使用于Callable


        //关闭线程池
        executorService.shutdown();
    }

}

Runable和Callable有什么区别?(⭐⭐⭐⭐⭐)

启动一个线程用run还是start?(⭐⭐⭐⭐⭐)

6.什么是上下文切换?

Java并发常见面试题总结(上) | JavaGuide

7.什么是线程死锁?如何预防和避免线程死锁?(⭐⭐⭐⭐⭐)

        每个线程都持有⼀些资源,并且正在等待获取其他线程持有的资源。由于每个线程都在等待,没有⼀个线程能够继续执⾏,这样整个程序就被阻塞了

Java并发常见面试题总结(上) | JavaGuide

8.说说 sleep() 方法和 wait() 方法区别和共同点?(⭐⭐⭐⭐⭐)

Java并发常见面试题总结(上) | JavaGuide

9.为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

   调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

可以直接调用 Thread 类的 run 方法吗?

10.讲一下JMM(JAVA内存模型)(⭐⭐⭐⭐⭐)

        JMM规定了所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的本地内存(Working Memory),线程的本地内存中保存了该线程使用到的变量的主内存的副本拷贝

        线程对变量的所有操作(读取、赋值等)都必须在本地内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有本地内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。

        不同的线程之间也无法直接访问对方本地内存中的变量,线程之间值的传递都需要通过主内存来完成

        这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。

要解决这个问题,就需要把变量声明为 volatile ,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。

所以,volatile 关键字 除了防止 JVM 的指令重排 ,还有一个重要的作用就是保证变量的可见性。

11.并发编程的三个特性?(在 Java 程序中怎么保证多线程的运行安全?)(⭐⭐⭐⭐⭐)

  1. 原子性 : 一次操作或者多次操作,要么所有的操作全部都得到执行并且不会受到任何因素的干扰,要么都不执行。(synchronized 可以保证代码片段的原子性。)
  2. 可见性 :当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。(volatile 关键字可以保证共享变量的可见性。)
  3. 有序性 :程序执行的顺序按照代码的先后顺序执行。(Java 在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序。volatile 关键字可以禁止指令进行重排序优化。)

12.volatile关键字(⭐⭐⭐⭐⭐)

        1.被volitale修饰的变量 保证可见性 和  防止重排序

        2. 增加了volitale的时候,

            2.1 的时候, 会把当前线程的共享变量的值刷新的主内存中

            2.2 的时候,当前线程的共享变量的值失效,只能从主内存读取

        3. (不会内存屏蔽就别背),通过内存屏蔽的方式防止重排序

        JMM内存模型

加分(可以不背):

volatile用于保证内存的可见性,可以将其看做是轻量级的锁,它具有如下的内存语义:

  • 写内存语义:当写一个volatile变量时,JMM会把该线程本地内存中的共享变量的值刷新到主内存中。
  • 读内存语义:当读一个volatile变量时,JMM会把该线程本地内存置为无效,使其从主内存中读取共享变量。

volatile的底层是采用内存屏障来实现的,就是在编译器生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。内存屏障就是一段与平台相关的代码,Java中的内存屏障代码都在Unsafe类中定义,共包含三个方法:LoadFence()、storeFence()、fullFence()。

volatile 关键字

13.说说synchronized和volatile的区别?

synchronized 和 volatile 有什么区别?

14.ThreadLocal简介(⭐⭐⭐⭐⭐)

   ThreadLocal(是Thread Local Variable,线程局部变量)类是Java为线程安全提供的一个工具类,代表一个线程局部变量把数据放在ThreadLocal中可以让每个线程创建一个该变量的副本,线程间可以独立地改变自己的副本,而不会和其他线程产生副本冲突,从而避免并发访问的线程安全问题,就像每个线程都完全拥有该变量一样。

     采用方法   

  • T get() 返回此线程局部变量的当前线程副本值
  • void remove() 删除此线程局部变量的当前线程的副本值
  • void set(T value) 设置此线程局部变量中当前线程副本值

17.ThreadLocal原理(底层结构)

ThreadLocal 原理了解吗?

this指当前ThreadLocal

在Thread类中

public class Thread implements Runnable {
    //......
    //与此线程有关的ThreadLocal值。由ThreadLocal类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;

}
  static class ThreadLocalMap {
 
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
 
        
    }

         可看出ThreadLocalMap是ThreadLocal的内部静态类,而它的构成主要是用Entry来保存数据 ,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。详细内容要大家自己去跟。

set方法

 public void set(T value) {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,
        //则直接更新要保存的变量值,否则创建threadLocalMap,并赋值
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // 初始化thradLocalMap 并赋值
            createMap(t, value);
    }

        从上面的代码可以看出,ThreadLocal  set赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化。

get方法

        在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回threadLocals中的值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则设置值(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null。

    public T get() {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //3、如果map数据为空,
        if (map != null) {
            //3.1、获取threalLocalMap中存储的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
        return setInitialValue();
    }
 
 
private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

remove方法

        remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

15.ThreadLocal内存泄漏问题?

  ThreadLocal 内存泄露问题是怎么导致的?

        (不背:弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。所以key会在垃圾回收的时候被回收掉, 而key对应的value则不会被回收, 这样会导致一种现象:key为null,value有值。)

        (不背:内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,

广义并通俗的说,就是:不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。)

16.ThreadLocal和Synchronized的区别

 17.ThreadLocal使用场景

        Session管理、数据库连接,处理数据库事务。

synchronized原理,锁静态方法、实例方法、代码块有什么区别?

18.说一说自己对于 synchronized 关键字的了解?(⭐⭐⭐⭐⭐)

        synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized 关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。它是一种悲观锁,是一种可重入锁。

如何使用 synchronized?

19.synchronized和Lock的区别(⭐⭐⭐⭐⭐)

  1. 来源:
    lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;

  2. 异常是否释放锁:
    synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)

  3. 是否响应中断
    lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;

  4. 是否知道获取锁
    Lock可以通过trylock来知道有没有获取锁,而synchronized不能;

  5. synchronized可以给类、方法、代码块加锁,而lock只能给代码块加锁。

20.什么是线程池?为什么要使用线程池,线程池的好处?

        线程池顾名思义就是事先创建若干个可执行的线程放入一个(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程,而是返回池中从而减少创建和销毁对象的开销。

使用线程池的好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

为什么要用线程池?

21.线程池ThreadPoolExecutor构造函数的构造参数?(⭐⭐⭐⭐⭐)

/**
 * 用给定的初始参数创建一个新的ThreadPoolExecutor。
 */
public ThreadPoolExecutor(int corePoolSize,
                      int maximumPoolSize,
                      long keepAliveTime,
                      TimeUnit unit,
                      BlockingQueue<Runnable> workQueue,
                      ThreadFactory threadFactory,
                      RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
            throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

ThreadPoolExecutor 参数:

  • int corePoolSize : 核心线程数定义了最小可以同时运行的线程数量。
  • int maximumPoolSize : 最大线程数
  • long keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程以外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁
  • TimeUnit unit : keepAliveTime 参数的时间单位
  • BlockingQueue<Runnable> workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
  • ThreadFactory threadFactory :就是创建线程的线程工厂。
  • RejectExecutionHandler handler :饱和策略。关于饱和策略下面单独介绍一下。

线程池常见参数有哪些?如何解释?

22.如何创建线程池?(⭐⭐⭐⭐⭐)

如何创建线程池?

可缓存、周期性、单个、定长

CachedThreadPool:可缓存的线程池,该线程池中没有核心线程非核心线程的数量限制为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收空闲线程,适用于耗时少,任务量大的情况。

SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。

FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量没有非核心线程
 

对应 Executors 工具类中的方法如图所示:

23.线程池的工作流程?

如何创建线程池?

核心线程池->任务队列->最大线程池

        线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。

当调用 execute() 方法添加一个任务时,线程池会做如下判断

1、如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务(即使有空闲线程也会创建执行)

2、如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务,这个时候线程池中的线程数小于maximumPoolSize

3、如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理,这个时候缓冲队列不允许有新的任务了

4、如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,也就是allowCoreThreadTimeOut(true),那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止

24.execute()方法和submit()方法的区别?

        都是用来提交任务的。

  1. execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否
  2. submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Future 的 get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
  3. 可以接受的任务类型:execute只能接受Runnable类型的任务,submit不管是Runnable还是Callable类型的任务都可以接受

25.handler的拒绝(饱和)策略:(⭐⭐⭐⭐⭐)

线程池的饱和策略有哪些?

有四种:

  • 第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满(RejectedExecutionException)
  • 第二种DisCardPolicy:不执行新任务,也不抛出异常
  • 第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
  • 第四种CallerRunsPolicy:直接调用execute来执行当前任务,(调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务)

26.简单说下对Java中原子类的理解?

        Atomic 原子类总结 | JavaGuide

27.AQS原理、介绍,什么是AQS?

里面一共9道题目:

AQS

AQS 详解 | JavaGuide

        AQS内部维护了一个CLH队列来管理锁。线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync queue里。 接着会不断的循环尝试获取锁,条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。

        CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。

看个 AQS(AbstractQueuedSynchronizer)原理图:

AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。

private volatile int state;//共享变量,使用volatile修饰保证线程可见性

状态信息通过 protected 类型的 getState,setState,compareAndSetState 进行操作


//返回同步状态的当前值
protected final int getState() {
    return state;
}
//设置同步状态的值
protected final void setState(int newState) {
    state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

28.AQS 对资源的共享方式

AQS 定义两种资源共享方式

  • Exclusive(独占):只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁:
    • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
    • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
  • Share(共享):多个线程可同时执行,如 CountDownLatchSemaphore、 CyclicBarrierReadWriteLock 我们都会在后面讲到。

29.AQS底层原理

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    private static final long serialVersionUID = 7373984972572414691L;

    /**
     * Creates a new {@code AbstractQueuedSynchronizer} instance
     * with initial synchronization state of zero.
     */
    protected AbstractQueuedSynchronizer() { }

    static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled. */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking. */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition. */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate.
         */
        static final int PROPAGATE = -3;

        volatile int waitStatus;

        volatile Node prev;

        volatile Node next;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         */
        volatile Thread thread;

        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        final Node predecessor() {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        /** Establishes initial head or SHARED marker. */
        Node() {}

        /** Constructor used by addWaiter. */
        Node(Node nextWaiter) {
            this.nextWaiter = nextWaiter;
            THREAD.set(this, Thread.currentThread());
        }

        /** Constructor used by addConditionWaiter. */
        Node(int waitStatus) {
            WAITSTATUS.set(this, waitStatus);
            THREAD.set(this, Thread.currentThread());
        }

        /** CASes waitStatus field. */
        final boolean compareAndSetWaitStatus(int expect, int update) {
            return WAITSTATUS.compareAndSet(this, expect, update);
        }

        /** CASes next field. */
        final boolean compareAndSetNext(Node expect, Node update) {
            return NEXT.compareAndSet(this, expect, update);
        }

        final void setPrevRelaxed(Node p) {
            PREV.set(this, p);
        }

        // VarHandle mechanics
        private static final VarHandle NEXT;
        private static final VarHandle PREV;
        private static final VarHandle THREAD;
        private static final VarHandle WAITSTATUS;
        static {
            try {
                MethodHandles.Lookup l = MethodHandles.lookup();
                NEXT = l.findVarHandle(Node.class, "next", Node.class);
                PREV = l.findVarHandle(Node.class, "prev", Node.class);
                THREAD = l.findVarHandle(Node.class, "thread", Thread.class);
                WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class);
            } catch (ReflectiveOperationException e) {
                throw new ExceptionInInitializerError(e);
            }
        }
    }

    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

    /**
     * The synchronization state.
     */
    private volatile int state;

    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
    ...其他方法
}

Java并发包基石-AQS详解 - dreamcatcher-cx - 博客园

30.CountDownLatch介绍

        CountDownLatch 有什么用?

        CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。

        CountDownLatch 定义了一个计数器,和一个阻塞队列, 当计数器的值递减为0之前,阻塞队列里面的线程处于挂起状态,当计数器递减到0时会唤醒阻塞队列所有线程,这里的计数器是一个标志,可以表示一个任务一个线程,也可以表示一个倒计时器,CountDownLatch可以解决那些一个或者多个线程在执行之前必须依赖于某些必要的前提业务先执行的场景。

 常用方法

CountDownLatch(int count); //构造方法,创建一个值为count 的计数器。
​
await();//阻塞当前线程,将当前线程加入阻塞队列。
​
await(long timeout, TimeUnit unit);//在timeout的时间之内阻塞当前线程,时间一过则当前线程可以执行,
​
countDown();//对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。

31.CountDownLatch实现原理,底层?

  CountDownLatch 的原理是什么?

public class CountDownLatch {

    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

    private final Sync sync;


    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }


    public void countDown() {
        sync.releaseShared(1);
    }

    public long getCount() {
        return sync.getCount();
    }

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

CountDownLatch原理_以千的博客-CSDN博客

32.CountDownLatch应用场景?

用过 CountDownLatch 么?什么场景下用的?

1、线程在开始运行前等待其他任务执行完毕。

将 CountDownLatch 的计数器初始化为 n (new CountDownLatch(n)),每当一个任务线程执行完毕,就将计数器减 1 (countdownlatch.countDown()),当计数器的值变为 0 时,在 CountDownLatch 上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

2、实现多个线程在某时刻开始同时开始执行

注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 CountDownLatch 对象,将其计数器初始化为 1 (new CountDownLatch(1)),多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为 0,多个线程同时被唤醒。

33.CyclicBarrier介绍

CyclicBarrier 有什么用?

        CyclicBarrier也叫同步屏障,可以让一组线程达到一个屏障时被阻塞,直到最后一个线程达到屏障时,所以被阻塞的线程才能继续执行。
        CyclicBarrier好比一扇门,默认情况下关闭状态,堵住了线程执行的道路,直到所有线程都就位,门才打开,让所有线程一起通过。

34.CyclicBarrier原理/底层

CyclicBarrier 的原理是什么?

38.CountDownLatch和CyclicBarrier区别?

CountDownLatch 是计数器,只能使用一次,而 CyclicBarrier可以多次使用。

CountDownLatch: 一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;

CyclicBarrier : 多个线程互相等待,直到到达同一个同步点,再继续一起执行。

36.CopyOnWriteArrayList(放过这个)介绍,使用场景?

(差不多得了,从写和读两方面回答,加锁不加锁)

        CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器

这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器

适用场景:读多写少的场景

看一看,不背

37.CopyOnWriteArrayList(放过这个)底层具体怎么实现?(差不多得了,就这样,别再找更好的资料了)

CopyOnWriteArrayList的特点:

  • 实现了List接口
  • 内部持有一个ReentrantLock lock = new ReentrantLock();
  • 底层是用volatile transient声明的数组 array
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    //序列号
    private static final long serialVersionUID = 8673264195747942595L;

    /** 
     * 可重入锁,对数组增删改时,使用它加锁
     */
    final transient ReentrantLock lock = new ReentrantLock();

    /**
     * 存放元素的数组,其实就是本体
     */
    private transient volatile Object[] array;
}

构造方法内部都使用了setArray

在上述的构造方法中都使用了setArray()这个方法,不过这个方法只是将array引用指向对应的数组对象罢了,这个方法不是关键。

//设置数组引用指向的数组对象
final void setArray(Object[] a) {
        array = a;
}
    /**
     * 默认构造方法,构建数组长度为0,
     * 没错就是0,接下来会知道为什么这么做
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    /**
     * 通过集合类来构造
     */
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }

    /**
     * 通过数组来构造
     */
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

添加元素

    //直接将元素添加到末尾
    public boolean add(E e) {
        //获取锁
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //先获取原先的数组
            Object[] elements = getArray();
            int len = elements.length;
            //构建一个新的数组,大小是原数组的大小 1
            Object[] newElements = Arrays.copyOf(elements, len   1);
            //将元素插入到数组末尾
            newElements[len] = e;
            //将array引用指向新的数组,原来的数组会被垃圾收集器回收
            setArray(newElements);
            return true;
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    //在指定位置插入新元素
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //判断是否越界
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: " index  ", Size: " len);
            Object[] newElements;
            //计算插入位置与数组末尾下标的距离
            int numMoved = len - index;
            //若为0,则是添加到数组末尾
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len   1);
            else {
                //不为0,则将原数组分开复制
                newElements = new Object[len   1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index   1, numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }

 删除元素,很简单,就是判断要删除的元素是否最后一个,如果最后一个直接在复制副本数组的时候,复制长度为旧数组的 length-1 即可;
但是如果不是最后一个元素,就先复制旧的数组的index前面元素到新数组中,然后再复制旧数组中index后面的元素到数组中,最后再把新数组的引用赋值给旧数组的引用。最后在finally语句块中将锁释放。

public E remove(int index) {
        //加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                //如果要删除的是列表末端数据,拷贝前len-1个数据到新副本上,再切换引用
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                //否则,将除要删除元素之外的其他元素拷贝到新副本中,并切换引用
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            //解锁
            lock.unlock();
        }
    }

获取元素
读的时候不需要加锁,直接返回数组中的数据。

    //这个是真正用于查询的方法
    @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    /**
     * 向外开放的方法
     */
    public E get(int index) {
        return get(getArray(), index);
    }

    final Object[] getArray() {
        return array;
    }

38.(别删)BlockingQueue(放过这个)   介绍   阻塞功能如何实现   写一个   底层   有哪些,具体有什么功能

39.BlockingQueue介绍

        阻塞队列(BlockingQueue)被广泛使用在“生产者-消费者”问题中,其原因是 BlockingQueue 提供了可阻塞的插入和移除的方法。当队列容器已满,生产者线程会被阻塞,直到队列未满;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。

BlockingQueue 是一个接口,继承自 Queue

40.BlockingQueue有哪些?具体有什么功能

记1、2、4实现类就行了

44.BlockingQueue方法(稍微看看,不背)

public interface BlockingQueue<E> extends Queue<E> {
    /**
     * 入队一个元素,如果有空间则直接插入,并返回true;
     * 如果没有空间则抛出IllegalStateException
     */
    boolean add(E e);

    /**
     * 入队一个元素,如果有空间则直接插入,并返回true;
     * 如果没有空间返回false
     */
    boolean offer(E e);

    /**
     * 入队一个元素,如果有空间则直接插入,如果没有空间则一直阻塞等待
     */
    void put(E e) throws InterruptedException;

    /**
     * 入队一个元素,如果有空间则直接插入,并返回true;
     * 如果没有空间则等待timeout时间,插入失败则返回false
     */
    boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;

    /**
     * 出队一个元素,如果存在则直接出队,如果没有空间则一直阻塞等待
     */
    E take() throws InterruptedException;

    /**
     * 出队一个元素,如果存在则直接出队,如果没有空间则等待timeout时间,无元素则返回null
     */
    E poll(long timeout, TimeUnit unit) throws InterruptedException;

    /**
     * 返回该队列剩余的容量(如果没有限制则返回Integer.MAX_VALUE)
     */
    int remainingCapacity();

    /**
     * 如果元素o在队列中存在,则从队列中删除
     */
    boolean remove(Object o);

    /**
     * 判断队列中是否存在元素o
     */
    public boolean contains(Object o);

    /**
     * 将队列中的所有元素出队,并添加到给定的集合c中,返回出队的元素数量
     */
    int drainTo(Collection<? super E> c);

    /**
     * 将队列中的元素出队,限制数量maxElements个,并添加到给定的集合c中,返回出队的元素数量
     */
    int drainTo(Collection<? super E> c, int maxElements);
}

41.ReentrantLock介绍

  • ReentrantLock是一个可重入的互斥锁,又被称为“独占锁”。
  • ReentrantLock锁在同一个时间点只能被一个线程锁持有;可重入表示,ReentrantLock锁可以被同一个线程多次获取。
  • ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。在“公平锁”的机制下,线程依次排队获取锁;而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。

42.synchronized 和 ReentrantLock 有什么区别? 

synchronized 和 ReentrantLock 有什么区别?

43.ReentranLock底层原理

深入理解ReentrantLock的实现原理 - 掘金

44.悲观锁和乐观锁   区别   是什么  使用场景  怎么实现 (⭐⭐⭐⭐⭐)

乐观锁和悲观锁详解 | JavaGuide

【BAT面试题系列】面试官:你了解乐观锁和悲观锁吗? - 编程迷思 - 博客园

什么是CAS机制?_1*null的博客-CSDN博客_cas机制

45. CAS和synchronized区别

1、对于资源竞争较少的情况:性能cas>synchronized,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

2、对于资源竞争严重的情况:性能cas<synchronized,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

46.Java中的各种锁

八股文第五版

史上最全 Java 中各种锁的介绍_java锁-CSDN

协程

多线程题目手写

什么是OOM?StackOverflowError和OutOfMemoryError?

同一个线程里面可以加两把锁吗?为什么?

Java线程安全怎么保证?

Java8新特性

(⭐⭐⭐⭐⭐)

1.Interface

     (   interface 的设计初衷是面向抽象,提高扩展性。这也留有一点遗憾,Interface 修改的时候,实现它的类也必须跟着改。)

       ( 为了解决接口的修改与现有的实现不兼容的问题。)新 interface 的方法可以用default 或 static修饰,这样就可以有方法体,实现类也不必重写此方法。

一个 interface 中可以有多个方法被它们修饰,这 2 个修饰符的区别主要也是普通方法和静态方法的区别。

  1. default修饰的方法,是普通实例方法,可以用this调用,可以被实现类继承和重写。
  2. static修饰的方法,使用上和一般类静态方法一样。但它不能被实现类继承,只能用Interface调用。
public interface InterfaceNew {
    static void sm() {
        System.out.println("interface提供的方式实现");
    }
    static void sm2() {
        System.out.println("interface提供的方式实现");
    }

    default void def() {
        System.out.println("interface default方法");
    }
    default void def2() {
        System.out.println("interface default2方法");
    }
    //须要实现类重写
    void f();
}

public interface InterfaceNew1 {
    default void def() {
        System.out.println("InterfaceNew1 default方法");
    }
}

        如果有一个类既实现了 InterfaceNew 接口又实现了 InterfaceNew1接口,它们都有def(),并且 InterfaceNew 接口和 InterfaceNew1接口没有继承关系的话,这时就必须重写def()。不然的话,编译的时候就会报错。

public class InterfaceNewImpl implements InterfaceNew , InterfaceNew1{
    public static void main(String[] args) {
        InterfaceNewImpl interfaceNew = new InterfaceNewImpl();
        interfaceNew.def();
    }

    @Override
    public void def() {
        InterfaceNew1.super.def();
    }

    @Override
    public void f() {
    }
}

2.函数式接口

定义有且只有一个抽象方法,但可以有多个非抽象方法的接口(接口默认方法的修饰符为public abstract)

在 java 8 中专门有一个包放函数式接口java.util.function,该包下的所有接口都有 @FunctionalInterface 注解,提供函数式编程。

在其他包中也有函数式接口,其中一些没有@FunctionalInterface 注解,但是只要符合函数式接口的定义就是函数式接口,与是否有

@FunctionalInterface注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。

3.Lambda表达式(箭头函数)(需要能够大概介绍)

替代匿名内部类

Lambda 表达式是一个匿名函数,java 8 允许把函数作为参数传递进方法中。

过去给方法传动态参数的唯一方法是使用内部类。比如

1.Runnable 接口

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("The runable now is using!");
            }
}).start();
//用lambda
new Thread(() -> System.out.println("It's a lambda function!")).start();

2.Comparator 接口

List<Integer> strings = Arrays.asList(1, 2, 3);

Collections.sort(strings, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
    return o1 - o2;}
});

//Lambda
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2);
//分解开
Comparator<Integer> comperator = (Integer o1, Integer o2) -> o1 - o2;
Collections.sort(strings, comperator);

3.自定义接口

上面的 3 个例子是我们在开发过程中最常见的,从中也能体会到 Lambda 带来的便捷与清爽。它只保留实际用到的代码,把无用代码全部省略。

        那它对接口有没有要求呢?我们发现这些匿名内部类只重写了接口的一个方法,当然也只有一个方法必须要重写,其他的方法都已实现(default),(接口默认方法修饰符为public abstract)

        这就是我们上文提到的函数式接口,也就是说只要方法的参数是函数式接口都可以用 Lambda 表达式

@FunctionalInterface
public interface Comparator<T>{
    ...
}

@FunctionalInterface
public interface Runnable{
    ...
}
看看就行,不用背
```java
public static void main(String[] args) {
        //完整的Lambda表达式
        print((int x) -> {
            return 2*x;
        },10);
        //省略类型声明,但必须所有参数都不写
        print((x) -> {
            return 2*x;
        },10);
        //如果只有一个参数,则可以省略参数小括号
        print(x -> {
            return 2*x;
        },10);
        //如果只有一条执行语句,且是返回语句,则可以省略大括号和return
        Mymath m = x -> 2*x;
        print(m,10);
    }
    public static void print(Mymath m,int x){
        int num = m.multiplication(x);
        System.out.println(num);
    }
    static interface Mymath{
        int multiplication(int x);
    }
```输出结果
20
20
20
20

方法的引用

Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。

public class LambdaClassSuper {
    LambdaInterface sf(){
        return null;
    }
}

public class LambdaClass extends LambdaClassSuper {
    public static LambdaInterface staticF() {
        return null;
    }

    public LambdaInterface f() {
        return null;
    }

    void show() {
        //1.调用静态函数,返回类型必须是functional-interface
        LambdaInterface t = LambdaClass::staticF;

        //2.实例方法调用
        LambdaClass lambdaClass = new LambdaClass();
        LambdaInterface lambdaInterface = lambdaClass::f;

        //3.超类上的方法调用
        LambdaInterface superf = super::sf;

        //4. 构造方法调用
        LambdaInterface tt = LambdaClassSuper::new;
    }
}

3.Optional类

背并看csdn收藏:JDK1.8新特。性值Optional_398701344努力加油!-CSDN博客_jdk1.8 optional

Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).filter(v->v==1).orElse(3);

 以下只稍微看看就行

阿里巴巴开发手册关于 Optional 的介绍open in new window中这样写到:

防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:

1) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。

反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。

2) 数据库的查询结果可能为 null。

3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。

4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。

5) 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。

6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。

正例:使用 JDK8 的 Optional 类来防止 NPE 问题。

他建议使用 Optional 解决 NPE(java.lang.NullPointerException)问题,它就是为 NPE 而生的,其中可以包含空值或非空值。下面我们通过源码逐步揭开 Optional 的红盖头。

假设有一个 Zoo 类,里面有个属性 Dog,需求要获取 Dog 的 age

class Zoo {
   private Dog dog;
}

class Dog {
   private int age;
}

传统解决NPE方法:

Zoo zoo = getZoo();
if(zoo != null){
   Dog dog = zoo.getDog();
   if(dog != null){
      int age = dog.getAge();
      System.out.println(age);
   }
}

Optional类解决方法:

Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).ifPresent(age ->
    System.out.println(age)
);

如何创建一个Options

        上例中Optional.ofNullable是其中一种创建 Optional 的方式。我们先看一下它的含义和其他创建 Optional 的源码方法。

/**
* Common instance for {@code empty()}. 全局EMPTY对象
*/
private static final Optional<?> EMPTY = new Optional<>();

/**
* Optional维护的值
*/
private final T value;

/**
* 如果value是null就返回EMPTY,否则就返回of(T)
*/
public static <T> Optional<T> ofNullable(T value) {
   return value == null ? empty() : of(value);
}
/**
* 返回 EMPTY 对象
*/
public static<T> Optional<T> empty() {
   Optional<T> t = (Optional<T>) EMPTY;
   return t;
}
/**
* 返回Optional对象
*/
public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}
/**
* 私有构造方法,给value赋值
*/
private Optional(T value) {
  this.value = Objects.requireNonNull(value);
}
/**
* 所以如果of(T value) 的value是null,会抛出NullPointerException异常,这样貌似就没处理NPE问题
*/
public static <T> T requireNonNull(T obj) {
  if (obj == null)
         throw new NullPointerException();
  return obj;
}

ofNullable 方法和of方法唯一区别就是当 value 为 null 时,ofNullable 返回的是EMPTY,of 会抛出 NullPointerException 异常。如果需要把 NullPointerException 暴漏出来就用 of,否则就用 ofNullable

/**
* 如果value为null,返回EMPTY,否则返回Optional封装的参数值
*/
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
}
/**
* 如果value为null,返回EMPTY,否则返回Optional封装的参数值,如果参数值返回null会抛 NullPointerException
*/
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
}

map() 和 flatMap() 有什么区别的?

1.参数不一样,map 的参数上面看到过,flatMap 的参数是这样

class ZooFlat {
        private DogFlat dog = new DogFlat();

        public DogFlat getDog() {
            return dog;
        }
    }

class DogFlat {
        private int age = 1;
        public Optional<Integer> getAge() {
            return Optional.ofNullable(age);
        }
}

ZooFlat zooFlat = new ZooFlat();
Optional.ofNullable(zooFlat).map(o -> o.getDog()).flatMap(d -> d.getAge()).ifPresent(age ->
    System.out.println(age)
);

2.flatMap() 参数返回值如果是 null 会抛 NullPointerException,而 map() 返回EMPTY

/**
* value是否为null
*/
public boolean isPresent() {
    return value != null;
}
/**
* 如果value不为null执行consumer.accept
*/
public void ifPresent(Consumer<? super T> consumer) {
   if (value != null)
    consumer.accept(value);
}

获取value 

/**
* Return the value if present, otherwise invoke {@code other} and return
* the result of that invocation.
* 如果value != null 返回value,否则返回other的执行结果
*/
public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}

/**
* 如果value != null 返回value,否则返回T
*/
public T orElse(T other) {
    return value != null ? value : other;
}

/**
* 如果value != null 返回value,否则抛出参数返回的异常
*/
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
}
/**
* value为null抛出NoSuchElementException,不为空返回value。
*/
public T get() {
  if (value == null) {
      throw new NoSuchElementException("No value present");
  }
  return value;
}

 过滤值

/**
* 1. 如果是empty返回empty
* 2. predicate.test(value)==true 返回this,否则返回empty
*/
public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
}

 4.Stream流(⭐⭐⭐⭐⭐)

java 新增了 java.util.stream 包,它和之前的流大同小异。之前接触最多的是资源流,比如java.io.FileInputStream,通过流把文件从一个地方输入到另一个地方,它只是内容搬运工,对文件内容不做任何CRUD

Stream依然不存储数据,不同的是它可以检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等。可以想象成是 Sql查询语句。

它的源数据可以是 CollectionArray 等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用。

  1. stream 串行流
  2. parallelStream 并行流,可多线程执行

常用类型

接下来我们看java.util.stream.Stream常用方法

/**
* 返回一个串行流
*/
default Stream<E> stream()

/**
* 返回一个并行流
*/
default Stream<E> parallelStream()

/**
* 返回T的流
*/
public static<T> Stream<T> of(T t)

/**
* 返回其元素是指定值的顺序流。
*/
public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}


/**
* 过滤,返回由与给定predicate匹配的该流的元素组成的流
*/
Stream<T> filter(Predicate<? super T> predicate);

/**
* 此流的所有元素是否与提供的predicate匹配。
*/
boolean allMatch(Predicate<? super T> predicate)

/**
* 此流任意元素是否有与提供的predicate匹配。
*/
boolean anyMatch(Predicate<? super T> predicate);

/**
* 返回一个 Stream的构建器。
*/
public static<T> Builder<T> builder();

/**
* 使用 Collector对此流的元素进行归纳
*/
<R, A> R collect(Collector<? super T, A, R> collector);

/**
 * 返回此流中的元素数。
*/
long count();

/**
* 返回由该流的不同元素(根据 Object.equals(Object) )组成的流。
*/
Stream<T> distinct();

/**
 * 遍历
*/
void forEach(Consumer<? super T> action);

/**
* 用于获取指定数量的流,截短长度不能超过 maxSize 。
*/
Stream<T> limit(long maxSize);

/**
* 用于映射每个元素到对应的结果
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

/**
* 根据提供的 Comparator进行排序。
*/
Stream<T> sorted(Comparator<? super T> comparator);

/**
* 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。
*/
Stream<T> skip(long n);

/**
* 返回一个包含此流的元素的数组。
*/
Object[] toArray();

/**
* 使用提供的 generator函数返回一个包含此流的元素的数组,以分配返回的数组,以及分区执行或调整大小可能需要的任何其他数组。
*/
<A> A[] toArray(IntFunction<A[]> generator);

/**
* 合并流
*/
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

本文列出 Stream 具有代表性的方法之使用

@Test
public void test() {
  List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");
    //返回符合条件的stream
    Stream<String> stringStream = strings.stream().filter(s -> "abc".equals(s));
    //计算流符合条件的流的数量
    long count = stringStream.count();

    //forEach遍历->打印元素
    strings.stream().forEach(System.out::println);

    //limit 获取到1个元素的stream
    Stream<String> limit = strings.stream().limit(1);
    //toArray 比如我们想看这个limitStream里面是什么,比如转换成String[],比如循环
    String[] array = limit.toArray(String[]::new);

    //map 对每个元素进行操作返回新流
    Stream<String> map = strings.stream().map(s -> s + "22");

    //sorted 排序并打印
    strings.stream().sorted().forEach(System.out::println);

    //Collectors collect 把abc放入容器中
    List<String> collect = strings.stream().filter(string -> "abc".equals(string)).collect(Collectors.toList());
    //把list转为string,各元素用,号隔开
    String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(","));

    //对数组的统计,比如用
    List<Integer> number = Arrays.asList(1, 2, 5, 4);

    IntSummaryStatistics statistics = number.stream().mapToInt((x) -> x).summaryStatistics();
    System.out.println("列表中最大的数 : "+statistics.getMax());
    System.out.println("列表中最小的数 : "+statistics.getMin());
    System.out.println("平均数 : "+statistics.getAverage());
    System.out.println("所有数之和 : "+statistics.getSum());

    //concat 合并流
    List<String> strings2 = Arrays.asList("xyz", "jqx");
    Stream.concat(strings2.stream(),strings.stream()).count();

    //注意 一个Stream只能操作一次,不能断开,否则会报错。
    Stream stream = strings.stream();
    //第一次使用
    stream.limit(2);
    //第二次使用
    stream.forEach(System.out::println);
    //报错 java.lang.IllegalStateException: stream has already been operated upon or closed

    //但是可以这样, 连续使用
    stream.limit(2).forEach(System.out::println);
}

  • 转换流操作 :例如filter和map方法,将一个Stream转换成另一个Stream,返回值都是Stream。

  • 终结流操作 :例如count和collect方法,将一个Stream汇总为我们需要的结果,返回值都不是Stream。

Stream只在遇到终结操作的时候才会执行,比如:

 List.of(1, 2, 3).stream()
                .filter(i -> i > 2)
                .peek(System.out::println);

这么一段代码是不会执行的,peek方法可以看作是forEach,这里我用它来打印Stream中的元素。

因为filter方法和peek方法都是转换流方法,所以不会触发执行。

如果我们在后面加入一个count方法就能正常执行:

List.of(1, 2, 3).stream()
                .filter(i -> i > 2)
                .peek(System.out::println)
                .count();

count方法是一个终结操作,用于计算出Stream中有多少个元素,它的返回值是一个long型。

Stream的这种没有终结操作就不会执行的特性被称为延迟执行

@Test
public void laziness(){
  List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");
  Stream<Integer> stream = strings.stream().filter(new Predicate() {
      @Override
      public boolean test(Object o) {
        System.out.println("Predicate.test 执行");
        return true;
        }
      });

   System.out.println("count 执行");
   stream.count();
}
/*-------执行结果--------*/
count 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行

小结

从源码和实例中我们可以总结出一些 stream 的特点

  1. 通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。
  2. 方法参数都是函数式接口类型
  3. 一个 Stream 只能操作一次,操作完就关闭了,继续使用这个 stream 会报错。
  4. Stream 不保存数据,不改变数据源

5.Data-Time Api

来改进时间、日期的处理

总结

我们梳理总结的 java 8 新特性有

  • Interface & functional Interface
  • Lambda
  • Stream
  • Optional
  • Date time-api

计算机与网络

重点面试题

1.OSI的七层模型,及其对应的功能

        应用层:负责给应用程序提供统一的接口。

        表示层:提供对应用层数据的编码和转换功能。

        会话层:管理(建立、维护、重连)应用程序之间的会话

        运输层:提供端到端的数据传输

        网络层:负责数据的路由、转发、分片等

        数据链路层:负责数据的封装成帧和差错检测,以及MAC寻址。

        物理层:实现最终信号的传输。通过物理介质传输比特流

2.三次握手(三报文握手)(已面)

TCP建立连接的过程叫做握手,握手需要在客户端和服务器之间三个TCP报文段。

三次握手

1.第一次握手:客户端给服务器发送一个连接请求报文段,同步序号SYN=1(初始序号seq=x,报文段不能携带数据),客户端进入同步已发送状态。

2.第二次握手:服务端收到连接请求报文段之后,若同意建立连接,应答一个确认报文段,SYN=1,ACK=1(seq=y,ack(确认号)=x+1),服务端进入同步收到状态。

3.第三次握手:客户端收到确认报文后,回应一个确认报文段ACK=1(ack=y+1,seq=x+1),客户端进入ESTABLISHED(已建立连接)状态,双方就已建立连接。

3.为什么要三次握手,两次不行?

(1)确认双方的接受和发送能力正常,也就是客户端要考察服务端的发送和接收能力,服务端也要考察客户端的发送和接收能力。

(2)防止失效的请求再次传到服务端,造成错误;

     如果只有两次握手。

        失效的连接请求是指:客户端发出的连接请求没有收到服务端的确认,过一段时间后,客户端又向服务端发送连接请求,且建立成功,顺序完成数据传输。

     客户端第一次发送的请求因网络延迟到达服务端(并没有丢失),服务端以为是客户端又发起的新请求,于是服务端同意连接,并向客户端发回确认,但此时客户端根本不理会,服务端确一直等待客户端发送数据,导致服务端的资源浪费。

其他问题:

        如果最后一个ACK包丢失,服务器端收不到响应,会超时重传SYN+ACK包。

4.三次握手过程中可以携带数据吗?

        其实第三次握手的时候,是可以携带数据的。也就是说,第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。

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

5.四次挥手(已面)

四次挥手

 第一次挥手:客户端发送一个连接释放(FIN)报文段,FIN=1  (seq=u) ,客户端进入FIN-WAIT-1(终止等待1)状态。

第二次挥手:服务端收到连接释放(FIN)报文段后,发送确认(ACK)报文段ACK=1(seq=v,ack=u+1),服务端进入CLOSE-WAIT(停止等待状态),此时TCP处于半关闭状态,服务端仍可以向客户端发送数据。

客户端收到确认(ACK)报文段之后进入FIN-WAIT-2(终止等待2)状态。

第三次挥手:如果服务端没有要向客户端发送的数据了,服务端向客户端发送连接释放(FIN+ACK)报文段FIN=1,ACK=1,重复上次发送的确认号(ack=u+1,seq=w),服务端进入LAST-ACK(最后确认)状态。等待客户端的确认。

第四次挥手:客户端收到连接释放(FIN+ACK)报文段之后,发出确认(ACK)报文段,ACK=1(ack=w+1,seq=u+1),客户端进入TIME_WAIT(时间等待)状态,需要过一段时间(2MSL,MSL是最长报文段寿命)确保服务端收到自己的确认报文后,才进入CLOSED状态。

        服务端收到确认(ACK)报文后,就关闭连接了,处于CLOSED状态。

6.为什么客户端在TIME_WAIT状态必须等待2MSL时间呢?

        为了保证客户端发送的最后一个ACK报文段能够到达服务端,如果ACK报文段丢失,服务端收不到确认,会超时重传FIN+ACK报文段,而客户端就能在2MSL时间内收到重传的FIN+ACK报文段,接着客户端重传一次ACK报文段,重新启动2MSL计时器,最后,客户端和服务端都能进入CLOSED状态。

        如果客户端不等待而直接进入CLOSED状态,那么就无法收到服务段重传的FIN+ACK报文,不会重传ACK报文段,服务端无法正常进入CLOSED状态。

7.TCP协议的主要特点(已面)

(1)TCP是面向连接的运输层协议;应用程序在使用TCP协议之前,必须先建立TCP连接,在传输完数据之后,必须释放已经建立的TCP连接。

(2)每一条TCP连接只能有两个端点,TCP只支持一对一通信(一对一)

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

(4)TCP提供全双工通信,运行通信双方在任何时候都可以发送数据,因为两端都设有发送缓存和接收缓存。

(5)面向字节流,TCP把应用程序交下来的数据仅仅看成一连串无结构的字节流。

8.UDP协议特点(已面)

9.TCP和UDP的区别(已面)

UDPTCP
是否连接无连接面向连接
是否可靠不可靠传输,不使用流量控制和拥塞控制可靠传输,使用流量控制和拥塞控制
连接对象个数支持一对一,一对多,多对一和多对多交互通信只能是一对一通信
传输方式面向报文面向字节流
首部开销首部开销小,仅8字节首部最小20字节,最大60字节
适用场景适用于实时应用(IP电话、视频会议、直播等)适用于要求可靠传输的应用,例如文件传输

10.TCP和UDP应用场景(需要背)

TCP应用场景:

        效率要求相对低,但是要求可靠传输的场景。例子:文件传输、邮件传输

UDP应用场景:

        效率要求相对高,不要求可靠传输的场景 。例子:视频会议、直播

12.Http1.0 1.1 2.0区别   3.0???(已面)

面试官:说说 HTTP1.0/1.1/2.0 的区别? | web前端面试 - 面试官系列

HTTP1.0,1.1,2.0,3.0 - 掘金

图解|为什么 HTTP3.0 使用 UDP 协议?

Http1.0:

        短连接,HTTP1.0规定浏览器与服务器值保持短暂的连接,浏览器每次请求都需要与服务器建立一个新的TCP连接,服务器请求处理完成后立刻断开TCP连接。

Http1.1:

        引入长连接(持久连接),TCP连接默认不关闭,可以被多个请求复用,客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接。

        引入管道机制,在同一个TCP连接里面,客户端可以同时发送多个请求。

        有了持久连接和管道,大大的提升了HTTP的效率,但是服务端还是顺序执行的,效率还有提升的空间。

        (额外:Host域:HTTP1.1的请求消息和响应消息都支持host域,且请求消息中如果没有host域会报告一个错误,不在以 IP 为请求方标志)

Http2.0:

        为了解决Http1.1存在的效率问题,HTTP2.0采用了多路复用。即在一个连接里,客户端和服务端可以同时发送多个请求和响应,而且不用按照顺序一一对应,

        能这样做的前提是,Http2.0采用了二进制分帧,Http2.0会将所有信息分割成更小的消息和帧,并对它们采用二进制格式的编码。

        Header数据压缩

        服务器推送,就是服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求。

http2.0有啥应用场景?

post请求的数据是放在哪里?

13.get请求和post请求的区别

(1)post更安全(不会作为url的一部分,不会被缓存、不会保存在浏览器浏览记录中)
(2)post发送的数据更大(get有url长度限制)
(3)post能发送更多的数据类型(get只能发送ASCII字符)
(4)post比get慢
(5)post用于修改和写入数据,get一般用于搜索查询的操作(淘宝,支付宝的搜索查询都是get提交),目的是资源的获取,读取数据

为什么get比post更快?

1.最重要的一条,post在发送数据之前会先将请求头发送给服务器进行确认,然后才真正发送数据
post请求的过程:
(1)浏览器请求tcp连接(第一次握手)
(2)服务器答应进行tcp连接(第二次握手)
(3)浏览器确认,并发送post请求头(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
(4)服务器返回100 Continue响应
(5)浏览器发送数据
(6)服务器返回200 OK响应
get请求的过程:
(1)浏览器请求tcp连接(第一次握手)
(2)服务器答应进行tcp连接(第二次握手)
(3)浏览器确认,并发送get请求头和数据(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
(4)服务器返回200 OK响应
也就是说,目测get的总耗是post的2/3左右,这个口说无凭,网上已经有网友进行过测试。

2.get会将数据缓存起来,而post不会
        可以做个简短的测试,使用ajax采用get方式请求静态数据(比如html页面,图片)的时候,如果两次传输的数据相同,第二次以后消耗的时间将会在10ms以内(chrome测试),而post每次消耗的时间都差不多。经测试,chrome和firefox下如果检测到get请求的是静态资源,则会缓存,如果是数据,则不会缓存,但是IE什么都会缓存起来,当然,应该没有人用post去获取静态数据吧,反正我是没见过。

一个完整的http请求由哪些部分组成?

14.HTTP常用状态码以及使用场景?

1xx:表示通知信息,服务器收到请求,需要请求者继续执行操作

2xx:表示请求成功

3xx:表示重定向,需要附加操作以完成请求

4xx:客户端错误

5xx:服务器端错误。

100:继续,客户端应继续其请求

101:切换请求协议

200:请求成功

301:永久重定向,会缓存

302:临时重定向,不会缓存

400:请求错误,如请求语法错误,服务端无法理解

403:服务器禁止访问

404:资源未找到

500:服务器内部错误

503:服务器繁忙

15.HTTP状态码301和302的区别,各自的用途?

http状态码301和302详解及区别_幽雨雨幽的博客-CSDN博客_301 302

状态码301和302的区别 - Wayne-Zhu - 博客园

16.HTTP如何实现长连接?在什么时候会超时?

如何用HTTP实现长连接?_@Block_Smile的博客-CSDN博客_http如何实现长连接

17.TCP如何保证可靠传输(可靠性原理)和拥塞控制原理?

TCP协议保证数据传输可靠性的方式主要有:

  1. 校验和: TCP报文段首部中有对应的检验和字段发送的数据包的二进制相加然后取反,目的是检测数据在传输过程中的任何变化如果收到报文段的检验和有差错,接收方将丢弃这个报文段。
  2. 流量控制: TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止数据丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制
  3. 拥塞控制: 当网络拥塞时,减少数据的发送。
  4. 序列号和确认应答: TCP给发送的每一个包进行编号(序列号依次递增),接收方对数据包进行排序,把有序数据传送给应用层。 接收方收到报文就会确认
  5. 超时重传: 当 TCP 发出一个报文段后,它启动一个定时器。如果不能及时收到一个确认,将重发这个报文段。

拥塞控制

        拥塞控制就是为了防止过多的数据注入到网络中,使网络过载。

(1)发生网络拥塞 

(2)收到三个重复确认,执行快重传算法

在这里插入图片描述
        1.慢启动:每次收到确认应答,窗口大小*2。

        2.拥塞避免:当窗口大小达到阈值时,进入拥塞避免状态,每次确认,窗口大小+1

        一旦发生网络发生拥塞导致超时重传,阈值设为当前窗口一半再重新进行慢启动过程

        3.快重传:接收方在收到一个失序的报文段后就立即发出重复确认,M1接收确认,M2接收确认,M3没有接收到但是接收到了M4,必须立即发出对M2的重复确认,发送方一连收到3个重复确认立即进行重传,不会误认为网络拥塞,然后执行快恢复算法。

        4.快恢复:将阈值和窗口大小都调整为现在窗口大小的一半并开始执行拥塞避免算法。

18. IP地址有哪些分类?

(背一下图4-5中内容就行)

A类地址(1~126):网络号占前8位,以0开头,主机号占后24位。

B类地址(128~191):网络号占前16位,以10开头,主机号占后16位。

C类地址(192~223):网络号占前24位,以110开头,主机号占后8位。

D类地址(224~339):以1110开头,保留位为多播地址。

E类地址(240~255):以1111开头,保留位今后使用。

 网络号127保留作为换回测试

19.什么是SQL注入?

        所谓的sql注入就是通过某种方式将恶意的sql代码添加到输入参数中,然后传递到sql服务器使其解析并执行的一种攻击手法

实例:

        用户名: ‘or 1 = 1 # 
        密 码:

后台原来的语句:

' " + username + " '

 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注入的方法。攻击者的恶意SQL会被当做SQL的参数而不是SQL命令被执行。

        JDBC中,PrepareStatement,预编译进行参数绑定,?当成占位符。

        在mybatis中,当使用#时,变量是占位符,就是一般我们使用javajdbc的PrepareStatement时的占位符,可以防止sql注入;当使用$时,变量就是直接追加在sql中,一般会有sql注入问题。

21.网络五层模型

         应用层:通过应用进程间的交互来完成特定网络应用。

        运输层:提供端到端的数据传输

        网络层:赋值数据的路由、转发、分片

        数据链路层:负责数据的封装成帧和差错检测,以及MAC寻址。

        物理层:实际最终信号的传输是通过物理层实现的。通过物理介质传输比特流

22.Https和Http的区别(已面)

        1、https协议需要申请CA证书,一般免费证书较少,因而需要一定费用。

        2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl/tls协议加密传输协议。

        3、http和https使用的是完全不同的连接方式,用的默认端口号也不一样,前者是80,后者是443。

        4、http的连接很简单,是无状态的;HTTPS协议是由SSL/TLS+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

        (无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)

23.对称加密和非对称加密的区别?(已面)



 

24.每一层的网络协议

应用层:

        超文本传输协议HTTP

        域名解析协议:DNS

        远程终端协议TELNET

        文件传输协议FTP

        动态主机配置协议DHCP

运输层

        传输控制协议TCP

        用户数据报文协议UDP

网络层:

        网际协议IP

        地址解析协议ARP

        网际控制报文协议ICMP

        外部网关协议BGP

数据链路层:

        自动请求重传协议ARQ

        CSMA/CD协议

        点对点协议PPP

       

25.ARP协议的工作原理?(差不多得了)

ARP协议原理 - 知乎

(MAC地址=物理地址=硬件地址)

        1.首先,每个主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址之间的对应关系。
        2.当源主机要发送数据时,首先检查ARP列表中是否有对应IP地址的目的主机的MAC地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送ARP数据包,该数据包包括的内容有:源主机 IP地址,源主机MAC地址,目的主机的IP 地址。
        3.当本网络的所有主机收到该ARP数据包时,首先检查数据包中的IP地址是否是自己的IP地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的IP和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址。
        4.源主机收到ARP响应包后。将目的主机的IP和MAC地址写入ARP列表,并利用此信息发送数据。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。
广播发送ARP请求,单播发送ARP响应。

(不同局域网的主机的MAC地址查找中间通过路由器实现)

27.谈谈你对ARQ协议的理解?

计算机网络常见知识点&面试题(补充) | JavaGuide

TCP可靠传输:ARQ协议(停止等待、超时重传、滑动窗口、回退N帧、选择重传)_啊a阿花的博客-CSDN博客_arq协议

自动重传请求ARQ 包括停止等待 ARQ 协议和连续 ARQ 协议。

停止等待协议

        它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复 ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组。

在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认。

连续ARQ协议

        连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。

28.谈谈你对停止等待协议的理解?(是这个意思就行)

26-tcp可靠传输——停止等待协议_songly_的博客-CSDN博客_tcp 停止等待协议

        1.在发送无差错的情况下,发送发每次向接收方发送一次分组,接收方就会向发送方发送一个确认分组,当发送方收到确认后即会发送下一个分组。

        2.超时重传:发送方会内置一个超时计时器,时间略长于一个分组发送至接收方,接收方又将确认发送过来的时间,若发送方向接收方发送的数据丢失,计时器到时间则会使发送方重新发送分组直至收到确认。

        3.确认丢失:若接收方发送的确认丢失了,发送方会超时重传同一个分组,接收方收到此重复的分组后将此丢弃并重新发送一次确认。

        4.确认迟到:若接收方发送的确认迟到,而触发了发送方的超时重传后,接收方丢弃重复的分组再次发送确认,使得发送方收到了第二次的确认,但是此时第一次的确认又到了,发送方则会丢弃重复的确认。

29.TCP滑动窗口?(差不多得了,大概是这个意思就行)

TCP滑动窗口 - alifpga - 博客园

        TCP会话的双方都各自维护一个发送窗口和一个接收窗口。

        在等到确认应答返回之前,必须在缓冲区中保留已发送的 数据。

        滑动窗口采用的是累计确认,收到了某个确认应答则意味着前面的数据全部收到了。

        发送窗口大小则要求取决于的接收窗口大小。

        TCP利用滑动窗口解决流量控制的的问题。
窗口的概念
        发送方的发送缓存内的数据都可以被分为4类:

                1. 已发送,已收到ACK

                2. 已发送,未收到ACK

                3. 未发送,但允许发送

                4. 未发送,但不允许发送

                其中类型2和3都属于发送窗口。

        接收方的缓存数据分为3类:

                1. 已接收

                2. 未接收但准备接收

                3. 未接收而且不准备接收

                其中类型2属于接收窗口。
滑动机制

(收到32,33,但是31未收到,发送确认31,表示期望收到的序号)
        发送窗口只有收到发送窗口内字节的ACK确认,才会移动发送窗口的左边界。
        接收窗口只对按序收到的数据中的最高序号给出确认,然后移动窗口的左边界。

30.谈下你对流量控制的理解?(差不多得了,大概意思到位就行)

通俗易懂讲解TCP流量控制机制,了解一下 - 帅地 - 博客园

         发送方的发送窗口不能超过接收方给出的接收窗口大小。

31.什么是粘包?

        TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。

32.TCP粘包是怎么产生的?

        接收方:

        接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。

        若下一包数据到达时上一包数据尚未被用户进程取走,则下一包数据放到接收缓冲区时就接到上一包数据之后,

        而用户进程从接收缓冲区取数据,这样就一次取到了多包数据。


        发送方:

  • TCP 协议是面向字节流的协议,它可能会组合或者拆分应用层的数据;

        TCP为提高传输效率,发送方可能会将多个数据包合在一起发送,这样接收方就收到了粘包数据。

  • 没有定义消息的边界;

  (拆包)TCP协议规定有MSS(最大报文段长度),如果数据包过长就会被分开传输。这样接收方就收到了粘包数据。

33.怎么解决粘包和拆包?

        1.发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
        2.发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就把每个数据包拆分开来。
        3.可以在数据包之间设置边界,如添加特殊符号
 

34.forword和redirect的区别?

是servlet种的两种主要的跳转方式。forward又叫请求转发,redirect叫做重定向

区别:

        服务器浏览器、地址栏,数据共享,效率,次数

两者的区别总结:

(差不多得了)

1. 从地址栏显示来说:

        1)forword在服务器内部进行,服务器直接访问目标地址的 url网址,把里面的东西读取出来,但是浏览器并不知道,因此用forward的话,客户端浏览器的网址不会发生变化的。

        2)redirect服务器 根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,所以地址栏显示的是新的地址

2。 从数据共享来说:

        1)由于在整个定向的过程中用的是同一个request,因此forward会将request的信息带到被重定向的jsp或者servlet中使用。即可以共享request作用域的数据

        2)redirect不能共享

3. 从效率来说:

       1)forword效率高,而redirect效率低

4. 从请求的次数来说:

        forword只有一次请求;而redirect有两次请求,

35.Http方法有哪些?(已面)

36.在浏览器输入url到显示到主页的过程?(已面)

        1.根据域名到DNS中找到IP

        2.根据IP建立TCP连接(三次握手)

        3.连接建立成功发起http请求

        4.服务器响应http请求

        5.浏览器解析HTML代码并请求html中的静态资源(js,css)

        6.关闭TCP连接(四次挥手)

        7.浏览器渲染页面

解析DNS域名

        浏览器查找DNS缓存->操作系统查找DNS缓存->操作系统查找本地host文件->操作系统向本地域名服务器发起请求,查找本地DNS缓存->根域名服务器开始递归查询

        1.浏览器查找自己的DNS缓存,如果有直接返回,如果没有进行步骤二

        2.操作系统查找自己的DNS缓存,如果有直接返回给浏览器,如果没有进行步骤三

        3.操作系统查找自己的本地host文件,如果有返回给浏览器,如果没有则进行步骤四

        4.操作系统向本地域名服务器发起请求,查找本地DNS缓存,如果有,返回给操作系统,然后操作系统返回给浏览器,如果没有进行步骤五

        5.如果本地域名服务器并未缓存该网址映射关系,那么它从根域名服务器开始递归查询。

37.DNS的域名解析过程?

通过域名访问网站时:

        1.浏览器查找自己的DNS缓存(如果有直接返回,如果没有进行步骤二)

        2.操作系统查找自己的DNS缓存(如果有直接返回给浏览器,如果没有进行步骤三)

        3.操作系统查找自己的本地host文件,(如果有返回给浏览器,如果没有则进行步骤四)

        4.操作系统向本地域名服务器发起请求,查找本地DNS缓存(如果有,返回给操作系统,然后操作系统返回给浏览器,如果没有进行步骤五)

        5.如果本地域名服务器并未缓存该网址映射关系,那么它从根域名服务器开始递归查询。

(域名系统(英文:Domain Name System,缩写DNS)是互联网的一项服务。它作为将域名IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。)

39.Https的工作过程?(已面)

在下面

HTTPS工作原理_morris131的博客-CSDN博客

https工作原理-实现过程(详细版)_M-artin.online的博客-CSDN博客_https工作流程

40.Https优缺点?

41.Cookie和session的区别

Cookie和Session的区别 - 简书

背1-6

1)、cookie存储在客户端,SESSION存储在服务器;
2)、cookie机制保持连接,通信时压力在客户端;SESSION机制保持连接,通信时压力在服务器;
3)、SESSION机制更安全,因为cookie存放在客户端,容易被窃取。但是session机制依赖于sessionID,而sessionID保存在cookie中,一旦客户端禁用了cookie,session也将失效;
4)、cookie是以文本的形式存储在客户端,存储量有限(<=4KB);
session可以无限量地往里面添加内容。
5)、Cookie支持跨域名访问,Session不支持跨域名访问;
6)、Cookie可以设置为长期有效,而Session会随会话窗口关闭而失效。

session用于存放登录信息等重要信息,cookie存放其他信息

42.tcp,http的keepalive(差不多得了)

KeepAlive详解 - 简书

43.长连接短连接(差不多得了)

长连接和短链接的区别 - 简书

44.DNS为什么用UDP?

DNS使用TCP还是UDP?_weixin_33989780的博客-CSDN博客

45.http为什么用tcp

tcp优点、udp缺点

46.url和uri之间的区别?(差不多得了,莫背)

uri和url的区别与联系(一看就理解)_sinat_38719275的博客-CSDN博客_uri和url的区别

47.syn洪水攻击

简单说下 SYN FLOOD 是什么-帅地玩编程

48.DDOS攻击

什么是 DDoS 攻击? - 知乎

49.ipv4  ipv6(知道啥是ipv4/ipv6地址、区别)

IPv4和IPv6有什么区别? - 知乎

50.数字签名、数字证书(知道是啥就行)

什么是数字签名和证书? - 简书

51.DNS劫持(差不多得了,没找到好资料,看不懂算了)

 简单说下怎么实现 DNS 劫持-帅地玩编程

JVM

一个社招的金三银四——Java虚拟机面试题总结_技术交流_牛客网

1.JVM主要组成部分,及其作用?JVM的内存结构?(⭐⭐⭐⭐⭐)

Java 内存区域详解 | JavaGuide

说一下 Jvm 的主要组成部分?及其作用?-帅地玩编程

  JVM运行时数据区域

看书

谈谈对运行时数据区的理解?-帅地玩编程

JVM运行时数据区(详解+面试)_计本张天扬的博客-CSDN博客_jvm运行时数据区

虚拟机栈由什么组成?

堆的内存结构?对象什么时候进入幸存者区?

2.JVM堆和栈的区别?

堆和栈的区别是什么?-帅地玩编程

3.为什么要把堆和栈区分出来呢?栈不是也能存储数据吗?

在JVM中,为什么要把堆与栈分离?栈不是也可以存储数据吗?_神奇女侠666的博客-CSDN博客_jvm为什么要分堆和栈

4.Java参数传递时是传值呢?还是传引用?

Java中参数传递(值传递还是引用传递)_緈諨の約錠的博客-CSDN博客

5.Java对象的大小如何计算?

Java 对象的大小是怎么计算的?-帅地玩编程

full gc什么时候发生?

垃圾回收算法有哪些?熟悉的垃圾回收器

对象什么情况下一直不会进入老年代?

6.如何判断垃圾可以回收?(⭐⭐⭐⭐⭐)

 JVM 垃圾回收详解 | JavaGuide

判断垃圾可以回收的方法有哪些?-帅地玩编程

7.被标记为垃圾的对象一定会回收吗?

不一定

任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行,因此下面第二段代码的自救行动失败了。

被标记为垃圾的对象一定会被回收吗?-帅地玩编程

public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("yes,i am still alive:)");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize mehtod executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();
        // 对象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();
        // 因为finalize方法优先级很低,所以暂停0.5秒以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no,i am dead:(");
        }
        // 下面这段代码与上面的完全相同,但是这次自救却失败了
        SAVE_HOOK = null;
        System.gc();
        // 因为finalize方法优先级很低,所以暂停0.5秒以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no,i am dead:(");
        }
    }
}

输出

finalize mehtod executed!
yes,i am still alive:)
no,i am dead:(

8.内存泄漏(⭐⭐⭐⭐⭐)

(1)什么是内存泄漏、内存溢出?

        不再会被使用的对象的内存不能被回收,就是内存泄露

        内存溢出程序向系统申请的内存空间超出了系统能给的。比如内存只能分配一个int类型,我却要塞给他一个long类型,系统就出现内存溢出。

(2)内存泄漏根本原因:

        如果长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露。尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是 Java 中内存泄漏的发生场景。

public class Simple   {
       Object object;
       void method () {
              object = new Object();
        }
}

就像上面的代码,严格意义上就是一种内存泄漏,因为object不再被使用了,但它不会被立即回收,而是得等到Simple对象被释放的时候。
可以这样写

public class Simple   {
       Object object;
       void method () {
              Object object = new Object();
              //使用Object
              object = null;
        }
}

把Object定义为局部变量,并在最后赋值为null

(3)JVM内存泄漏常见的几种情况

JVM内存泄漏的几种常见情况分析_冲就完事了的博客-CSDN博客_jvm内存泄漏

(4)如何避免内存泄漏

  1. 尽量不要使用 static 成员变量,减少生命周期;

  2. 及时关闭(close)资源;

  3. 不用的对象,可以及时手动设置为 null。

9.垃圾回收算法(重点)(⭐⭐⭐⭐⭐)

浅析JVM中常见的垃圾收集算法_果冻.Lee的技术博客_51CTO博客

10.为什么要采用分代垃圾收集算法?(⭐⭐⭐⭐⭐)

为什么要采用分代收集算法?-帅地玩编程

11.老年代和新生代,永久代,什么时候触发垃圾回收算法?

书P129

java堆可以细分为新生代和老年代

新生代:几乎是所有对象出生的地方,较大对象除外等。

老年代:存活时间较长的对象。

MinorGC:新生代进行一次回收。

FullGC(Major):对整个堆进行整理,包括老年代和新生代。

JVM 垃圾回收详解 | JavaGuide

java堆内存详解 - 盛开的太阳 - 博客园

​​

永久代:

        指内存的永久保存区域,主要存放Class 和Meta (元数据)的信息,Class在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class 的增多而胀满,最终抛出OOM异常。

        永久代不在堆中,永久代只是方法区的实现而已。

JAVA8与元数据 :

    在Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。

    元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。

13.内存碎片

什么是内存碎片?如何解决?-帅地玩编程

14.常用的垃圾收集器?(再说,还没看懂)

常用的垃圾收集器有哪些?-帅地玩编程

cms有哪些阶段是stw的?

15.CMS垃圾回收器(⭐⭐⭐⭐⭐)

JVM 垃圾回收详解 | JavaGuide

谈谈你对 CMS 垃圾收集器的理解?-帅地玩编程

        浮动垃圾,指的是并发清理阶段产生的垃圾。因为并发清理阶段用户程序也在运行,产生的垃圾在标记过程之后,所以本次清理过程不会被清理。

G1Z中的young gc和mixed gc区别?

16.G1垃圾收集器(浅显的背一下书上内容,差不多得了)(⭐⭐⭐⭐⭐)

看书

CMS、G1 垃圾回收器 - 掘金

17.谈谈你对内存分配的理解?大对象怎么分配?空间分配担保?

书P129

        空间分配担保极端情况:内存回收后新生代所有对象都存活,需要将老年代进行分配担保,将Survivor无法容纳的对象直接送人老年代。

谈谈你对内存分配的理解?大对象怎么分配?空间分配担保?-帅地玩编程

18.JVM监控工具、故障处理工具?(再说)

jps:虚拟机进程状况工具

jstat:虚拟机统计信息监视工具

jinfo:Java配置信息工具

jmap:Java内存映像工具

jhat:虚拟机堆转储快照分析工具

jstack:Java堆栈跟踪工具

19.如何利用监控工具调优?

如何利用监控工具调优?-帅地玩编程

20.JVM参数,调优参数?(一定要自己用一下)

JVM 的一些参数?-帅地玩编程

21.JVM报错信息?

JVM常见报错信息_魔舞清华的博客-CSDN博客_jvm报错信息

22.类加载(⭐⭐⭐⭐⭐)

类加载过程

类加载机制  

各阶段作用

有哪些类加载器,分别有什么作用

双亲委派模型(⭐⭐⭐⭐⭐)

谈谈你对双亲委派模型的理解?工作过程?为什么要使用-帅地玩编程

类加载方式

自定义类加载器

(基本上全部)

jvm系列(一):java类的加载机制 - 纯洁的微笑 - 博客园

23.怎么打破双亲委派模型?有哪些实际场景是需要打破双亲委派模型的?

怎么打破双亲委派模型?-帅地玩编程

双亲委派模型和破坏场景_u014753478的博客-CSDN博客_打破双亲委派机制的场景

24.类文件结构?(有空再看)

类文件结构详解 | JavaGuide

 39.常量池

详解JVM常量池、Class常量池、运行时常量池、字符串常量池(心血总结)_祈祷ovo的博客-CSDN博客_jvm常量池和字符串常量池

图有毛病,其他挺好。

28.弱引用和强引用、软引用、虚引用

Java四种引用:强引用、5软引用、弱引用和虚引用代码示例与应用_春风化作秋雨的博客-CSDN博客

jvm调优经历,以后有空一定要自己用一下子jvm调优!

jvm常用的参数调优?

MySQL

mysql题目做一做

MySQL夺命15问,你能坚持到第几问?   13、14、15不看

一个社招的金三银四——MySQL面试题总结_技术交流_牛客网

1.sql语句执行流程(差不多得了,是大概的意思就行)

​​

背这个

​​

 Mysql SQL语句执行过程详解 - 简书

2.数据库三范式

​​

 数据库三大范式_凉_ting的博客-CSDN博客_数据库三范式

数据库设计三大范式 - Ruthless - 博客园

3.char和varchar区别(⭐⭐⭐⭐⭐)

char 和 varchar 的区别?-帅地玩编程

mysql索引类型有哪些,一般用哪些?

如果一个表要创建索引,应该注意哪些地方?

索引选择的原则?索引失效的原则?

4.索引理解

谈谈你对索引的理解?-帅地玩编程

5.索引底层用的是什么数据结构

索引的底层使用的是什么数据结构?-帅地玩编程

6.B树和B+树,是什么、区别

B树就是B-树 , 翻译问题!

一文彻底搞懂MySQL基础:B树和B+树的区别_公众号:码农富哥的博客-CSDN博客_mysqlbtree 与b+tree区别

7.Mysql索引为什么不用b树,而用b+树?为什么不用哈希,而用b+树?(⭐⭐⭐⭐⭐)

下面

MySQL为什么要使用B+树索引 - 雪山飞猪 - 博客园

​​

8.聚簇索引和非聚簇索引(⭐⭐⭐⭐⭐)

主键索引就是聚集索引?MySQL 索引类型大梳理_慕课手记

聚簇索引和非聚簇索引 - 扯不断得红尘 - 博客园

9.哈希索引

MySQL: Hash索引 - 简书

10.覆盖索引(差不多得了)

MySQL覆盖索引 - 简书

11.索引分类(差不多得了)

主键索引就是聚集索引?MySQL 索引类型大梳理_慕课手记

12. 组合索引(联合索引)(差不多得了,能扯一扯就行)

 面试官:谈谈你对mysql联合索引的认识? - 知乎

​​

13.联合索引的最左匹配原则(⭐⭐⭐⭐⭐)

​​

MYSQL | 最左匹配原则 - 一个人的孤独自白 - 博客园

14.索引什么时候不会使用?什么时候不推荐建索引?(⭐⭐⭐⭐⭐)

索引失效的7种情况 - liehen2046 - 博客园

15.SQL查询优化(⭐⭐⭐⭐⭐)

SQL优化最干货总结 - MySQL(2020最新版) - 知乎

16.InnoDB和MyISAM区别?(差不多得了,背几点进行)

MyISAM与InnoDB 的区别(9个不同点)_Chackca的博客-CSDN博客_innodb和myisam的区别

17.水平切分  垂直切分(差不多得了)

mysql表的垂直拆分和水平拆分 - 知乎

数据库垂直拆分 水平拆分 - 有梦就能实现 - 博客园

18.主从复制(好多,不会就算了)

看完这篇还不懂 MySQL 主从复制,可以回家躺平了~ - 掘金

工作过程及之前内容

19.分库分表(⭐⭐⭐⭐⭐)

MySQL分库分表_小小渔夫的博客-CSDN博客

20.主从复制涉及到哪几个线程

mysql主从复制原理 - 知乎

​​

21.主从复制的延迟原因以及解决办法?(差不多得了)

MySQL主从复制延迟原因及处理思路 - 云+社区 - 腾讯云

​​​​​​mysql的主从复制延迟问题--看这一篇就够了 - ityml - 博客园

一篇文带你彻底解决mysql的主从复制延迟问题 - 掘金

22.事务四大特性  四大隔离级别   脏读、不可重复读、幻读   MySQL默认隔离级别?(⭐⭐⭐⭐⭐)

读未提交  读已提交  可重复读  串行化

MySQL默认隔离基本:可重复读

​​

幻读指当用户操作某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户读取该范围的数据行时,会发现有新的“幻影” 行。

MySQL数据库事务的四大特性以及事务的隔离级别_哎呦、不错哦的博客-CSDN博客_数据库事务四大特性

如何开启事务?

23.MVCC(⭐⭐⭐⭐⭐)

3.4的第三张图有毛病

看一遍就理解:MVCC原理详解 - 掘金

24.MySQL中的锁(⭐⭐⭐⭐⭐)

MySQL中常见的锁包括:

  1. 行级锁(Row-level Locking):也称为共享锁(Shared Lock),它允许事务读取一行数据,但不允许其他事务修改该行数据。行级锁适用于读多写少的场景。
  2. 表级锁(Table-level Locking):也称为排他锁(Exclusive Lock),它允许事务独占整个表,防止其他事务对表进行任何操作。表级锁适用于写操作频繁的场景。
  3. 页面锁(Page Locking):它是一种特殊的锁,用于锁定数据库中的页面(通常为4KB)。页面锁可以防止其他事务修改同一页上的数据,从而提高并发性能。
  4. InnoDB引擎的行级锁和事务隔离级别:InnoDB是MySQL中最常用的存储引擎之一,它支持多种事务隔离级别,如读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。不同的隔离级别会影响到InnoDB的锁机制,例如在可重复读隔离级别下,所有写操作都需要获取行级锁,而在串行化隔离级别下,所有写操作都需要获取表级锁。

除了上述常见的锁之外,还有一些其他的锁机制,如记录级锁、间隙锁等。不同的锁机制适用于不同的场景,需要根据具体情况来选择合适的锁策略。
 

25.MySQL如何解决幻读?

1.串行化

2.

  • 同时添加间隙锁与行锁称为Next-key lock,注意间隙锁只有在InnoDB的可重复读隔离级别下生效;
  • MVCC只实现读取已提交和可重复读,InnoDB在可重复读的隔离级别下使用MVCC+Next-key lock解决幻读;
  • Next-key lock = 间隙锁+行锁

26.快照读和当前读?

数据库面试题:mysql当前读和快照读(MVCC)_我是方小磊的博客-CSDN博客_数据库当前读

当前读就是读的是当前时刻已提交的数据,快照读就是读的是快照生成时候的数据。

在读未提交隔离级别下,快照是什么时候生成的?

没有快照,因为不需要,怎么读都读到最新的。不管是否提交

在读已提交隔离级别下,快照是什么时候生成的?

SQL语句开始执行的时候。

在可重复读隔离级别下,快照是什么时候生成的?

事务开始的时候

select 语句加锁是当前读

# 共享锁
select a from t where id = 1 lock in share mode;

#排他锁
select a from t where id = 1 for update;

27.MySQL引擎(⭐⭐⭐⭐⭐)

  • Innodb引擎:Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。支持表级锁
  • MyIASM引擎(Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。支持表级锁。
  • MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高。

什么情况使用Innodb,什么情况使用MyIASM?

28.关系型数据库和非关系型数据库(⭐⭐⭐⭐⭐)

非关系型数据库(NOSQL)和关系型数据库(SQL)区别详解 - 云+社区 - 腾讯云

30.Mysql占用cpu过高怎么办?(差不多得了)

mysql占用CPU过高解决 - 知乎

【64期】MySQL 服务占用cpu 100%,如何排查问题? (MySQL面试第七弹) - 知乎

31.慢查询如何排查?(⭐⭐⭐⭐⭐)

select

1.1  1.2.1  慢查询分析只需要知道需要用工具  工具叫什么名字就行

慢查询 - SoyWang - 博客园

32.explain命令(⭐⭐⭐⭐⭐)

获取 select 语句的执行计划

不用背太细,只需要记得explain的参数有几个,分别是做什么的   explain语句是什么 怎么用就行

mysql explain详解 - 云+社区 - 腾讯云

32.mysql问题排查手段?

MySQL 问题排查都有哪些手段?-帅地玩编程

33.数据库优化?除了sql语句优化,还能怎么优化数据库?(⭐⭐⭐⭐⭐)

34.select *优化,避免子查询优化,避免in优化你具体是在哪些场景使用?

35.为什么select *比较慢?为什么子查询、in比较慢?(⭐⭐⭐⭐⭐)

36.sql语句各个关键词的执顺序?

37.回表有了解过吗?(⭐⭐⭐⭐⭐)

38.如何回滚?

Redis

1.Redis应用场景  (⭐⭐⭐⭐⭐)

​​

社交网络

点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。

消息队列

消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。

​​

2.redis基本数据类型(redis通过key获取值。。。)  最少记前五种(⭐⭐⭐⭐⭐)

基本数据类型

1、String:最常用的一种数据类型,String类型的值可以是字符串、数字      或者二进制等,但值最大不能超过512MB。

2、Hash:Hash 是一个键值对集合,存储键值对。

​​

3、Set:无序去重的String集合。Set 提供了交集、并集等方法,对于实现共同好友、共同关注等功能特别方便。

​​

为什么可以去重?

4、List:有序可重复的String集合,底层是依赖双向链表实现的。

​​

5、SortedSet(Zset):sorted set 存储有序的元素。每个元素都有个 score,按照 score 从小到大排序。score 相同时,按照 key 的ASCII码排序。

问:底层数据结构是什么?排序的时间复杂度是多少?

​​

应用场景

排行榜

特殊的数据类型

1、Bitmap:位图,可以认为是一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在 Bitmap 中叫做偏移量。Bitmap的长度与集合中元素个数无关,而是与基数的上限有关。

2、Hyperloglog。HyperLogLog 是用来做基数统计的算法,其优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。典型的使用场景是统计独立访客。

3、Geospatial :主要用于存储地理位置信息,并对存储的信息进行操作,适用场景如定位、附近的人等。

Redis缓存和内存有什么关系?

Redisson有什么优势?

3.Redis为什么这么快(⭐⭐⭐⭐⭐)

4.缓存穿透、缓存击穿、缓存雪崩(重点)(⭐⭐⭐⭐⭐)

什么是缓存雪崩、缓存击穿、缓存穿透? - 知乎

5.如何保证缓存与数据库的一致性?(重点)(⭐⭐⭐⭐⭐)

redis缓存一致性 - huonan - 博客园

6.Redis的持久化方式 (⭐⭐⭐⭐⭐)

RDB和AOF的区别?Redis宕机哪种恢复的比较快?

Redis的两种持久化方式 - 知乎

7.Redis淘汰策略 (⭐⭐⭐⭐⭐)

当Redis内存超出物理内存限制时,内存数据会开始和磁盘产生频繁的交换,使得性能急剧下降。为了限制内存的使用,Redis提供参数maxmemory来限制最大内存,当内存超出后,会有以下策略(maxmemory-policy)来淘汰key以腾出空间:

volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。LRU算法
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。LFU算法
allkeys-lru:从数据集中挑选最近最少使用的数据淘汰 。LRU算法
allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。LFU算法
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
no-enviction(驱逐):禁止驱逐数据,这也是默认策略。意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失。

8.Redis过期键的删除策略?(⭐⭐⭐⭐⭐)

定时删除
        ​ 创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作。

        当前时间和expires中对过期时间一致时,定时器触发删除。

​         优点:节省内存,到时间就删除,快速释放不必要的内存占用。

        ​ 缺点:CPU压力较大,无论CPU此时负载情况如何,均占用CPU来执行删除,会影响Redis服务器的响应时间和指令吞吐量。

时间换空间

惰性删除
        ​ 数据到达过期时间,不做处理,等下次访问该数据等时候执行删除。

        在获取数据时,内部会调用expirelfNeeded()方法,来确定数据是否到期。

未到期,返回数据。
到期,删除,返回不存在。
​         优点:节约cpu性能,到了必须删除的时候才执行删除。

        ​ 缺点:内存压力较大,会出现长期占用内存的数据。

空间换时间

定期删除

        每隔一定的时间,会扫描一定数量的数据库数据,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

Redis中同时使用了惰性过期和定期过期两种过期策略。

9.Redis产生Hash冲突怎么办?(差不多得了)

redis面试:hash冲突怎么办_OceanStar的学习笔记的博客-CSDN博客_redis解决hash冲突

10.在生成RDB期间,Redis可以处理写请求吗?

在生成 RDB 期间,Redis 可以同时处理写请求么?-帅地玩编程

11.Redis(重启)如何兼顾性能又保证数据尽可能少丢失?

如何实现数据尽可能少丢失又能兼顾性能呢?-帅地玩编程

12.Redis线程模型?(不想看就算了)

Redis线程模型-帅地玩编程

Redis线程模型 - 简书

13.Redis分区 (是什么、好处、实现方式、缺点)

Redis 分区

14.Redis并发竞争key问题?

Redis 并发竞争key问题如何解决? - 云+社区 - 腾讯云

分布式锁、时间戳、消息队列这三种就行

15.Redis和Memcached区别?优势(背几个差不多得了)

Redis相比Memcached有哪些优势?-帅地玩编程

16.如何选择合适的持久化方式?

如何选择合适的持久化方式-帅地玩编程

17.Redis事务  

没有回滚、不保证原子性、无隔离级别、一般用Lua脚本

跟我一起学Redis之Redis事务简单了解一下 - 云+社区 - 腾讯云

18.Redis缓存预热  (⭐⭐⭐⭐⭐)

什么是缓存预热?-帅地玩编程

19.Redis缓存降级  (⭐⭐⭐⭐⭐)

能说一说是什么就行了

redis面试:什么是缓存降级_OceanStar的学习笔记的博客-CSDN博客_redis缓存降级

17.Redis多线程? 

Redis在6.0版本引入多线程机制

18.Redis为何引入多线程?

Redis 6.0为何引入多线程?-帅地玩编程

主要就背:

        随着互联网的飞速发展,互联网业务系统所要处理的线上流量越来越大,Redis的单线程模式会导致系统消耗很多 CPU 时间在网络 I/O 上从而降低吞吐量,要提升 Redis的性能有两个方向:

  • 优化网络 I/O 模块
  • 提高机器内存读写的速度

Redis支持多线程主要就是两个原因:

  • 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核
  • 多线程任务可以分摊 Redis 同步 网络IO 读写负荷

Redis  IO多线程模型只用来处理网络读写请求,对于 Redis 的读写命令,依然是单线程处理

19.Redis多线程实现机制?主线程和IO多线程如何协作?(不想看就算了)

​​

主要流程

  1. 主线程负责接收建立连接请求,获取 socket 放入全局等待读处理队列;
  2. 主线程通过轮询将可读 socket 分配给 IO 线程;
  3. 主线程阻塞等待 IO 线程读取 socket 完成;
  4. 主线程执行 IO 线程读取和解析出来的 Redis 请求命令;
  5. 主线程阻塞等待 IO 线程将指令执行结果回写回 socket完毕;
  6. 主线程清空全局队列,等待客户端后续的请求。

该设计有如下特点

  • IO 线程要么同时在读 Socket,要么同时在写,不会同时读或写。
  • IO 线程只负责读写 Socket 解析命令,不负责命令处理。

20.主从模式、哨兵模式、集群模式(⭐⭐⭐⭐⭐)

需要知道是什么、以及机制(原理)、优缺点以后再说

redis系列之——高可用(主从、哨兵、集群) - 知乎

21.Redis主从复制数据产生延迟怎么办?(可能看不懂,没找到好答案)

redis主从复制数据延迟解决方案 - 掘金

22.Redis主从复制数据丢失?

(数据复制、同步是什么,弄不懂)

Redis主从复制丢失数据的情况分析 - 简书

23.分布式锁(⭐⭐⭐⭐⭐)

分布式锁特性:

        不背 高可用性 

        记    可重入性:同一个线程可以多次获取该锁(就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁

         具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

24.布隆过滤器(⭐⭐⭐⭐⭐)

误判率别背:有毛病

布隆过滤器,这一篇给你讲的明明白白-阿里云开发者社区

        (差不多得了,别背)误判:映射函数是哈希函数,可能产生哈希冲突(不同的数据映射位相同,都为1),当存入数据较多时,就可能出现,未存入数据经过多个映射之后,全部所对应位数为1,但是实际上不存在的情况。

25.IO多路复用(没看懂,再说吧,可能有毛病)(常问)

IO多路复用的三种机制Select,Poll,Epoll - 简书

26.Redis 宕机怎么办

持久化、主从、哨兵、集群模式  来回答

27.Redis如何存对象(差不多得了,知道怎么存就行了)

Redis 存储对象信息是用 Hash 还是 String - 又拍云 - 博客园

Redis存储对象的三种方式_kainx的博客-CSDN博客_redis存储对象

28.了解二叉平衡搜索树

(能讲个大概就行)

什么是平衡二叉树(AVL) - 知乎

29.MySQL和Redis的区别?

30.Redis单线程为什么快?

Git

1.常用命令

背git的内容

git reset中-hard和soft区别,默认是哪个?

git是怎么用的?

git合并分支冲突怎么解决?

Docker

1.docker的优缺点?

2.docker部署和虚拟机部署有什么区别?

3.docker如何部署Java项目?

Mybatis

1.介绍

动态Sql语句的持久层框架。Mybatis可以将Sql语句配置在XML文件中,避免将Sql语句硬编码在Java类中。与JDBC相比:
1)Mybatis通过参数映射方式,可以将参数灵活的配置在SQL语句中的配置文件中,避免在Java类中配置参数
2)Mybatis可以通过Xml配置文件对数据库连接进行管理


2.传参的几种方式(⭐⭐⭐⭐⭐)

#{}和$()的区别

MyBatis常见面试题总结 | JavaGuide

#{}(使用预处理【预编译】的处理,不存在sql注入的安全问题)或者${}(存在sql注入的安全问题)

MyBatis 执行批量插入,能返回数据库主键列表吗?

MyBatis 动态 sql 是做什么的?都有哪些动态 sql?能简述一下动态 sql 的执行原理不?

八股文第五版本也看

MyBatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?

3.如何使用?

4.mybatis的分页如何使用?

5.mybatis怎么实现多表联查?返回结果是什么?

6.mybatis缓存 一级 二级概念与实现原理? 并举例

7.既然有了一级缓存,那么为什么要提供二级缓存呢?

10.一级缓存如果数据库更新了会不会再去查库?

8.mybatis优缺点

7.mybatis 执行过程?

10.mybatis逻辑分页和物理分页的区别

11.当实体类的属性名和表中的字段名称不一样怎么办?

12.怎么获取插入时自动生成的(主)键值?

13.在mapper中如何传递多个参数?

14.mybatis-plus和mybatis的区别和联系?

15.spring boot和mybatis是怎么结合的?

16.mybatis的循环依赖?

17.mybatis实现一对多查询?

18.mybatis如何关联查询?

Spring

1.介绍Spring IOC和Spring Aop?(⭐⭐⭐⭐⭐)

什么是 IOC
        Ioc—Inversion of Control,即控制反转,不是什么技术,⽽是⼀种设计思想。在 Java 开发 中, Ioc 意味着将你设计 好的对象交给容器控制,⽽不是传统的在你的对象内部直接控制。
IOC 的好处
        使⽤IOC 后,我们不需要⾃⼰去创建某个类的实例,⽽由 IOC 容器去创建,当我们需要使⽤某个对象时,直接到容器中去获取就可以了。

 Aop:面向切面编程, 基于动态代理的,可以使用jdk,cglib两种代理方式。
         Aop就是动态代理的规范化, 把动态代理的实现步骤,方式都定义好了, 
         让开发人员用一种统一的方式,使用动态代理。

动态代理的作用:
    1)在目标类源代码不改变的情况下,增加功能。
    2)减少代码的重复
    3)专注业务逻辑代码
    4)解耦合,让你的业务功能和日志,事务非业务功能分离。

2.spring常用的一些注解?Autowired和Resource的区别(⭐⭐⭐⭐⭐)

/**
 * @Component: 创建对象的,等同于<bean>的功能
 *      属性:value 就是对象的名称,也就是bean的id值,
 *          value的值是唯一的,创建的对象在整个spring容器中就一个
 *      位置:在类的上面
 *  @Component(value = "myStudent")等同于
 *  <bean id="myStudent" class="com.bjpowernode.ba01.Student" />
 *
 *  spring中和@Component功能一致,创建对象的注解还有:
 *  1.@Repository(用在持久层的上面):放在dao的实现类上面,
 *              表示创建dao对象,dao对象是能访问数据库的。
 *  2.@Service(用在业务层的上面):放在service的实现类上面,
 *              创建service对象,service对象是做业务处理,可以有事务等功能的。
 *  3.@Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,
 *              控制器对象,能够接受用户提交的参数,显示请求的处理结果。
 *      以上三个注解的使用语法和@Component一样的。都能创建对象,但是这三个注解还有额外的功能。
 * @Repository,@Service,@Controller是给项目的对象分层的。
 */
@Component(value = "myStudent")
public class Student {

     /**
     * @Value: 简单数据类型
     *      属性:value是String类型的,表示简单数据类型的属性值
     *      位置:1.在属性定义的上面,无需set方法,推荐使用
     *           2.在set方法的上面 ,这种比较少用
     */
    @Value(value = "张飞")
    String name;
    @Value(value = "29")
    private Integer age;

    /**
     * 引用类型注入
     * @Autowired: spring框架提供的注解,实现引用类型的赋值
     * spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName,buType
     * @Autowired:默认使用的是buType自动注入
     *
     * 位置:1)在属性定义的上面,无需set方法,推荐使用
     *      2)在set方法的上面
     *
     * 如果要使用byName方式,需要做的是:
     * 1.在属性上面加入@Autowired
     * 2.在属性上面加入@Qualifier(value="bean的id") :表示使用指定名称的bean完成赋值
     */
    @Autowired//位置1
    private School school;

    /**
     *引用类型
     * @Resource: 来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型
     *              使用的也是自动注入原理,支持byName , byType .默认是byName
     *  位置:1.在属性定义的上面,无须set方法,推荐使用
     *       2.在set方法上面
     *
     *  如果让@Resource只使用byName方式,需要增加一个属性 name
     *  name的值是bean的id(名称)
     *      如: @Resource(name="mySchool")
     */
}

  • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
  • Autowired 默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为 byName(根据名称进行匹配)。
  • 当一个接口存在多个实现类的情况下,@Autowired@Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired 可以通过 @Qualifier 注解来显式指定名称,@Resource可以通过 name 属性来显式指定名称。
  • @Autowired 支持在构造函数、方法、字段和参数上使用。@Resource 主要用于字段和方法上的注入,不支持在构造函数或参数上使用。

3.什么是Spring?

Spring常见面试题总结 | JavaGuide

4.SpringBootSpringMVCSpring有什么区别?

        看代码随想录最强八股文第五版
5.bean的生命周期?

Spring常见面试题总结 | JavaGuide

6.Spring包含的模块有哪些?

Spring常见面试题总结 | JavaGuide

7.将一个类声明为Bean的注解有哪些?

Spring常见面试题总结 | JavaGuide

@Component 和 @Bean 的区别是什么?

AspectJ 定义的通知类型有哪些?

10.动态代理的方式?

        看代码随想录最强八股文第五版

Bean 的作用域有哪些?

11.说一个注解,这个注解是如何起作用的?

15.如何在spring中使用依赖注入?

16.spring中的自动配置怎么做的?

17.spring中有哪些常见的配置文件?

18.IOC的装配过程?

19.spring动态代理是如何生成的,jdk动态代理和cglib的区别?AOP什么时候用JDK动态代理,什么时候用cglib代理?

20.springAOP原理?AOP底层实现是啥?

21.Spring事务隔离级别,传播机制?

22.怎么用SpringAOP收集日志,口述下具体怎么写?

 23.循环依赖

循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A

循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖

①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象

②二级缓存:缓存早期的bean对象(生命周期还没走完)

③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的

解决流程

第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories  

第二,A在初始化的时候需要B对象,这个走B的创建的逻辑

第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories  

第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键

第五,B通过从通过二级缓存earlySingletonObjects  获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects  

第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects 

第七,二级缓存中的临时对象A清除 

作者:荒野大飞
链接:https://www.nowcoder.com/discuss/594538886861242368
来源:牛客网

24.configuration和component的区别?

25.before/after里面代码怎么写?可以作用在类上吗?怎么用?

SpringMVC

1.理解

        MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。

Spring MVC 的核心组件有哪些?

SpringMVC 工作原理了解吗?

4.SpringMVC中常⽤的注解有哪些,作⽤是什么

八股文第五版

5.什么是SpringMVC 拦截器?

八股文第五版

SpringBoot

1.核心注解(⭐⭐⭐⭐⭐)

@SpringBootApplication
通常用在启动类上,申明让spring boot自动给程序进行必要的配置,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:

        1. @SpringBootConfiguration

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

        2. @EnableAutoConfiguration

        打开自动配置的功能,也可以关闭某个自动配置的选项。

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

        3. @ComponentScan

        Spring组件扫描功能,让spring Boot扫描到Configuration类并把它加入到程序上下文。

2.介绍  特点(⭐⭐⭐⭐⭐)

3.SpringBoot的启动流程?自动配置(⭐⭐⭐⭐⭐)

4.SpringBoot的start机制?(⭐⭐⭐⭐⭐)

5.如何管理Bean对象?(⭐⭐⭐⭐⭐)

6.使用过哪些注解?

7.springboot如何接收前端发来的JSON数据。

8.springboot是怎么搭建的?

9.常用注解?

SpringCloud

1.SpringBoot和SpringCloud的区别?

2.SpringCloud五大组件?

操作系统

1.同步 异步 阻塞 非阻塞(差不多得了)

同步、异步、阻塞、非阻塞的概念-帅地玩编程

IO-同步、异步、阻塞、非阻塞 - 云+社区 - 腾讯云(可能有毛病,建议非要求别讲)

2.进程的状态

三种基本状态:就绪、运行、终止

进程的五种基本状态 - 操作系统_定雨的博客-CSDN博客_进程的状态

3.进程调度算法

看书上

先来先服务  短作业优先 优先级调度 高响应比优先调度算法  时间片轮转调度算法 多级反馈队列

4.进程通信方式?(记4、5个吧,差不多得了)

1 2 3 5 6 7 

1. 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。


2. 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。


3. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。


4. 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。


5. 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。


6. 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及机器的进程通信。


7. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

5.死锁

(1)是什么?(差不多是这个意思就行了)

死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 如下图所示:如果此时有一个线程 A,已经持有了锁 A,但是试图获取锁 B,线程 B 持有锁 B,而试图获取锁 A,这种情况下就会产生死锁。

死锁两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

​​

(2)死锁产生的必备条件、如何避免、预防、解除

学过操作系统的朋友都知道产生死锁必须具备以下四个条件:

  1. 互斥条件:该资源任意一个时刻只由一个线程占用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源在使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

如何预防死锁? 破坏死锁的产生的必要条件即可:

  1. 破坏请求与保持条件 :一次性申请所有的资源,每个进程在开始执行时就申请他所需要的全部资源。
  2. 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果超过一定时间申请不到,可以主动释放它占有的资源。
  3. 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

如何避免死锁?

避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。

安全状态 指的是系统能够按照某种线程推进顺序(P1、P2、P3.....Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,使每个线程都可顺利完成。称<P1、P2、P3.....Pn>序列为安全序列。

如何解除死锁?

怎么解除死锁?-帅地玩编程

6.缓冲区溢出

什么是缓冲区溢出?有什么危害?-帅地玩编程

7.页面置换算法

看书

最佳置换算法  先进先出  LRU   LFU   Clock

8.银行家算法

书121银行家算法

9.磁盘调度算法

书233

10.书第四章看看(没空就算了)

分页分段的区别

至少记得1 2 3

分页与分段的区别?-帅地玩编程

11.书第五章内容

有空去看看

物理地址、逻辑地址、虚拟内存的概念-帅地玩编程

12.典型的锁

几种常见的线程锁 - 简书

java中的各种锁详细介绍 - JYRoy - 博客园

Linux

Linux cp 命令详解-帅地玩编程

1.常用的linux命令(⭐⭐⭐⭐⭐)

查看内存、查看日志、查找关键字、运行Java程序、怎么抓一个日志、文件指令、查询某个文件、Linux如何查看TCP状态

查看使用情况

Linux查看某个端口是否被占用

linux查看进程、杀进程、创建文件、文件夹

linux指令  如给文件目录授权,查进程,切换用户,cat和vim和vi的区别

linux文件相关的命令、如何部署项目

韩顺平图解Linux  第8章 第9章 第14章 第21章

面试

超级简历WonderCV - HdR推荐简历模板,智能简历制作工具,专业中英文简历模板免费下载

简历

​​​​​​请求大佬帮我看看简历存在那些问题?投了30多份简历都没过初审_我要提问_牛客网

【程序厨】一分命中率接近 100% 的校招简历_职业发展_牛客网

简历这样写,你的面试已经成功了99%_职业发展_牛客网

有没有公司招前端暑假实习勒,请大牛看看这简历怎么改,双非二本_招聘信息_牛客网

[经验贴]技术如何写一份脱俗的简历_技术交流_牛客网

其他

1.对公司有什么了解?

2.有女朋友吗?

4.项目是自己写的吗?

5.自我介绍

7.评价一下自己

8.怎么证明自己能力好

9.职业规划,说了比较中长期的规划

10.短期的规划

11.最近看的书籍,有什么感想,书的作者是谁

12.平时怎么学习的?

13.最近打算学什么?

14.个人的缺点

15.介绍一下你的大学

16.个人性格,内向还是外向?

17.平时的兴趣爱好,空闲时间怎么过

18.为什么想来到xx地方?

21.你对公司加班的看法

22.你还有什么问题要问的吗

23.何时可以到岗?

24.如果在公司中遇到问题或者困难,你是怎么解决的?

25.自己是如何学习Java的?

26.自己的人生职业规划?

27.为什么不选择考研?

28.你的优势是什么?

29.如何排除一个问题?

30.有对自己的技术规划吗?

31.最近在看什么技术书籍或者文章吗?

社招

1.为什么离职?

2.前几份工作主要工作职责是什么?公司主要是负责什么方面的?

项目

1.遇到了什么问题,怎么解决的?

2.在项目中收获了什么?

3.介绍一下这个项目

4.讲一下项目,在项目中担任的角色?

5.《黑马头条》XXL-JOB做什么?

数据结构

栈和队列的区别

队列:先进先出,这意味着最先添加到队列的元素将是第一个被移除的元素。
栈:先进后出,这意味着最后添加到栈的元素将是第一个被移除的元素。

队列:只能在表的一段进行插入,并在另一端进行删除
栈:只能在表的一端插入和删除

如何用两个栈实现队列

【数据结构】用栈实现队列-CSDN博客

链表和数组的区别

请回答数组和链表的区别,以及优缺点

栈、队列的基本使用

【java】栈(Stack)的基本使用_java stack用法-CSDN博客

【Java】Java队列Queue使用详解_java queue-CSDN博客

算法

重点

最主要记忆冒泡、选择、插入、快速、堆排序  其余的记忆一下时间空间复杂度即可

java实现10种排序算法_jdk的排序算法-CSDN博客

二分查找

【二分查找】详细图解_二分查找法流程图-CSDN博客

题目

1.编辑距离

微软又考了这道题

dp[i][j]表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]

初始化:dp[i][0] = i;

                同理dp[0][j] = j;

两层for循环从1开始遍历。

递推公式:

if (word1[i - 1] == word2[j - 1]) {
            dp[i][j] = dp[i - 1][j - 1];
        }
        else {
            dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
        }

 2.最长公共子距离

dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]

初始化:

        dp[i][0] = 0;

        同理dp[0][j]也是0。

if (text1[i - 1] == text2[j - 1]) {
    dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}

手撕

1.堆排序手撕

堆排序

        堆排序_guanlovean的博客-CSDN博客_堆排序

        剑指 Offer II 076. 数组中的第 k 大的数字

手写大根堆,在数组中操作

        构建大顶堆

        然后堆顶和末尾互换,并调用判断交换函数。

        时刻注意len,尚未排序的元素个数,交换判断函数可能有递归调整子节点。

        第1个叶节点下标 len/2

        最后一个非叶节点len/2-1 

       第i个节点的左子节点2i+1

                          右子节点2i+2

        注意 发生交换时,需要递归调整子节点

        

        函数:

        main(){

                构建大顶堆函数

                交换堆顶0和末尾节点(从len-1到1),判断交换函数

        }

        构建大顶堆(){

                从最后一个非叶节点到第一个,使用判断交换函数

        }

        判断交换函数(){

                判断节点交换

                是否递归判断子节点

        }

2.LRU手撕

3.LFU手撕

4.快排手撕

5.各种排序

6.十大排序

十大排序算法简介-帅地玩编程

归纳

1.哈希表

        一般哈希表都是用来快速判断一个元素是否出现集合里

        题目:

                160相交链表

                1. 两数之和

2.二分法

        有序或者貌似有序数组查找某个元素,可以使用二分

        题目:  33. 搜索旋转排序数组

        注意边界  判断条件是while(left<=right)

3.链表

        为了更加方便,我们可以虚拟头节点,即在原来的头节点前面加上一个节点,方便操作。

4.滑动窗口

        遍历并维持一个窗口

        题目:

                3. 无重复字符的最长子串

5.动态规划

        当前的操作是否和之前的操作有关

对单单只有一个数组:

        需要动态规划时,如果选用二维dp,dp[i][j]一般表示,从字符串i到j,左下角一般不用。

        题目:5. 最长回文子串  只有一个数组需要我们判断其中最长回文子串

        

两个数组

        一般的话二维dp数组

        dp[i][j],数组1从0到i 和数组2从0到j 

        剑指 Offer II 095. 最长公共子序列

6.深度优先搜索

使用栈,函数就是天然栈

200. 岛屿数量

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值