Java面试高频题

Java高频面试题

一、Java基础知识

1、面向对象的特征(了解)

特征:封装、继承、多态

  1. 封装:把对象的状态信息(属性)隐藏在内部,不允许外部对象直接访问,但可以提供允许被外部访问的方法来操作属性。

  2. 继承:子类继承父类的属性和行为,并能根据自己的需求扩展出新的行为,提高了代码的复用性。

  3. 多态:不同对象对同一个方法名可以做出不同的响应。

    它一般的实现方式是继承和实现。

    父类类型 变量名 = new 子类对象 ; 
    
    变量名.方法名();
    

2、 Java的基本数据类型有哪些(了解)

Java基本类型共有八种,基本类型可以分为三类,字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double

3、JDK JRE JVM 的区别 (必会)

JDK:java 开发工具包,包含了JRE、编译器以及许多调试、分析等工具软件,它能够创建和编译Java程序。

JRE:java运行时环境,是运行Java已编译程序所必须的软件环境,包含了JVM和Java标准类库,JRE提供给只想运行Java程序的用户使用,不能用于创建和编译Java程序。

JVM:java 虚拟机,用来运行Java字节码文件,是Java跨平台的关键,因为屏蔽了不同操作系统之间的差异,可以让相同的Java程序在不同的操作系统上运行出相同的结果。

4、 重载和重写的区别(必会)

重载: 发生在同一个类中;方法名必须相同,参数类型不同.个数不同.顺序不同,方法返回值和访问修饰符可以不同;发生在编译时。

重写: 发生在父子类中;方法名.参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。

5、Java中==和equals的区别(必会)

== 的作用:
  基本类型:比较的就是是否相同
  引用类型:比较的就是地址值是否相同

equals 的作用:
引用类型:默认情况下,比较的是地址值
特:String、Integer、Date这些类库中equals被重写,比较的是内容而不是地址!

6、 请解释字符串比较之中 “ == ” 和 equals() 的区别?

答: ==:比较的是两个字符串内存地址(堆内存)的数值是否相等,属于数值比较; equals():比较的是两个字符串的内容,属于内容比较。

7、 String、StringBuffer、StringBuilder三者之间的区别(必会)

String 字符串常量(线程安全)

StringBuffer 字符串变量(线程安全)

StringBuilder 字符串变量(非线程安全)

String :String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[] ,String对象是不可变的,也就可以理解为常量,线程安全。

AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。

StringBuffer:对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。

StringBuilder:并没有对方法进行加同步锁,所以是非线程安全的。

小结:

(1)如果要操作少量的数据用 String;

(2)多线程操作字符串缓冲区下操作大量数据用 StringBuffer;

(3)单线程操作字符串缓冲区下操作大量数据用 StringBuilder。

8、 接口和抽象类的区别是什么?(必会)

  1. 关键字:extends 来继承; implements 来实现接口。
  2. 实现数量:单继承,多实现。
  3. 构造函数:抽象类可以有构造函数;接口不能有。
  4. main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
  5. 访问修饰符:抽象类中的方法可以是任意访问修饰符;接口中的方法默认使用 public 修饰
  6. 成员变量:抽象类中的成员变量可以是任何类型的;接口中的成员变量只能是public static final类型

9 、string常用的方法有哪些?(了解)

indexOf():返回指定字符的索引。

charAt():返回指定索引处的字符。

replace():字符串替换。

trim():去除字符串两端空白。

split():分割字符串,返回一个分割后的字符串数组。

getBytes():返回字符串的 byte 类型数组。

length():返回字符串长度。

toLowerCase():将字符串转成小写字母。

toUpperCase():将字符串转成大写字符。

substring():截取字符串。

equals():字符串比较。

10、 什么是单例模式?有几种?(必会)

单例模式:某个类的实例在 多线程环境下只会被创建一次出来。

实现单例模式的思路:私有构造方法、确保对象实例只有一个、以静态方法返回实例

单例模式有饿汉式单例模式、懒汉式单例模式

饿汉式:线程安全,一开始就把对象实例创建好,用的时候直接用,但启动慢,浪费内存。

因为饿汉模式浪费内存,所以就有了懒汉式,懒汉式:非线程安全,先不创建类的对象实例,等需要的时候再创建,(懒汉模式在并发情况下可能引起的问题)但是这种模式在并发情况下会出现创建多个对象的情况。

解决方案一:双重检查加锁(DCL)

所以懒汉模式需要加上锁synchronized (Singleton.class) 来控制类只允许被实例化一次。

先验证对象是否创建,只有当对象未创建的时候才上锁,对象创建以后都不会上锁,这样有效的提升了程序的效率,也可以保证只会创建一个对象的实例。

但是DCL在并发情下也会存在一个问题,指令重排;

new Singleton()的时候JVM会生成三个指令

指令1:分配内存空间。

指令2:调用构造器,初始化对象属性。

指令3:构建对象引用指向内存。

发生指令重排

1、执行指令1:分配对象内存,

2、执行指令3:构建对象引用指向内存。

3、然后正好这个时候CPU 切到了线程2工作,而线程2此时也调用getInstance获取对象,那么线程2将执行下面这个代码 if (singleton == null),此时线程2发现对象不为空(因为线程1已经创建对象引用并分配对象内存了),那么线程2会得到一个没有初始化属性的对象(因为线程1还没有执行指令2)。

所以在这种情况下,双检测锁定的方式会出现DCL失效的问题。

为避免指令重排,加volatile

方式三:静态内部类

11、 反射(了解)

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

获取 Class 对象的 3 种方法 :

  1. 调用某个对象的 getClass()方法

    Person p=new Person(); 
    
    Class clazz=p.getClass(); 
    
  2. 调用某个类的 class 属性来获取该类对应的 Class 对象

    Class clazz=Person.class; 
    
  3. 使用 Class 类中的 forName()静态方法(最安全/性能最好)

     Class clazz=Class.forName("类的全路径"); (最常用)
    

12、 jdk1.8的新特性(高薪常问)

  1. Lambda 表达式 :允许把函数作为一个方法的参数。
  2. 方法引用:允许直接引用已有 Java 类或对象的方法或构造方法。
  3. 函数式接口:有且仅有一个抽象方法的接口
  4. 接口允许定义默认方法和静态方法:允许接口中存在一个或多个默认非抽象方法和静态方法。
  5. Stream API:真正的函数式编程风格引入到 Java 中。这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。
  6. 日期/时间类改进
  7. Optional 类:是一个可以为 null 的容器对象。如果值存在则 isPresent()方法会返回 true,调用 get()方法会返回该对象。
  8. Base64 实现:Java 8 内置了 Base64 编码的编码器和解码器。

13、 Java的异常(必会)

Throwable是Error和Exception的父类

  • ErrorError 属于程序无法处理的错误 ,一旦出现,程序将被迫停止运行。

  • Exception :Exception 又可以分为

    1.运行时异常:都是RuntimeException类及其子类异常。

    (例如)如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,

    这些异常是不检查异常,Java编译器不会检查它,也就是说,即使没有用try-catch、throws,也会编译通过。

    这些异常一般是由程序逻辑错误引起的,应该尽可能避免。

    2.非运行时异常 (编译异常):是RuntimeException以外的异常

    (例如)如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

    必须进行处理的异常,如果不处理,程序就不能编译通过。

14、 BIO、NIO、AIO 有什么区别?(高薪常问)

BIO:Block IO 同步阻塞式 IO,是传统 IO模型;一个线程触发IO操作后必须等待这个 IO操作执行完成,期间不能做其他工作;它的特点是模式简单使用方便,并发处理能力低。

NIO:New IO 同步非阻塞 IO,是传统 IO 的升级;一个线程触发 IO操作后它可以立即返回,但是它需要不断轮询去获取执行结果;

AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,异步非堵塞 IO ;一个线程触发IO操作后立即返回去做其他工作,内核系统在IO操作执行完成后会通知该线程。

15、 Threadlocal的原理(高薪常问)

Threadlocal:线程本地变量,为共享变量在每个线程中创建一个副本,每个线程都可以访问自己内部的变量副本。通过threadlocal保证线程的安全性。

ThreadLocal中有ThreadLocalMap,ThreadLocalMap的key为当前ThreadLocal对象,而value对应线程的变量副本

内存泄露产生的原因:ThreadLocalMap使用ThreadLocal的弱引用作为key,Key(ThreadLocal)势必会被GC回收,这样就会导致key为null, 而value还存在着强引用,因此造成内存泄露。所以使用完ThreadLocal之后,记得调用remove方法。在不使用线程池的前提下,即使不调用remove方法,线程的"变量副本"也会被gc回收,即不会造成内存泄漏的情况。

16、 同步锁、死锁、乐观锁、悲观锁 (高薪常问)

同步锁:当多个线程访问同一个数据时,很容易出现问题,为避免这种情况,我们要保证线程同步互斥,(也就是并发执行多个线程,在同一个时间内只允许一个线程访问共享数据)。Java中可以使用synchronized关键字来取得一个对象的同步锁。

死锁:
造成死锁的⼏个原因:
⼀个资源每次只能被⼀个线程使⽤
⼀个线程在阻塞等待某个资源时,不释放已占有资源
⼀个线程已经获得的资源,在未使⽤完之前,不能被强⾏剥夺
若⼲线程形成头尾相接的循环等待资源关系
这是造成死锁必须要达到的 4 个条件,如果要避免死锁,只需要不满⾜其中某⼀个条件即可。⽽其中前 3个条件是作为锁要符合的条件,所以要避免死锁就需要打破第4 个条件,不出现循环等待锁的关系。
在开发过程中:
要注意加锁顺序,保证每个线程按同样的顺序进⾏加锁
要注意加锁时限,可以针对所设置⼀个超时时间
要注意死锁检查,这是⼀种预防机制,确保在第⼀时间发现死锁并进⾏解决

乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型。

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

17、说一下 synchronized 底层实现原理?(高薪常问)

synchronized 是由JVM实现的一种实现同步互斥的方法,synchronized 同步代码块和synchronized 修饰方法。

1.synchronized 同步代码块

有synchronized 的代码经过编译之后生成monitorenter和monitorexit两个字节码指令

其中monitorenter指令指向同步代码块开始的位置,monitorexit指令指向的是同步代码块结束的位置

当虚拟机执行到monitorenter指令时,首先尝试获取锁也就是获取monitor的持有权,当锁的计数器为0时,可以成功获取,获取后锁的计数器+1,

当虚拟机执行到monitorexit指令时,将锁的计数器-1,当计数器为0时,表示锁被释放,

如果获取对象失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止

2.synchronized 修饰方法

有synchronized 修饰方法时,没有monitorenter和monitorexit指令,取而代之的是ACC_SYNCHRONIZED标识,该标识指明该方法是同步方法,JVM通过该ACC_SYNCHRONIZED标识来判断方法是否被声明为同步方法,从而执行相应的同步调用。

锁的升级:

目前锁有四种状态,从级别低到高是:无锁、偏向锁、轻量级锁、重量级锁,锁状态只能升级不能降级

当有一个线程访问同步块时,升级成偏向锁;

有锁竞争时,升级成轻量级锁;

自旋十次失败,升级成重量级锁;

15、synchronized 和 volatile 的区别是什么?(高薪常问)

volatilesynchronized
本质volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从 主存中读取;synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
级别volatile 仅能使用在变量级别synchronized 则可以使用在变量、方法、和类级别的
修改可见性、原子性volatile 仅能实现变量的修改可见性,不能保证原子性synchronized 则可以保证变量的修改可见性和原子性
线程的阻塞volatile 不会造成线程的阻塞synchronized 可能会造成线程的阻塞
被编译器优化volatile标记的变量不会被编译器优化synchronized标记的变量可以被编译器优化

16、 synchronized 和 Lock 有什么区别? (高薪常问)

  1. 关键字/类: synchronized 是 java 内置关键字;在 jvm 层面,Lock 是个 java 类;

  2. 判断是否获取到锁: synchronized 无法判断是否获取到锁,Lock 可以判断是否获取到锁;

  3. 是否自动释放锁:synchronized 会自动释放锁,Lock 需在 finally 中手工释放锁,否则容易造成线程死锁:

  4. 是否一直等:用 synchronized 关键字的两个线程 1 和线程 2,如果当前线程 1 获得锁,线程 2 线程等待。如果线程 1 阻塞,线程 2 则会一直等待下去,而 Lock 锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

  5. 可:synchronized 的锁可重入、不可中断、非公平,而 Lock 锁可重入、可中断、可公平 (两者皆可);

  6. 少量/大量代码:synchronized 锁适合少量代码的同步问题,Lock 锁适合大量代码的同步问题。

二 、集合

1、 集合和数组的区别(了解)

  1. 长度:数组长度固定;集合长度可变

  2. 存储数据类型:数组中存储的是同一种数据类型的元素,可以是基本数据类型,也可以是引用数据类型; 集合存储的都是对象,而且对象的数据类型可以不一致

2、List 和 Map、Set 的区别(必会)

List (顺序)有序 可重复

Set(独一无二)无序的、不可重复

Map键值对:key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值

3、 List 和 Map、Set 的实现类(必会)

  1. Connection接口

    [List 有序,可重复:]

    ArrayList

    优点: 底层数据结构是数组,查询快,增删慢。
    缺点: 线程不安全,效率高

    Vector

    优点: 底层数据结构是数组,查询快,增删慢。
    缺点: 线程安全,效率低, 已给舍弃了

    LinkedList
    优点: 底层数据结构是链表,查询慢,增删快。
    缺点: 线程不安全,效率高

    [Set无序,唯一:]

    *HashSet*
    底层数据结构是哈希表。(无序,唯一)
    如何来保证元素唯一性?
    依赖两个方法:hashCode()和equals()

    *LinkedHashSet*
    底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
    a.由链表保证元素有序
    b.由哈希表保证元素唯一

    *TreeSet*
    底层数据结构是红黑树。(有序,唯一)
    a. 如何保证元素排序的呢?
    自然排序
    比较器排序
    b.如何保证元素唯一性的呢?
    根据比较的返回值是否是0来决定

  2. Map接口有四个实现类

    *HashMap*
    基于 hash 表的 Map 接口实现,线程不安全,高效,支持 null 值和 null 键。
    *HashTable*
    线程安全,低效,不支持 null 值和 null 键;
    *LinkedHashMap*
    线程不安全,是 HashMap 的一个子类,保存了记录的插入顺序;
    *TreeMap*

    线程不安全,能够把它保存的记录根据键排序,默认是键值的升序排序

4、Hashmap的底层原理(高薪常问)

JDK1.8之前:数组+链表

JDK1.8后: 数组+链表或红黑树,主要的目的是提高查找效率

(1)数组+链表或红黑树实现,当链表中的元素超过了 8 个以后, 会将链表转换为红黑树,当红黑树节点 小于 等于6 时又会退化为链表。

(2)当new HashMap():底层没有创建数组,首次调用put()方法时,底层创建长度为16的Node[]数组,用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,专业术语叫做扩容,在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能.

默认的负载因子大小为0.75,数组大小为16。也就是说,默认情况下,那么当HashMap中元素个数超过16* 0.75=12的时候,就把数组的大小扩展为2* 16=32,即扩大一倍。

(3)在我们Java中任何对象都有hashcode,hash算法就是通过hashcode与自己进行向右位移16的异或运算。这样做是为了计算出来的hash值足够随机,足够分散,还有产生的数组下标足够随机,

map.put(k,v)实现原理

1)首先将k,v封装到Node对象当中(节点)。
2)先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
3)下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equals。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。

map.get(k)实现原理

1)、先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
2)、在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

(4)Hash冲突
不同的对象算出来的数组下标是相同的这样就会产生hash冲突,

当单线链表达到一定长度后效率会非常低。

所以在链表长度大于8的时候,将链表就会变成红黑树,提高查询的效率。

5、Hashmap和hashtable ConcurrentHashMap区别(高薪常问)

*区别对比一(HashMap 和 HashTable 区别):*

(1)线程安全:HashMap 是非线程安全的,HashTable 是线程安全的。

(2)效率因为线程安全的问题,HashMap 效率比 HashTable 的要高。

(3)空:HashMap 的键和值都允许有 null 值存在,而 HashTable 则不行。

(4)Hashtable 是同步的,而 HashMap 不是。因此,HashMap 更适合于单线程环境,而 Hashtable 适合于多线程环境。一般现在不建议用 HashTable, ①是 HashTable 是遗留类,内部实现很多没优化和冗余。②即使在多线程环境下,现在也有同步的ConcurrentHashMap 替代,没有必要因为是多线程而用HashTable。

*区别对比二(HashTable 和 ConcurrentHashMap 区别):*

关键字:HashTable 使用的是 Synchronized 关键字修饰,ConcurrentHashMap 是JDK1.7使用了锁分段技术来保证线程安全的。JDK1.8取消了Segment分段锁,采用CAS和synchronized来保证并发安全。

数据结构:数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。

synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

三、Java基础 -多线程

1、什么是线程?线程和进程的区别?(了解)

线程:是进程的一个实体,是 cpu 调度和分派的基本单位,是比进程更小的可以独立运行的基本单位。
进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位。
特点:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行时各自内存单元相互独立,线程之间 内存共享,这使多线程编程可以拥有更好的性能和用户体验。

2、创建线程有几种方式(必会)

(1)继承 Thread 类并重写 run 方法创建线程,实现简单但不可以继承其他类
(2)实现 Runnable 接口并重写 run 方法。避免了单继承局限性,编程更加灵活,实现解耦。
(3)实现 Callable 接口并重写 call 方法,创建线程。可以获取线程执行结果的返回值,并且可以抛出异常。
(4)使用线程池创建(使用 java.util.concurrent.Executor 接口)

3、Runnable 和 Callable 的区别?(必会)

主要区别
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息

4、如何启动一个新线程、调用 start 和 run 方法的区别?(必会)

在这里插入图片描述

调用 start 方法可以启动线程,并且使得线程进入就绪状态,
而 run 方法只是 thread的一个普通方法,还是在主线程中执行

5、线程有哪几种状态以及各种状态之间的转换?(必会)

(1)第一是 new->新建状态。在生成线程对象,并没有调用该对象的 start 方法,这是线程处于创建状态。
(2)第二是 Runnable->就绪状态。当调用了线程对象的 start 方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。
(3)第三是 Running->运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行 run 函数当中的代码。
(4)第四是阻塞状态。阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
1)等待 – 通过调用线程的 wait() 方法,让线程等待某工作的完成。
2)超时等待 – 通过调用线程的 sleep() 或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
3)同步阻塞 – 线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(5)第五是 dead->死亡状态: 线程执行完了或者因异常退出了 run()方法,该线程结束生命周期.
在这里插入图片描述

6、线程相关的基本方法?(必会)

线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等
(1)线程等待(wait)
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,
需要注意的是调用 wait()方法后,会释放对象的锁。因此,wait 方法一般用在同步方法或同步代码块中。
(2)线程睡眠(sleep)
sleep 导致当前线程休眠,
sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法会导致当前线程进入 WATING 状态.
与 wait 方法不同的是 sleep 不会释放当前占有的锁,
(3)线程让步(yield)
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。
(一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对线程优先级并不敏感。)
(4)线程中断(interrupt)
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)
(5)Join 等待其他线程终止
join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞状态,到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸.
(6)线程唤醒(notify)
Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。

7、 wait()和 sleep()的区别?(必会)

(1)来自不同的类
wait():来自 Object 类;
sleep():来自 Thread 类;
(2)关于锁的释放:
wait():在等待的过程中会释放锁;
sleep():在等待的过程中不会释放锁
(3)使用的范围:
wait():必须在同步代码块中使用;
sleep():可以在任何地方使用;
(4)是否需要捕获异常
wait():不需要捕获异常;
sleep():需要捕获异常;

四、Java基础-多线程

1、为什么需要线程池

在实际使用中,线程是很占用系统资源的,如果对线程管理不完善的话很容易导致系统问题。因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主要有如下好处:
(1)降低资源的消耗:使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建销毁时造成的消耗
(2)提高响应速度:由于没有线程创建和销毁时的消耗,可以提高系统响应速度
(3)提高线程可管理性:通过线程可以对线程进行合理的管理,根据系统的承受能力调整可运行线程数量的大小等

2、线程池的分类(高薪常问)

3、核心参数(高薪常问)

corePoolSize:核心线程池的大小
maximumPoolSize:线程池能创建线程的最大个数
keepAliveTime:空闲线程存活时间
unit:时间单位,为 keepAliveTime 指定时间单位
workQueue:阻塞队列,用于保存任务的阻塞队列
threadFactory:创建线程的工程类
handler:饱和策略(拒绝策略)

4、线程池的原理(高薪常问)

在这里插入图片描述
线程池的工作过程如下:
当一个任务提交至线程池之后,
(1)线程池首先判断核心线程池里的线程是否已经满了。如果不是,则创建一个新的工作
线程来执行任务。否则进入
(2)判断工作队列是否已经满了,倘若还没有满,将线程放入工作队列。否则进入 3.
(3)判断线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行。
如果线程池满了,则交给饱和策略来处理任务。

5、拒绝策略

ThreadPoolExecutor.AbortPolicy(系统默认): 丢弃任务并抛出RejectedExecutionException 异常,让你感知到任务被拒绝了,我们可以根据业务逻辑选择重试或者放弃提交等策略。
ThreadPoolExecutor.DiscardPolicy: 也是丢弃任务,但是不抛出异常,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程),通常是存活时间最长的任务,它也存在一定的数据丢失风险。
ThreadPoolExecutor.CallerRunsPolicy:既不抛弃任务也不抛出异常,而是将某些任务回退到调用者,让调用者去执行它。

6、如何关闭线程池

关闭线程池,可以通过 shutdown 和 shutdownNow 两个方法
原理:遍历线程池中的所有线程,然后依次中断
1、shutdownNow 首先将线程池的状态设置为 STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;
2、shutdown 只是将线程池的状态设置为 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程

7、线程池中阻塞队列的作用?为什么是先添加列队而不是先创建最大线程?

1)一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入 wait 状态,释放 cpu 资源。阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take 方法挂起,从而维持核心线程的存活、不至于一直占用 cpu 资源
2)在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。
就好比一个企业里面有 10 个(core)正式工的名额,最多招 10 个正式工,要是任务超过正式工人数(task > core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这 10人,但是任务可以稍微积压一下,即先放到队列去(代价低)。10 个正式工慢慢干,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时工)要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)

8、线程池中线程复用原理

线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,通过这种方式只使用固定的线程就将所有任务的 run 方法串联起来

五、Jvm

1、JDK1.8 JVM 运行时内存(高薪问题)

在这里插入图片描述
程序计数器:
线程私有的(每个线程都有一个自己的程序计数器), 是一个指针,记录代码执行的位置
Java 虚拟机栈:
线程私有的(每个线程都有一个自己的 Java 虚拟机栈). 一个方法运行, 就会给这个方法创建一个栈帧, 栈帧入栈执行代码, 执行完毕之后出栈(弹栈)存引用变量,基本数据类型
本地方法栈:
线程私有的(每个线程都有一个自己的本地方法栈), 和 Java 虚拟机栈类似, Java 虚拟机栈加载的是普通方法,本地方法加载的是 native 修饰的方法.native:在 java 中有用 native 修饰的,表示这个方法不是 java 原生的.
堆:
线程共享的(所有的线程共享一份). 存放对象的,new 的对象都存储在这个区域.还有就是常量池.
元空间:
存储.class 信息, 类的信息,方法的定义,静态变量等.
JDK1.8 和 JDK1.7 的 jvm 内存最大的区别是1.8 不存在方法区,将方法区的实现给去掉了.而是在本地内存中,新加入元数据区(元空间)

2、JDK1.8 堆内存结构(高薪常问)

在这里插入图片描述
Young 年轻区(代): Eden+S0+S1, S0 和 S1 大小相等, 新创建的对象都在年轻代
Tenured 年老区: 经过年轻代多次垃圾回收存活下来的对象存在年老代中.
Jdk1.7 和 Jdk1.8 的区别在于, 1.8 将永久代中的对象放到了元数据区, 不存永久代这一区域了

3、Gc 垃圾回收(高薪常问)—如何发现垃圾和如何回收垃圾?

JVM 的垃圾回收动作可以大致分为两大步,首先是「如何发现垃圾」,然后是「如何回收垃圾」。说明一点, 线程私有的不存在垃圾回收, 只有线程共享的才会存在垃圾回收, 所以堆中存在垃圾回收.
(1)如何发现垃圾
Java 语言规范并没有明确的说明 JVM 使用哪种垃圾回收算法,但是常见的用于「发现垃圾」的算法有两种,引用计数算法和根搜索算法。
1)引用计数算法
该算法很古老(了解即可)。核心思想是,堆中的对象每被引用一次,则计数器加 1,每减少一个引用就减 1,当对象的引用计数器为 0 时可以被当作垃圾收集。
优点:快。
缺点:无法检测出循环引用。如两个对象互相引用时,他们的引用计数永远不可能为 0。
2)根搜索算法(也叫可达性分析)
根搜索算法是把所有的引用关系看作一张图,从一个节点 GC ROOT 开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即可以当作垃圾。
Java 中可作为 GC Root 的对象有
a.虚拟机栈中引用的对象
b.本地方法栈引用的对象
c.方法区中静态属性引用的对象
d.方法区中常量引用的对象
(2) 如何回收垃圾
Java 中用于「回收垃圾」的常见算法有 4 种:
1)标记-清除算法(mark and sweep)
分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。
缺点:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片。
2)标记-整理算法
是在标记-清除算法基础上做了改进,标记阶段是相同的,但标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
优点:内存被整理后不会产生大量不连续内存碎片。
3)复制算法(copying)
将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。
缺点:可使用的内存只有原来一半。
4)分代收集算法(generation)
当前主流 JVM 都采用分代收集(Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为年轻代、年老代、永久代,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率
a 年轻代(Young Generation)
a1. 所有新生成的对象首先都是放在年轻代的。
a2. 新 生 代 内 存 按 照 8:1:1 的 比 例 分 为 一 个 eden 区 和 两 个Survivor(survivor0,survivor1)区。大部分对象在 Eden 区中生成。回收时先将 eden 区存活对象复制到一个 survivor0 区,然后清空 eden 区,当这个 survivor0 区也存放满了时,则将 eden 区和 survivor0 区存活对象复制到另一个 survivor1 区,然后清空 eden 和这个survivor0 区,此时 survivor0 区是空的,然后将 survivor0 区和 survivor1 区交换,即保持 survivor1 区为空, 如此往复。
a3. 当 survivor1 区不足以存放 eden 和 survivor0 的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次 Full GC,也就是新生代、老年代都进行回收。
a4. 新生代发生的 GC 也叫做 Minor GC,MinorGC 发生频率比较高(不一定等Eden 区满了才触发)
b 年老代(Old Generation)
b1. 在年轻代中经历了 N 次垃圾回收后仍然存活的对象,就会被放到年老代中。
因此,可以认为年老代中存放的都是一些生命周期较长的对象。
b2. 内存比新生代也大很多(大概是2倍),当老年代内存满时触发Major GC即Full GC,Full GC 发生频率比较低,老年代对象存活时间比较长,存活率比较高。
c 持久代(Permanent Generation)
用于存放静态文件,如 Java 类、方法等。持久代对垃圾回收没有显著影响,从 JDK8以后已经废弃, 将存放静态文件,如 Java 类、方法等这些存储到了元数据区

六、JavaWeb

1、 TCP 与 UDP 区别? (了解)

TCP(Transmission Control Protocol 传输控制协议)
UDP 是 User Datagram Protocol 的简称,是用户数据报协议
(1)TCP 基于连接 UDP 无连接
(2)TCP 要求系统资源较多,UDP 较少
(3)TCP 保证数据正确性,UDP 可能丢包
(4)TCP 保证数据顺序,UDP 不保证

2、什么是 HTTP 协议?

客户端和服务器端之间数据传输的格式规范,格式简称为“超文本传输协议”。
是一个基于请求与响应模式的、无状态的、应用层的协议,基于 TCP 的连接方式

3、TCP 的三次握手,为什么?

为了准确无误地把数据送达目标处,TCP 协议采用了三次握手策略。
在这里插入图片描述
为什么要三次握手?
三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,
而三次握手最主要的目的就是 双方确认自己与对方的发送与接收是正常的。
SYN:同步序列编号(Synchronize Sequence Numbers)。是 TCP/IP 建立连接时
使用的握手信号。
第一次握手:客户端给服务器发送一个 SYN。客户端发送网络包,服务端收到了。服
务器得出结论:客户端的发送能力,服务端的接收能力正常。
第二次握手:服务端收到 SYN 报文之后,会应答一个 SYN+ACK 报文。服务端发包,客户
端收到了。客户端得出结论:服务端的接收和发送能力,客户端的接收和发送能力正常。但
是此时服务端不能确认客户端的接收能力是否正常。
第三次握手;客户端收到 SYN+ACK 报文之后,回应一个 ACK 报文。客户端发包,服务端收
到了。服务器得出结论:客户端的接收和发送能力,自己的接收发送能力都正常。
通过三次握手,双方都确认对方的接收以及发送能力正常。

4、TCP 的四次挥手,为什么?

第一次挥手:Clien发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
第二次挥手:Server收到FIN后,发送一个ACK给Client,Server进入CLOSE_WAIT状态。
第三次挥手: Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,发送ACK给Server,Server进入CLOSED状态,完成四次握手。

在这里插入图片描述
理解版:
①客户端申请断开连接即FIN (我这边准备断开连接了)
②服务端接收信息返回,表示我已经接收到 (收到,请稍等,我这边准备一下)
③服务端发送信息表示可以断开连接 (我准备好了,你可以断开连接了)
④客户端接受信息,同时返回信息通知服务端自己收到信息,开始断开 连接(好的,拜拜!)

5、HTTP 中重定向和请求转发的区别?

实现:
转发:用 request 的 getRequestDispatcher()方法得到 ReuqestDispatcher 对象,调用 forward()方法request.getRequestDispatcher(“other.jsp”).forward(request, response);
重定向:调用 response 的 sendRedirect()方法
response.sendRedirect(“other.jsp”);
1> 重定向 2 次请求,请求转发 1 次请求
2> 重定向地址栏会变,请求转发地址栏不变
3> 重定向是浏览器跳转,请求转发是服务器跳转
4> 重定向可以跳转到任意网址,请求转发只能跳转当前项目

6、Get 和 Post 的区别?

  1. Get 是不安全的,因为在传输过程,数据被放在请求的 URL 中;Post 的所有操作对用户来说都是不可见的。
  2. Get 传送的数据量较小,一般传输数据大小不超过 2k-4k(根据浏览器不同,限制不一样,但相差不大这主要是因为受 URL 长度限制;Post 传送的数据量较大,一般被默认为不受限制。
  3. Get 限制 Form 表单的数据集的值必须为 ASCII 字符;而 Post 支持整个 ISO10646字符集。
  4. Get 执行效率却比 Post 方法好。Get 是 form 提交的默认方法。

7、cookie 和 session 的区别?(必会)

1)存储位置不同
cookie 的数据信息存放在客户端浏览器上。
session 的数据信息存放在服务器上。
2)存储容量不同
单个 cookie 保存的数据<=4KB,一个站点最多保存 20 个 Cookie。
对于 session 来说并没有上限,但出于对服务器端的性能考虑,session 内不要存放过多的东西,并且设置 session 删除机制。
3)存储方式不同
cookie 中只能保管 ASCII 字符串,并需要通过编码方式存储为 Unicode 字符或者二进制数据。
session 中能够存储任何类型的数据,包括且不限于 string,integer,list,map 等。
4)隐私策略不同
cookie对客户端是可见的,别有用心的人可以分析存放在本地的cookie并进行cookie欺骗,所以它是不安全的。
session 存储在服务器上,不存在敏感信息泄漏的风险。
5)有效期上不同
开发可以通过设置 cookie 的属性,达到使 cookie 长期有效的效果。
session 依赖于名为 JSESSIONID 的 cookie,而 cookie JSESSIONID 的过期时间默认为-1,只需关闭窗口该 session 就会失效,因而 session 不能达到长期有效的效果。
6)服务器压力不同
cookie 保管在客户端,不占用服务器资源。对于并发用户十分多的网站,cookie 是很好的选择。
session 是保管在服务器端的,每个用户都会产生一个 session。假如并发访问的用户十分多,会产生十分多的 session,耗费大量的内存。

8、Jsp 和 Servlet(了解)

1)Jsp 和 Servlet 的区别?
相同点:
jsp 经编译后就变成了 servlet,jsp 本质就是 servlet,
不同点:
JSP 侧重视图,Sevlet 主要用于控制逻辑。

9、Ajax 的介绍(必会)

Ajax 即"Asynchronous JavaScript And XML"(异步 JavaScript 和 XML),是指一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术

七、Mysql

1、连接查询分为?(必会)

1)左连接
(左外连接)以左表为基准进行查询,左表数据会全部显示出来,右表 如果和左表匹配 的数
据则显示相应字段的数据,如果不匹配,则显示为 NULL;
2)右连接
(右外连接)以右表为基准进行查询,右表数据会全部显示出来,右表 如果和左表匹配的数据
则显示相应字段的数据,如果不匹配,则显示为 NULL;

2、聚合函数的分类(必会)

1)聚合函数
SQL 中提供的聚合函数可以用来统计、求和、求最值等等。
2)分类
COUNT:统计行数量
SUM:获取单个列的合计值
AVG:计算某个列的平均值
MAX:计算列的最大值
MIN:计算列的最小值

3、SQL 关键字(必会)

1)分页
MySQL 的分页关键词 limit
SELECT * FROM student3 LIMIT 2,6; 查询学生表中数据,从第三条开始显示,显示6 条
2)分组
MySQL 的分组关键字:group by
SELECT sex, count(*) FROM student3 GROUP BY sex;
3) 去重
去重关键字:distinct
select DISTINCT NAME FROM student3;

4、SQL Select 语句完整的执行顺序是什么?(必会)

查询中用到的关键词主要包含如下展示,并且他们的顺序依次为 form…on…left
join…where…group by…avg()/sum()…having…select…
order by…asc/desc…limit…
from: 需要从哪个数据表检索数据
where: 过滤表中数据的条件
group by: 如何将上面过滤出的数据分组算结果
order by : 按照什么样的顺序来查看返回的数据

5、数据库三范式是什么?(必会)

第一范式:1NF 原子性,属性不能再分
第二范式:2NF ,满足1NF,且不存在部分依赖
第三范式:3NF ,满足2NF,且不存在传递依赖

6、存储引擎 (高薪常问)

1)MyISAM 存储引擎的主要特点
MySQL5.5 版本之前的默认存储引擎
支持表级锁(表级锁是 MySQL 中锁定粒度最大的一种锁,表示对当前操作的整张表加锁);
不支持事务,外键。
2)InnoDB 存储引擎的主要特点
MySQL5.5 版本之后的默认存储引擎;
支持行级锁(行级锁是 Mysql 中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁);
支持事务;

7、数据库事务(必会)

1)事务特性有哪些?

原子性:即不可分割性,事务要么全部被执行,要么就全部不被执行。

一致性:事务的执行使得数据库从一种正确状态转换成另一种正确状态。

隔离性:在事务正确提交之前,不允许把该事务对数据的任何改变提供给任何其他事务

持久性:事务正确提交后,其结果将永久保存在数据库中,即使在事务提交后有了其他故障,事务的处理结果也会得到保存。

2)隔离级别有哪些?

(1) 读未提交(read Uncommited):所有的事务都可以读取到别的事务中未提交的数据,会产生脏读问

(2) 读已提交(read commited):一个事务只能看见已经提交事务所做的改变,所以会避免脏读问题;由于一个事务可以看到别的事务已经提交的数据,于是随之而来产生了不可重复读和幻读等问题

(3 ) 可重复读(Repeatable read):这是MySQL的默认隔离级别,一个事务中多个实例在并发读取数据的时候会读取到一样的数据;会导致幻读(Phantom Read)。幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影”行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

(4) 可串行化(serializable):事物的最高级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争,一般为了提升程序的吞吐量不会采用这个

8、索引

1)索引的概念和优点(了解)

概念:索引存储在内存中,为服务器存储引擎为了快速找到记录的一种数据结构。

索引的主要作用是加快数据查找速度,提高数据库的性能。优点:(1)创建唯一性索引,保证数据库表中每一行数据的唯一性(2)大大加快数据的检索速度,这也是创建索引的最主要的原因(3)加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。(4)在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时

2)索引的分类(必会)

(1)普通索引:最基本的索引,它没有任何限制。
(2)唯一索引:与普通索引类似,不同的就是索引列的值必须唯一,但允许有空值。
如果是组合索引,则列值的组合必须唯一。
(3)主键索引:它是一种特殊的唯一索引,用于唯一标识数据表中的某一条记录,不允许有空值,一般 primary key 来约束。
(4)联合索引(又叫复合索引):多个字段上建立的索引,能够加速复合查询条件的检索。
(5)全文索引:老版本 MySQL 自带的全文索引只能用于数据库引擎为 MyISAM 的数据表,新版本 MySQL 5.6 的 InnoDB 支持全文索引。默认 MySQL 不支持中文全文检索,可以通过扩展 MySQL,添加中文全文检索或为中文内容表提供一个对应的英文索引表的方式来支持中文。

3)索引的底层实现原理(高薪常问)

Mysql 目前提供了以下 4 种索引:
B+Tree 索引: 最常见的索引类型, 大部分索引都支持 B+树索引.
Hash 索引: 只有 Memory 引擎支持, 使用场景简单.
R-Tree 索引(空间索引): 空间索引是 MyISAM 引擎的一个特殊索引类型, 主要地理空间数据, 使用也很少.
S-Full-text(全文索引): 全文索引也是 MyISAM 的一个特殊索引类型, 主要用于全文索引, InnoDB 从 Mysql5.6 版本开始支持全文索引
在这里插入图片描述

4) 如何避免索引失效(高薪常问)

模型数空运最快
索引失效情况:(七个字)
模:模糊查询LIKE以%开头
型:数据类型错误
数:对索引字段使用内部函数
空:索引列是NULL
运:索引列进行四则运算(加减乘除)
最:复合索引不按索引列最左开始查找
快:数据库预计全表扫描比索引更快

9、MySql 优化(高薪常问)

思路总结:主要考虑数据库优化与SQL语句优化。
1,数据库优化,包括存储引擎的优化,缓存的优化和内存的优化等。
2,SQL优化。思路如下:
(定位)首先先判断什么样的SQL需要优化。可以在MySQL中开启慢查询配置,设置成例如SQL执行时长超过5秒就可以定为慢SQL,并记录到日志中。
(分析)然后拿到慢SQL的执行记录和计划,通过explain关键字做分析。分析思路有例如SQL存在索引,判断是否执行了索引或者索引失效原因,若确实走索引则要考虑索引创建是否合理,以及是否遵循最左匹配原则等。

10、 mysql的undolog和redolog还有binglog分别是什么

redolog:重做日志,增删改查时数据先从磁盘读到buffer pool缓冲池里,再在缓冲池里做增删改查操作。如修改了缓冲池里的数据,如果还没落入磁盘,Mysql因为什么原因导致了失败异常,那么数据就会丢失了,这样是不被允许的。所以引入了redolog,就是在内存中的修改,在没有落入磁盘前,如果导致了任何失败,用redolog去进行修复。

binglog:归档日志,是mysql server层面的,属于逻辑日志,是以二进制形式记录的,用于记录数据库执行的写入性操作。binglog是通过追加的方式进行写入的。

使用场景有两个:数据恢复和主从复制

undolog:数据库事务的四大特性之一的原子性底层就是通过undolog实现的。undolog保存了事务发生之前的数据版本,可以用于回滚。

redolog和binglog区别:

1.层面

redolog属于InnoDB层面

binglog属于mysql server层面

2.物理/逻辑日志

redolog是物理日志,记录该数据页更新的内容

binglog是逻辑日志,记录更新语句的原始逻辑

3.覆盖写

redolog是循环写,日志空间大小固定,会覆盖之前的数据。

binglog是追加写,一份写到一定大小时,会更换下一个文件,不会覆盖

4.用来

redolog是用来防止mysql异常导致修改数据丢失

binlog是用来数据备份和主从节点数据同步的。

七、Mybatis 框架

1.谈一谈你对 Mybatis 框架的理解(了解)

1) MyBatis 是一个 半自动的ORM(Object Relation Mapping对象关系映射)框架
2)MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
3) MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
4) MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old JavaObjects,普通的Java对象)映射成数据库中的记录

2.在 mybatis 中,${} 和 #{} 的区别是什么?(必会)

${}是字符串拼接,#{}是占位符赋值
${} 需要手动加单引
#{} 可以自动添加单引号;可以有效的防止 SQL 注入 提高系统安全性。

3.MyBatis 编程步骤是什么样的?(了解)

4.在 mybatis 中,resultType 和 ResultMap 的区别是什么?(必会)

如果数据库结果集中的列名和要封装实体的属性名完全一致的话用 resultType 属性
如果数据库结果集中的列名和要封装实体的属性名有不一致的情况用 resultMap 属性,通过 resultMap 手动建立对象关系映射

5.在 Mybatis 中你知道的动态 SQL 的标签有哪些?作用分别是什么?(必会)

1)if 是为了判断传入的值是否符合某种规则,比如是否不为空.
2) where 标签可以用来做动态拼接查询条件,当和 if 标签配合的时候,不用显示
的声明类型 where 1 = 1 这种无用的条件
3) foreach 标签可以把传入的集合对象进行遍历,然后把每一项的内容作为参
数传到 sql 语句中.
4) include 可以把大量的重复代码整理起来,当使用的时候直接 include 即可,
减少重复代码的编写;
5)适用于更新中,当匹配某个条件后,才会对该字段进行跟新操作

6.谈一下你对 mybatis 缓存机制的理解?(了解)

一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问
使一级缓存失效的四种情况:

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查询条件不同
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作
  4. 同一个SqlSession两次查询期间手动清空了缓存
    二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
    二级缓存开启的条件:
    a>在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
    b>在映射文件中设置标签
    c>二级缓存必须在SqlSession关闭或提交之后有效
    d>查询的数据所转换的实体类类型必须实现序列化的接口
    使二级缓存失效的情况:
    两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭之后,一级缓存中的数据会写入二级缓存

7.mybatis 的优缺点

优点:
1)基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签, 支持编写动态SQL 语句,并可重用。
2)与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接;
3)很好的与各种数据库兼容( 因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持) ;
4)能够与 Spring 很好的集成;RightPDF
5)提供映射标签, 支持对象与数据库的 ORM 字段关系映射; 提供对象关系映射标签,支持对象关系组件维护。
缺点:
1)SQL 语句的编写工作量较大, 尤其当字段多、关联表多时, 对开发人员编写 SQL 语句的功底有一定要求;
2)SQL 语句依赖于数据库, 导致数据库移植性差, 不能随意更换数据库;

8.MyBatis 与Hibernate 有哪些不同?(必会)

JDBC:
SQL 夹杂在Java代码中,耦合度高,导致硬编码内伤
维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
代码冗长,开发效率低

Hibernate 和 JPA:
优点:操作简便,开发效率高
缺点:程序中的长难复杂 SQL 需要绕过框架
内部自动生产的 SQL,不容易做特殊优化
基于全映射的全自动框架,但是如果大量字段的 POJO 中进行部分映射时比较困难。
用到大量反射,导致数据库性能下降

MyBatis:
轻量级,性能出色
SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
开发效率稍逊于HIbernate,但是完全能够接受

9.简述 Mybatis 的插件运行原理,如何编写一个插件。

八、Spring 框架

1.Spring 的两大核心是什么?谈一谈你对 IOC 的理解? 谈一谈你对 DI 的理解? 谈一谈你对 AOP 的理解?(必会)

1)Spring 的两大核心是:IOC(控制反转)和 AOP(面向切面编程)
2)IOC 的意思是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到 Spring 容器中。
Spring 的 IOC 有三种注入方式 :构造器注入, setter 方法注入, 根据注解注入。
3)DI 的意思是依赖注入,和控制反转是同一个概念的不同角度的描述,总结:IOC 就是一种控制反转的思想, 而 DI 是对 IOC 的一种具体实现。
4)AOP,一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect). SpringAOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个 AOP 对象,这个 AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
5)Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理:
(1)JDK 动态代理只提供接口代理,不支持类代理,最终生成的代理类和目标类实现相同是接口
(2)CGLIB 是通过继承的方式做的动态代理,最终生成的代理类会继承目标类

2.Spring 的生命周期?(高薪常问)

(1)实例化
(2)依赖注入
(3)初始化,需要通过bean的init-method属性指定初始化的方法
(4)IOC容器关闭时销毁,需要通过bean的destroy-method属性指定销毁的方法

3.Spring 支持 bean 的作用域有几种吗? 每种作用域是什么样的?(必会)

Spring 支持如下 5 种作用域:
1)singleton:默认作用域,单例 bean,表示获取该bean所对应的对象都是同一个
2)prototype:表示获取该bean所对应的对象都不是同一个
3)request:为每一个 request 请求创建一个实例,在请求完成以后,bean 会失效并被垃圾回收器回收。
4)session:与 request 范围类似,同一个 session 会话共享一个实例,不同会话使用不同的实例。
5)global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在 global-session 中。

4.BeanFactory 和 ApplicationContext 有什么区别(了解)

ApplicationContext:
是 BeanFactory 的子接口,扩展了其功能,

5. Spring 框架中都用到了哪些设计模式?(必会)

1)工厂模式:BeanFactory 就是简单工厂模式的体现,用来创建对象的实例
2)单例模式:Bean 默认为单例模式
3)代理模式:Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成技术
4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate
5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所 有 依 赖 于 它 的 对 象 都 会 得 到 通 知 被 制 动 更 新 , 如 Spring 中 listener 的 实 现–ApplicationListener

6、 Spring 事务的实现方式和实现原理(必会)

Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring 是
无法提供事务功能的。真正的数据库层的事务提交和回滚是通过 binlog 或者 redo log 实
现的。
spring 事务实现主要有两种方法
1)编程式,beginTransaction()、commit()、rollback()等事务管理相关的方法
2)声明式,利用注解 Transactional 或者 aop 配置

7、 你知道的 Spring 的通知类型有哪些,分别在什么时候执行?(了解)

前置通知:使用@Before注解标识,在被代理的目标方法前执行
返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)
异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)
后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)
环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

8、Spring 的对象默认是单例的还是多例的? 单例 bean 存不存在线程安全问题呢?(必会)

1)在 spring 中的对象默认是单例的,但是也可以配置为多例。
2)单例 bean 对象对应的类存在可变的成员变量并且其中存在改变这个变量的线程时,多线程操作该 bean 对象时会出现线程安全问题。
原因是:多线程操作如果改变成员变量,其他线程无法访问该 bean 对象,造成数据混乱。
解决办法:
(1)在 bean 对象中避免定义可变成员变量;
(2)在 bean 对象中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中。
(3)最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,
这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。

9、 @Resource 和@Autowired 依赖注入的区别是什么? @Qualifier 使用场景是什么?(了解)

@Resource
只能放在属性上,表示先按照属性名匹配 IOC 容器中对象 id 给属性注入值若没有成功,会继续根据当前属性的类型匹配 IOC 容器中同类型对象来注入值
若指定了name属性@Resource(name = “对象id”),则只能按照对象id注入值。
@Autowird
放在属性上:表示先按照类型给属性注入值如果 IOC 容器中存在多个与属性同类型的对象,则会按照属性名注入值
也可以配合@Qualifier(“IOC 容器中对象 id”)注解直接按照名称注入值。
放在方法上:表示自动执行当前方法,如果方法有参数,会自动从 IOC 容器中寻找同类型的对象给参数传值
也可以在参数上添加@Qualifier(“IOC 容器中对象 id”)注解按照名称寻找对象给参数传值。
@Qualifier 使用场景:
@Qualifier(“IOC 容器中对象 id”)可以配合@Autowird 一起使用, 表示根据指定的 id 在 Spring 容器中匹配对象

10、Spring 的常用注解(必会)

1)@Component(任何层) @Controller @Service @Repository(dao): 将类标识为组件,Spring使用IOC容器管理这些组件(@Component:将类标识为普通组件 @Controller:将类标识为控制层组件 @Service:将类标识为业务层组件 @Repository:将类标识为持久层组件)
2)@Scope : 设置 Spring 对象的作用域
3)@PostConstruct @PreDestroy : 用于设置 Spring 创建对象在对象创建之后和销毁之前要执行的方法
4)@Value: 简单属性的依赖注入
5)@Autowired: 对象属性的依赖注入
6)@Qualifier: 要和@Autowired 联合使用,代表在按照类型匹配的基础上,再按照名称匹配。
7) @Resource 按照属性名称依赖注入
8) @ComponentScan: 组件扫描
9) @Bean: 表在方法上,用于将方法的返回值对象放入容器
10)@PropertySource: 用于引入其它的 properties 配置文件
11)@Import: 在一个配置类中导入其它配置类的内容
12)@Configuration: 被此注解标注的类,会被 Spring 认为是配置类。Spring 在启动的时候会自动扫描并加载所有配置类,然后将配置 类中 bean 放入容器
13)@Transactional 此注解可以标在类上,也可以表在方法上,表示当前类中的方法具有事务管理功能。

11、 Spring 的事务传播行为(高薪常问)

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。
@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。

(spring 事务的传播行为说的是,当多个事务同时存在的时候,spring 如何处理这些事务的行为。
备注(方便记忆): propagation 传播
require 必须的/suppor 支持/mandatory 强制托管/requires-new 需要新建/
not -supported 不支持/never 从不/nested 嵌套的
① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按 REQUIRED 属性执行。)

12、 Spring 中的隔离级别 (高薪常问)

隔离级别一共有四种:
(1)读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。
(2)读已提交:READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改。
(3)可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
(4)串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

13、Spring 中什么时候 @Transactional 会失效

spring 事务的原理是 AOP,进行了切面增强,那么失效的根本原因是这个 AOP 不起作用
了!常见情况有如下几种
1)发生自调用,类里面使用 this 调用本类的方法(this 通常省略),此时这个 this 对象不
是代理类,而是 UserService 对象本身!
解决方法很简单,让那个 this 变成 UserService 的代理类即可!
2)方法不是 public 的
3)数据库不支持事务
4)没有被 spring 管理
5)异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为 RuntimeException)

14、什么是 bean 的自动装配,有哪些方式?

开启自动装配,只需要在 xml 配置文件中定义“autowire”属性。
autowire 属性有五种装配的方式:
在这里插入图片描述

1)no – 缺省情况下,自动配置是通过“ref”属性手动设定 。
在这里插入图片描述
2)byName-根据 bean 的属性名称进行自动装配。
在这里插入图片描述

3)byType-根据 bean 的类型进行自动装配。
在这里插入图片描述

4)constructor-类似 byType,不过是应用于构造器的参数。如果一个 bean 与构造器参数的类型形同,则进行自动装配,否则导致异常。
在这里插入图片描述

5)autodetect-如果有默认的构造器,则通过 constructor 方式进行自动装配,否则使用byType 方式进行自动装配
在这里插入图片描述

@Autowired 自动装配 bean,可以在字段、setter 方法、构造函数上使用。

15、Spring、Spring MVC 和 Spring Boot 有什么区别

1)spring 是一个 IOC 容器,用来管理 Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供 AOP 机制弥补 OOP 的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志等
2)springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(url 到 handle 的映射)及适配执行 handle,将handle 结果使用视图解析技术生成视图展现给前端
3)springboot 是 spring 提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc 应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis、mongodb、es,可以开箱即用

九、SpringMVC 框架

1、谈一下你对 SpringMVC 框架的理解(了解)

同上题

2、SpringMVC 主要组件(必会)

前端控制器 DispatcherServlet:接收请求、响应结果,相当于转发器,有了
DispatcherServlet 就减少了其它组件之间的耦合度。
处理器映射器 HandlerMapping:根据请求的 URL 来查找 Handler
处理器适配器 HandlerAdapter:负责执行 Handler
处理器 Handler:处理业务逻辑的 Java 类
视图解析器 ViewResolver:进行视图的解析,根据视图逻辑名将ModelAndView 解析成真正的视图(view)
视图 View:View 是一个接口,它的实现类支持不同的视图类型,如 jsp,freemarker,pdf 等等

3、谈一下 SpringMVC 的执行流程以及各个组件的作用(必会)

在这里插入图片描述
1)用户发送请求到前端控制器(DispatcherServlet)
2)前端控制器(DispatcherServlet)收到请求调用处理器映射器(HandlerMapping),去查找处理器(Handler)
3) 处理器映射器(HandlerMapping)找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet前端控制器。
4) 前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)
5) 处理器适配器(HandlerAdapter)去调用自定义的处理器类(Controller,也叫后端控制器)。
6) 自定义的处理器类(Controller,也叫后端控制器)将得到的参数进行处理并返回结果给处理器适配器(HandlerAdapter)
7 ) 处 理 器 适 配 器 ( HandlerAdapter ) 将 得 到 的 结 果 返 回 给 前 端 控 制 器(DispatcherServlet)
8 ) DispatcherServlet( 前 端 控 制 器 ) 将 ModelAndView 传 给 视 图 解 析 器(ViewReslover)
9) 视图解析器(ViewReslover)将得到的参数从逻辑视图转换为物理视图并返回给前端控制器(DispatcherServlet)
10)前端控制器(DispatcherServlet)调用物理视图进行渲染并返回
11)前端控制器(DispatcherServlet)将渲染后的结果返回

4、说一下 SpringMVC 支持的转发和重定向的写法(必会)

1)转发:
forward 方式:在返回值前面加"forward:“,比如””“forward:user.do?name=method4”
2) 重定向:
redirect 方式:在返回值前面加 redirect:, 比如"redirect:http://www.baidu.com"

5、 SpringMVC 的常用注解(必会)

1)@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
2)@RequestBody:注解实现接收 http 请求的 json 数据,将 json 转换为 java 对象。
3)@ResponseBody:注解实现将 conreoller 方法返回对象转化为 json 对象响应给客户。
4)@PathVariable 用户从 url 路径上获取指定参数,标注在参数前 @PathVariabel(“要获取的参数名”)。
5)@RequestParam: 标注在方法参数之前,用于对传入的参数做一些限制,支持三个属性:

  • value:默认属性,用于指定前端传入的参数名称
  • required:用于指定此参数是否必传
  • defaultValue:当参数为非必传参数且前端没有传入参数时,指定一个默认值
    6)@ControllerAdvice 标注在一个类上,表示该类是一个全局异常处理的类。
    7)@ExceptionHandler(Exception.class) 标注在异常处理类中的方法上,表示该方法可以处理的异常类型。

6、谈一下 SpringMVC 统一异常处理的思想和实现方式(必会)

使用 SpringMVC 之后,代码的调用者是 SpringMVC 框架,也就是说最终的异常会抛到框架中,然后由框架指定异常处理类进行统一处理
方式一: 创建一个自定义异常处理器(实现 HandlerExceptionResolver 接口),并实现里面的异常处理方法,然后将这个类交给 Spring 容器管理
方式二: 基于注解的异常处理,在类上加注解(@ControllerAdvice)表明这是一个全局异常处理类
在方法上加注解(@ExceptionHandler),在 ExceptionHandler 中有一个 value
属性,可以指定可以处理的异常类型

7、在 SpringMVC 中, 如果想通过转发将数据传递到前台,有几种写法?(必会)

方式一:直接使用 request 域进行数据的传递
request.setAttirbuate(“name”, value);
方式二:使用 Model 进行传值,底层会将数据放入 request 域进行数据的传递
model.addAttribuate(“name”, value);
方式三:使用 ModelMap 进行传值,底层会将数据放入 request 域进行数据的传递
modelmap.put(“name”,value);
方式四:借用 ModelAndView 在其中设置数据和视图
mv.addObject(“name”,value);
mv.setView(“success”);
return mv;

8、在 SpringMVC 中拦截器的使用步骤是什么样的?(必会)

1)定义拦截器类:
SpringMVC 为我们提供了拦截器规范的接口,创建一个类实现 HandlerInterceptor,重
写接口中的抽象方法;
preHandle 方法:在调用处理器之前调用该方法,如果该方法返回 true 则请求继
续向下进行,否则请求不会继续向下进行,处理器也不会调用
postHandle 方法:在调用完处理器后调用该方法
afterCompletion 方法:在前端控制器渲染页面完成之后调用此方法
2)注册拦截器:
在 SpringMVC 核心配置文件中注册自定义的拦截器

<mvc:interceptors>
 <mvc:interceptor>
 <mvc:mapping path="拦截路径规则"/>
 <mvc:exclude-mapping path="不拦截路径规则"/>
 <bean class="自定义拦截器的类全限定名"/>
 </mvc:interceptor>
 </mvc:interceptors>

9、在 SpringMVC 中文件上传的使用步骤是什么样的? 前台三要素是什么?(必会)

文件上传步骤:
1)加入文件上传需要的 commons-fileupload 包
2)配置文件上传解析器,springmvc 的配置文件的文件上传解析器的 id 属性必须为 multipartResolver
3)后端对应的接收文件的方法参数类型必须为 MultipartFile,参数名称必须与前端的 name 属性保持一致
文件上传前端三要素:
(1)form 表单的提交方式必须为 post
(2)enctype 属性需要修改为:multipart/form-data
(3)必须有一个 type 属性为 file 的 input 标签,其中需要有一个 name 属性;如果需要上传多个文件需要添加 multiple 属性

10、SpringMVC 中如何解决 GET|POST 请求中文乱码问题?(了解)

11、Springmvc 中的拦截器和过滤器的区别是什么?

十二、SpringBoot

1、SpringBoot 的优点(必会)

SpringBoot 对上述 Spring 的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。
(1)版本锁定:解决是 maven 依赖版本容易冲突的问题,集合了常用的并且测试过的所有版本
(2)起步依赖 :解决了完成某一个功能要整合的 jar 包过多的问题,集合了常用的 jar 包
(3)自动配置:解决了整合框架或者技术的配置文件过多,集合了所有的约定的默认配置
(4)内置 Tomcat:通过内置的 tomcat,无需再用其他外置的 Tomcat 就直接可以运行 javaEE程序
总之:人们把 Spring Boot 称为搭建程序的脚手架。其最主要作用就是帮我们快速的构建庞大的 spring 项目,并且尽可能的减少一切 xml 配置,做到开箱即用,迅速上手,让我们关注与业务而非配置。

2、运行 SpringBoot 项目的方式(必会)

可以打包
可以使用 Maven 插件直接运行.
直接运行 main 方法

3、SpringBoot 的启动器 starter(必会)

什么是 starter?
starter 启动器,可以通过启动器集成其他的技术,比如说: web, mybatis, redis 等等.可以提供对应技术的开发和运行环境.
比如: pom 中引入 spring-boot-starter-web, 就可以进行 web 开发

4、SpringBoot 运行原理剖析(必会)

总之一个@SpringBootApplication 注解就搞定了所有事, 它封装了核心的
@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan 这三个类,大大节省了程序员配置时间,这就是 SpringBoot 的核心设计思想.

5、 SpringBoot 中的配置文件(必会)

  1. 有哪些配置文件?
    bootstrap: yml/application
    application: yml/application
    2)上面两种配置文件有什么区别?
    bootstrap 由父 ApplicationContext 加载, 比 application 配置文件优先被加载.
    (1)bootstarp 里的属性不能被覆盖.
    (2)application: springboot 项目中的自动化配置.
    (3)bootstrap:
    使用 spring cloud config 配置中心时, 需要加载连接配置中心的配置属性的, 就可以使用 bootstrap 来完成
    加载不能被覆盖的属性.
    加载一些加密/解密的数据

6、SpringBoot 常用注解(必会)

@SpringBootApplication: 它 封 装 了 核 心 的 @SpringBootConfiguration
+@EnableAutoConfiguration +@ComponentScan 这三个类,大大节省了程序员配
置时间,这就是 SpringBoot 的核心设计思想.
 @EnableScheduling 是通过@Import 将 Spring 调度框架相关的 bean 定义都加载到
IoC 容器
 @MapperScan:spring-boot支持mybatis组件的一个注解,通过此注解指定mybatis
接口类的路径,即可完成对 mybatis 接口的扫描
 @RestController 是 @Controller 和 @ResponseBody 的结合 , 一 个 类 被 加 上
@RestController 注解,数据接口中就不再需要添加@ResponseBody,更加简洁。
 @RequestMapping,我们都需要明确请求的路径.
 @GetMappping,@PostMapping, @PutMapping, @DeleteMapping 结 合
@RequestMapping 使用, 是 Rest 风格的, 指定更明确的子路径.
 @PathVariable:路径变量注解,用{}来定义 url 部分的变量名.
 @Service 这个注解用来标记业务层的组件,我们会将业务逻辑处理的类都会加上这个
注解交给 spring 容器。事务的切面也会配置在这一层。当让 这个注解不是一定要用。
有个泛指组件的注解,当我们不能确定具体作用的时候 可以用泛指组件的注解托付给
spring 容器
 @Component 和 spring 的注解功能一样, 注入到 IOC 容器中.
 @ControllerAdvice 和 @ExceptionHandler 配合完成统一异常拦截处理.
备注: 面试的时候记住 6.7 个即可~

十三、SpringCloud

1、微服务

目前微服务微服务架构主流的是 SpringBoot+Dubbo 和 SpringBoot+SpringCloud的架构模式

2、SpringCloud 是什么?(了解)

SpringCloud 是一系列框架的集合,集成 SpringBoot,提供很多优秀服务:服务发现和注册,统一配置中心, 负载均衡,网关, 熔断器等的一个微服务治理框架

3、 SpringCloud 有哪些核心组件?(必会)

 Eureka: 注册中心, 服务注册和发现
 Ribbon: 负载均衡, 实现服务调用的负载均衡
 Hystrix: 熔断器
 Feign: 远程调用
微服务和微服务之间的调用可以通过 Feign 组件来完成.
后台系统中, 微服务和微服务之间的调用可以通过 Feign 组件来完成.
Feign 组件集成了 Ribbon 负载均衡策略(默认开启的, 使用轮询机制), Hystrix 熔断器
(默认关闭的, 需要通过配置文件进行设置开启)
被调用的微服务需要提供一个接口, 加上@@FeignClient(“url”)注解
调用方需要在启动类上加上@EnableFeignClients, 开启 Feign 组件功能.
 Zuul: 网关
 Spring Cloud Config: 配置中心

4、SpringBoot 和 SpringCloud 的关系(必会)

(1)SpringBoot 是为了解决 Spring 配置文件冗余问题, 简化开发的框架.
SpringCloud 是为了解决微服务之间的协调和配置问题, 还有服务之间的通信, 熔断, 负载均衡,远程调度任务框架.
(2)SpringCloud 需要依赖 SpringBoot 搭建微服务, SpringBoot 使用了约定大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,SpringCloud很大的一部分是基于 SpringBoot 来实现。
(3)SpringBoot 不需要依赖 SpringCloud 就可以独立开发. SpringBoot 也可以集成Dubbo 进行开发

5、 SpringCloud 和 Dubbo 的区别(高薪常问)

(1)SpringCloud 和 Dubbo 都是主流的微服务架构.
 SpringCloud 是 Apache 下的 Spring 体系下的微服务解决方案.
 Dubbo 是阿里系统中分布式微服务治理框架.
(2)技术方面对比
SpringCloud 功能远远超过 Dubbo, Dubbo 只实现了服务治理(注册和发现). 但是 SpringCloud 提供了很多功能,
Dubbo 可 以 使 用 Zookeeper 作为注册中心 , 实 现 服 务 的 注 册 和 发 现 , SpringCloud 不仅可以使用 Eureka 作为注册中心, 也可以使用 Zookeeper 作为注册中心.
Dubbo 没有实现网关功能, 只能通过第三方技术去整合. 但是 SpringCloud 有zuul 路由网关, 对请求进行负载均衡和分发. 提供熔断器, 而且和 git 能完美集成.

6、 Eureka 和 Zookeeper 的区别(高薪常问)

7、什么是接口的幂等性,你知道哪些方案可以实现接口的幂等性

1)接口的幂等性: 同一个接口,多次发出同一个请求,必须保证操作只执行一次,比如下单以后增加积分,对于同一个用户的同一个订单无论我们调用多少次增加积分的接口都应该保证积分只增加一次。
2)接口幂等性的解决方案-不同的场景会选择不同的方案
(1)基于数据库来实现-先 select 然后再 insert,同样是用户增加积分的
案例,在增加积分之前先查询当前的订单是否增加过积分了,如果没有增
加则增加积分如果增加过了则忽略。
(2)基于 redis 来实现-可以把所有增加过积分的订单写入 redis 然后
每次增加积分的时候判断 redis 中是否有这个记录如果没有则增加积分
如果有则忽略该请求
(3)基于数据库的悲观锁来实现-比如在转账的业务中我们通常都是先
查询用户的金额然后在进行更新的操作,在多线程的环境下可能多个线
程都是先执行查询操作得到了500元的金额然后执行update更新操作,
就可能会导致当前的用户金额变成负数,此时就可以使用悲观锁来解决
这个该问题,悲观锁在同一时刻只允许一个请求获得锁,更新数据,其他
的请求则等待
select * from user id=123 for update;
(4)基于数据库的乐观锁来实现-主要添加一个 version 属性来控制更
新的操作,对于这个功能大家应该是比较熟悉的就不展开介绍了。
(5)基于数据库的唯一性索引来实现-增加积分的时候我们可以对订单
号这个字段设置为唯一,如果一个订单已经消耗过了则再次插入数据的
时候就会报错。
(6)根据数据库的状态来解决-很多时候业务表是有状态的,比如订单
表中有:1-下单、2-已支付、3-完成、4-撤销等状态,我们可以通过状态
来判断是否要继续执行某一个业务,比如停车场的支付功能,我们可以通
过停车场的支付二维码进行支付,也可以通过出口的人工扫码进行支付,
如果我们已经扫码支付了但是到出口的时候砸门未及时打开工作人员
就会要求你人工支付,那么你第二次支付的时候就可以判断下支付的状
态是否是已支付如果是则不再进行支付操作。
(7)分布式锁的方式实现-这个也很好理解不在展开

8、Eureka、Zookeeper、Nacos、Consul 这四个注册中心有什么区别?

十四、Redis

1.Redis 是什么?

高性能非关系型(NoSQL)的(key-value)键值对数据库。可以用作数据库、缓存、消息中间件等。

2. Redis 的存储结构有哪些?

  1. String,字符串
  2. Hash
  3. List,列表,按照插入顺序排序。你可以添加一个元素到列头(左边)或者尾部(右边)。
  4. Set,集合
  5. Zset,有序集合,每个元素都会关联一个分数(score),通过分数来为集合中的成员进行从小到大的排序。zset 的成员不允许重复,但分数(score)却可以重复。

3. Redis 的优点?

  1. 纯内存操作、性能好、Redis 支持事务 、持久化
  2. 单线程操作,避免了频繁的上下文切换
  3. 采用了非阻塞 I/O 多路复用机制。I/O 多路复用就是只有单个线程,通过跟踪每个 I/O 流的状态,来管理多个 I/O 流。

4.为什么要用 Redis

高性能:

  1. 假如用户第一次访问数据库中的某些数据。因为是从硬盘上读取的,这个过程会比较慢。
  2. 将该用户访问的数据存缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。
  3. 如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!

在这里插入图片描述
高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中
的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
在这里插入图片描述

5. Redis 的持久化

Redis 提供了两种持久化的方式,分别是 RDB(Redis DataBase)和 AOF(Append Only File)。
RDB,在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时是将快照文件直接读取到内存中。
AOF,以日志的形式来记录每个写操作(增量保存),就是将Redis执行过的所有写指令记录下来==(读操作不记录),只许追加文件但不可以改写文件==,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
RDB 和 AOF 两种方式也可以同时使用,在这种情况下,如果 redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。

6.Redis 的缺点

1. 缓存穿透

key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。在这里插入图片描述
解决方案:
(1)对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟
(2)设置可访问的名单(白名单)
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
(3)采用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
(4)进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

2. 缓存击穿

key对应的数据存在,但在redis中过期,这个时候大并发的请求可能会瞬间把数据库压垮。
在这里插入图片描述

解决方案:
(1)预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长。
(2)实时调整:现场监控哪些数据热门,实时调整key的过期时长
(3)使用锁
在这里插入图片描述

3. 缓存雪崩

缓存中同一时间大面积失效,这个时候又来了一波
请求,结果请求都怼到数据库上,从而导致数据库连接异常。
解决方案:
(1)构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)
(2)使用锁或队列:
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况,效率慢了。
(3)设置过期标志更新缓存:
记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。
(4)将缓存失效时间分散开:
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,就很难引发集体失效的事件。

4. 分布式锁

解决方案:
(1)使用redis实现分布式锁
多个客户端同时获取锁(setnx)
获取成功,执行业务逻辑{从db获取数据,放入缓存},执行完成释放锁(del)
其他客户端等待重试
在这里插入图片描述
(2)优化之设置锁的过期时间
在set时指定过期时间(推荐)
在这里插入图片描述
(3)优化之UUID防误删
在这里插入图片描述
(4)优化之LUA脚本保证删除的原子性
总结:
1、加锁

// 1. 从redis中获取锁,set k1 v1 px 20000 nx
String uuid = UUID.randomUUID().toString();
Boolean lock = this.redisTemplate.opsForValue()
      .setIfAbsent("lock", uuid, 2, TimeUnit.SECONDS);

2、使用lua释放锁

// 2. 释放锁 del
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 设置lua脚本返回的数据类型
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
// 设置lua脚本返回类型为Long
redisScript.setResultType(Long.class);
redisScript.setScriptText(script);
redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  • 互斥性。在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
  • 加锁和解锁必须具有原子性。

7.主从复制原理

主机数据更新后,自动同步到从。Master以写为主,Slave以读为主
在这里插入图片描述
优点:
读写分离,性能扩展
容灾快速恢复

8.哨兵模式

反客为主的自动版,
监控主服务器和从服务器是否正常运行。
如果主出现故障了根据投票数自动将从库转换为主库

9.Redis-Cluster 集群

无中心化集群,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可,不需要中间代理。
一个 Redis 集群包含 16384 个插槽(hash slot),根据算法计算 key 属于哪个槽, 就放在哪个节点。
Redis 集群提供了以下好处:
实现扩容
分摊压力
无中心配置相对简单
Redis 集群的不足:
多键操作是不被支持的
lua脚本不被支持
由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

十五、rabbitMQ

1、消息中间件的区别?

rabbitMQ对消息堆积的支持并不好,当有大量消息积压的时候,会导致rabbitMQ性能急剧下降
rocket

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值