java面试知识总结(第四天)

Java面试笔记

一、第一部分

1. 什么是面向对象?

答:对比面向过程,是两种不同的处理问题的角度;面向过程注重事情的每一个步骤和顺序,面向对象更注重事情有那些参与者(对象)、及各自需要做什么。

面向对象有:封装:封装的意义,在于名确标识出外部所使用的成员函数和数据项;

​ 继承:继承基类的方法,并做出自己的改变和扩展;

​ 多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。包含有继承

​ 和重写。注意:无法调用子类的特有的功能。

2. JVM性能调优

3. JDK、JRE、JVM三者区别和联系

​ 3.1. JDK:Java开发环境,由JRE和Java工具组成(javac、java、jconsole)

​ 3.2. JRE:Java运行时环境,由JVM(bin)和类库(lib)组成

​ 3.3. JVM:Java虚拟机

在这里插入图片描述

4. ==和equals

​ 4.1. ==:对比栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址

​ 4.2. equals:object中默认的是==比较,通常会重写。String类重写之后比较两个字符串的内容。

5. final

​ 5.1、最终的意思,简述final作用:

  • 修饰类:表示类不可以被继承;

  • 修饰方法:表示方法不可以被子类覆盖,但是可以重载;

  • 修饰变量:表示变量一旦被赋值就不可以更改它的值。

    5.2、为什么局部内部类和匿名内部类只能访问局部final变量?

    ​ 首先需要知道的一点是:内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。

    ​ 将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?
    就将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。使得局部变量与内部类内建立的拷贝保持一致。

    ​ 简单来说,就是局部变量和内部类成员变量保持一致,用final来做一个约定。

6. String、StringBuffer、StringlBuilder区别及使用场景

​ 6.1、String是final修饰的,不可变,每次操作都会产生新的String对象。

​ 6.2、StringBuffer和StringBuilder都是在原对象上操作;stringBuffer是线程安全的,StringBuilder线程不安全;StringBuffer方法都是synchronized修饰的;性能: StringBuilder > StringBuffer > String。

​ 6.3、场景:经常需要改变字符串内容时使用后面StringBuffer和StringBuilder;

​ 优先使用StringBuilder,多线程使用共享变量时使用StringBuffer。

7. 重载和重写的区别

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

public int add(int a, String b);
public String add(int a,String b);
//编译会报错

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

8. 接口和抽象类的区别

初级:

  • 抽象类可以存在普通成员函数(可以有实现的方法),而接口中只能存在public abstract方法。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
  • 抽象类只能继承—个,接口可以实现多个。

中高级:

  • 接口的设计目的,是对类的行为进行约束(更准确的说是一种"有""约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。
  • 而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。
  • 抽象类是对类本质的抽象,表达的是is a的关系,比如: BMw is a car。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。
  • 而接口是对行为的抽象,表达的是like a的关系。比如: Bird like a Aircraft(像飞行器一样可以飞),但其本质上is a Bird。接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。
  • 使用场景:当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。
  • 抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度

9. List和Set的区别

  • List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素
  • Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用lterator接口取得所有元素,在逐一遍历各个元素

10. hashCode与equals

​ 10.1、hashCode介绍

​ hashCode()的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定 该对象在哈希表中的索引位置。hashCode()定义在JDK的Object.java中,Java中的任何类都包含有hashCode()函数。散列表存储的是键值对(key-value),它的特点是:能根据"“键"快速的检索出对应的“值””。这其中就利用到了散列码!(可以快速找到所需要的对象)

​ 10.2、为什么要有hashCode

​ 以"Hashset如何检查重复"为例子来说明为什么要有hashCode:
对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,看该位置是否有值,如果没有、HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals ()方法来检查两个对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样就大大减少了equals的次数,相应就大大提高了执行速度。

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

二、第二部分

11、ArrayList和LinkedList区别(面试经常问的题)

​ 11.1、ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList(需要创建大量的node对象)

​ 11.2、LinkedList: 基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexlOf对list进行了遍历,当结果为空时会遍历整个列表。

12、HashMap和HashTable的区别?底层实现是什么?

区别:

  • HashMap方法没有synchronized修饰,线程非安全,
  • HashTable线程安全;HashMap允许key和value为null,而HashTable不允许

底层实现:数组+链表实现
jdk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在

  • 计算key的hash值,二次hash然后对数组长度取模,对应到数组下标,
  • 如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组,
  • 如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
  • key为null,存在下标0的位置

数组扩容

13、ConcurrentHashMap原理,jdk7和jdk8版本的区别

JDK1.7:

  • 数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构
  • 元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部
  • 锁: Segment分段锁Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影响并发度
  • 为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment
  • get方法无需加锁,volatile保证

JDK1.8:

  • 数据结构: synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性查找,替换,赋值操作都使用CAS
  • 锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容。

读操作无锁:

  • Node的val和next使用volatile修饰,读写线程对该变量互相可见
  • 数组用volatile修饰,保证扩容时被读线程感知

14、如何实现一个IOC容器

  1. 配置文件配置包扫描路径
  2. 递归包扫描获取.class文件
  3. 反射、确定需要交给IOC管理的类
  4. 对需要注入的类进行依赖注入

15、什么是字节码?采用字节码的好处是什么?

java中的编译器和解释器:

  • Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。
  • 编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展名为.class的文件),它不面向任何特定的处理器,只面向虚拟机。
  • 每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。
  • Java源代码—>编译器–>jvm可执行的Java字节码(即虚拟指令)–>jvm—>jvm中解释器---->机器可执行的二进制机器码---->程序运行。

采用字节码的好处:

  • Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行

16、Java类加载器有哪些

  • JDK自带有三个类加载器: bootstrap ClassLoader、ExtClassLoader、AppClassLoader。
  • BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件。
  • ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件夹下的jar包和class类。
  • AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。系统类加载器,线程上下文加载器。继承ExtClassLoader实现自定义类加载器

17、双亲委托模型

在这里插入图片描述

双亲委派模型的好处:

  • 主要是为了安全性,避免用户自己编写的类动态替换Java的一些核心类,比如String。
  • 同时也避免了类的重复加载,因为JⅣM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类。

18、Java中的异常体系

  • Java中的所有异常都来自顶级父类Throwable。
  • Throwable下有两个子类Exception和Error。
  • Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。
  • Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。
  • RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。

19、GC如何判断对象可以被回收

  • 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以向收。
  • 可达性分析法:从 GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GCRoots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。

GC Roots的对象有:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象
可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。

当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活

每个对象只能触发一次finalize()方法

由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。

20、线程的生命周期,线程有哪些状态

  1. 线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态。
  2. 阻塞的情况又分为三种:等待阻塞、同步阻塞、其他阻塞
  • 等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池"中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则VM会把该线程放入"锁池"中。
  • 其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终止或者超时、或者l/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法

三、第三部分

21、sleep()、wait()、join()、yield()的区别

1.锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。
⒉.等待池
当我们调用wait ()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify ()或notifyAll()后等待池的线程才会开始去竞争锁,notify ()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中。

  1. sleep是Thread类的静态本地方法,wait 则是 Object类的本地方法。
  2. sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
  3. sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
  4. sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
  5. sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于多线程之间的通信。
  6. sleep 会让出CPU执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行的。

yield () 执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行

join () 执行后线程进入阻塞状态,例如在线程B中调用线程A的join (),那线程B会进入到阻塞队列,直到线程A结束或中断线程

22、说说你对线程安全的理解(中级程序员)

不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问

当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的

是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。

在Java中,堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放。

	目前主流操作系统都是多任务的,即多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。
	在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。

23、Thread、 Runable的区别

Thread和Runnable的实质是继承关系,一个是继承一个是实现接口。没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。

24、说说你对守护线程的理解

可理解后台线程,不用管它。所有用户线程退出,它也会自动退出。

守护线程 :为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆;

守护线程类似于整个进程的一个默默无闻的小喽喽;它的生死无关重要,它却依赖整个进程而运行;哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断了;

注意:由于守护线程的终止是自身无法控制的,因此千万不要把lO、File等重要操作逻辑分配给它;因为它不靠谱;

作用:GC垃圾回收线程:就是一个经典的守护线程。

应用场景

​ (1)来为其它线程提供服务支持的情况;

​ (2)或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。

25、ThreadLocal的原理和使用场景

原理:

每一个Thread对象均含有一个ThreadLoca1Map类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值。

ThreadLocalMap由一个个Entry对象构成。

Entry继承自weakReference<ThreadLocal<?>>,一个Entry由ThreadLoca1对象和object构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收。

当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。

get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。

由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。

使用场景:

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离。
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
Spring框架在事务开始时会给当前线程绑定一个Jdbc connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离。

在这里插入图片描述

26、ThreadLocal内存泄露原因,如何避免

原因:

内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果严重,无论多少内存,迟早会被占光,不再会被使用的对象或者变量占用的内存不能被回收。就是内存泄露。

Threadlocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

避免方法:

ThreadLocal正确的使用方法
	每次使用完ThreadLocal都调用它的remove()方法清除数据。
	将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。

27、并发、并行、串行的区别

串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着

并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。

并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行

28、并发的三大特性

  1. 原子性

    原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。

    就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。2个操作必须全部完成。

  2. 可见性
    当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。
    若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。

  3. 有序性

    虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。

29、为什么用线程池?解释下线程池参数?

1、降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。

  • corePoo1size代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是—种常驻线程
  • maxinumPoo1size代表的是最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最大线程数
  • keepAliveTime、unit表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过 setKeepAliveTime来设置空闲时间
  • workQueue 用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程
  • ThreadFactory实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
  • Hand1er任务拒绝策略,有两种情况,第一种是当我们调用shutdown等方法关闭线程池后,这时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝。另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,这是也就拒绝

30、简述线程池处理流程

在这里插入图片描述
在这里插入图片描述

扩展

1、Solr和ElaticSearch的区别
Solr和ElaticSearch都是基于Lucene的Java搜索引擎服务器程序
Solr:
	更新索引比较较慢搜索效率比较高
	适用于:不需要实时搜索的项目,例如:电商类的项目
ElaticSearch:
	更新索引比较快搜索效率比较低
	他是为分布式项目而生
	适用于:需要实时搜索的项目,例如:新闻类项目,博客。。 o o o
2、Redis中有几种数据类型?分别是什么?
有五种,分别是:String(字符串)、hash(哈希)、list(列表)、set(集合)
							zset(sorted set:有序集合)。
3、Redis和 Memcached 的区别和使用场景?

区别:
1、Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等;

2、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储;

3、虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘;

4、过期策略–memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10;

5、分布式–设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从;

6、存储数据安全–memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化);

7、灾难恢复–memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复;

8、Redis支持数据的备份,即master-slave模式的数据备份;

9、应用场景不一样:Redis出来作为NoSQL数据库使用外,还能用做消息队列、数据堆栈和数据缓存等;Memcached适合于缓存SQL语句、数据集、用户临时性数据、延迟查询数据和session等。

使用场景:
1、如果有持久方面的需求或对数据类型和处理有要求的应该选择redis。
2、如果简单的key/value 存储应该选择memcached。

4、springboot和 springMvc

Spring 是一个“引擎”;

Spring MVC 是基于Spring的一个 MVC 框架 ;

Spring Boot 是基于Spring4的条件注册的一套快速开发整合包。

四、第四部分

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

1. 作用:

  • 一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务
  • 阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
  • 阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时;线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活、不至于一直占用cpu资源

2. 第二问

  • 在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。

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

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

33、spring是什么?

  • 轻量级的开源的J2EE框架。它是一个容器框架,用来装javabean (java对象),中间层框架(万能胶)可以起一个连接作用,比如说把Struts和hibernate粘合在一起运用,可以让我们的企业开发更快、更简洁。

  • Spring是一个轻量级的控制反转(loC)和面向切面(AOP)的容器框架

    –从大小与开销两方面而言Spring都是轻量级的。

    –通过控制反转(IoC)的技术达到松耦合的目的

    –提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发–包含并管 理应用对象(Bean)的配置和生命周期,这个意义上是一个容器。

    –将简单的组件配置、组合成为复杂的应用,这个意义上是一个框架。

34、谈谈你对AOP的理解

系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自身核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。
当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。
日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

**AOP:**将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情

35、谈谈你对IOC的理解

  • ioc容器:

    实际上就是个map (key,value),里面存的是各种对象(在xml里配置的bean节点、@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创建对象放到map里。

  • 控制反转:

    1. 没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
    2. 引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
    3. 通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是"控制反转”"这个名称的由来。
    4. 全部对象的控制权全部上缴给"第三方"IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似"粘合剂""的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个粘合剂",对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂"的由来。
  • 依赖注入:

    “获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

36、BeanFactory和ApplicationContext有什么区别?

ApplicationContext是BeanFactory的子接口

ApplicationContext提供了更完整的功能:

​ ①继承MessageSource,因此支持国际化。

​ ②统一的资源文件访问方式。

​ ③提供在监听器中注册bean的事件。

​ ④同时加载多个配置文件。

​ ⑤载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层。

  • BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
  • ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
  • 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
  • BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
  • BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是: BeanFactory需要手动注册,而ApplicationContext则是自动注册。

37、描述一下Spring Bean的生命周期(面试频率高)

1、解析类得到BeanDefinition
2、如果有多个构造方法,则要推断构造方法3、确定好构造方法后,进行实例化得到一个对象
4、对对象中的加了@Autowired注解的属性进行属性填充
5、回调Aware方法,比如BeanNameAware,BeanFactoryAware6、调用BeanPostProcessor的初始化前的方法
7、调用初始化方法
8、调用BeanPostProcessor的初始化后的方法,在这里会进行AOP(动态代理)
9、如果当前创建的bean是单例的则会把bean放入单例池
10、使用bean
11、Spring容器关闭时调用DisposableBean中destory()方法

38、解释下Spring支持的几种bean的作用域

  • singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。该对象的生命周期是与Spring lOC容器一致的(但在第一次被注入时才会创建)。
  • prototype—原型模式:为每一个bean请求提供一个实例。在每次注入时都会创建一个新的对象
  • request: bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。
  • session: 与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
  • application: bean被定义为在ServletContext的生命周期中复用一个单例对象。websocket: bean被定义为在websocket的生命周期中复用一个单例对象。
global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在
global-session中。全局作用域与Servlet中的session作用域效果相同。

39、Spring框架中的单例Bean是线程安全的么?

​ 不是线程安全的。解释如下:

  1. Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。

  2. 如果Bean是有状态的那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域把"singleton"改为"protopyte’这样每次请求Bean就相当于是new Bean()这样就可以保证线程的安全了。

    • 有状态就是有数据存储功能

    • 无状态就是不会保存数据 controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。

  3. Dao会操作数据库Connection,Connection是带有状态的,比如说数据库事务,Spring的事务管理器使用Threadlocal为不同线程维护了一套独立的connection副本,保证线程之间不会互相影响(Spring是如何保证事务获取同一个Connection的)

  4. 不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。

40、Spring框架中都用到了哪些设计模式

  • 简单工厂:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。(不是23种设计模式之一)
spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
  • 工厂方法:
实现了FactoryBean接口的bean是一类叫做factoryil的bean。其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getobject()方法,所以返回的不是factory这个bean,而是这个bean.getOjbect()方法的返回值。
  • 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
spring对单例的实现: spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是任意的java对象。
  • 适配器模式:
spring定义了一个适配接口,使得每一种Contro11er有一种对应的适配器实现类,让适配器代替contro11er执行相应的方法。这样在扩展Contro11er时,只需要增加一个适配器类就完成了SpringMVc的扩展了。
  • 装饰器模式:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有wrapper,另一种是类名中含有Decorator。
  • 动态代理:
切面在应用运行的时刻被织入。一般情况下,在织入切面时,AoOP容器会为目标对象创建动态的创建一个代理对象。SpringAOP就是以这种方式织入切面的。

织入:把切面应用到目标对象并创建新的代理对象的过程。
  • 观察者模式:
spring的事件驱动模型使用的是观察者模式,Spring中0bserver模式常用的地方是Listener的实现。
  • 策略模式:
Spring框架的资源访向Resource接口。该接口提供了更强的资源访问能力,Spring框架本身大量使用了Resource接口来访问底层资源。

41、Spring事务的实现方式和原理以及隔离级别

  • 在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是申明式的,@Transactional注解就是申明式的。
  • 事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。
  • spring事务隔离级别就是数据库的隔离级别:外加一个默认级别.
    1. read ----uncommitted(未提交读)
    2. read committed(提交读、不可重复读)
    3. repeatable read(可重复读)
    4. serializable (可串行化)
数据库的配置隔离级别是Read commited, 而iSpring配置的隔离级别是Repeatable Read,请问这时隔离级别是以哪一个为准?
	以spring配置的为准,如果spring设置的隔离级别数据库不支持,效果取决于数据库

42、spring事务传播机制

  1. REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
  2. SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
  3. MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
  4. REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。
  5. NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务
  6. NEVER:不使用事务,如果当前事务存在,则抛出异常
  7. NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)

43、spring事务什么时候会失效

spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有如下几种

  1. 发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是UserService对象本身!
    解决方法很简单,让那个this变成UserService的代理类即可!
  2. 方法不是public的
  3. 数据库不支持事务
  4. 没有被spring管理
  5. 异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)

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

开启自动装配,只需要在xml配置文件中定义"autowire"属性。
autowire属性有五种装配的方式:

  1. no-缺省情况下,自动配置是通过“ref"属性手动设定。
  2. byName-根据bean的属性名称进行自动装配。
  3. byType-根据bean的类型进行自动装配。
  4. constructor-类似byType,不过是应用于构造器的参数。如果一个bean与构造器参数的类型形同,则进行自动装配,否则导致异常。
  5. autodetect-如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配。

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

spring是一个lOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志、异常等

springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端

springboot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制) 、redis、mongodb、es,可以开箱即用

46、SpringMVC工作流程

  1. 1)用户发送请求至前端控制器DispatcherServlet。
  2. 2)DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  3. 3)处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
    1. DispatcherServlet 调用HandlerAdapter处理器适配器。
  4. 5)HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)
  5. 6)Controller执行完成返回ModelAndView。
  6. 7)HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
  7. 8)DispatcherServlet将ModelAndview传给ViewReslover视图解析器。
    1. ViewReslover解析后返回具体View。
  8. 10)DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
  9. 11)DispatcherServlet响应用户。

五、第五部分

47、Spring MVC的主要组件

Handler:也就是处理器。它直接应对着MVC中的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法。在Controller层中@RequestMapping标注的所有方法都可以看成是一个Handler,只要可以实际处理请求就可以是Handler

  1. HandlerMapping
    initHandlerMappings(context),处理器映射器,根据用户请求的资源uri来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行,这就是HandlerMapping需要做的事。
  2. HandlerAdapter
    initHandlerAdapters(context),适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。

Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。

48、Spring Boot自动配置原理

@lmport + @Configuration + Spring spi
自动配置类由各个starter提供,使用@Configuration +@Bean定义配置类,放到META-INF/spring.factories下
使用Spring spi扫描META-INF/spring.factories下的配置类
使用@lmport导入自动配置类

在这里插入图片描述

49、如何理解 Spring Boot中的Starter

  • 使用spring + springmvc使用,如果需要引入mybatis等框架,需要到xml中定义mybatis需要的bean
  • starter就是定义一个starter的jar包,写一个@Configuration配置类、将这些bean定义在里面,然后在starter包的META-INF/spring.factories中写入该配置类,springboot会按照约定来加载该配置类
  • 开发人员只需要将相应的starter包依赖进应用,进行相应的属性配置(使用默认配置时,不需要配置),就可以直接进行代码开发,使用对应的功能了,比如mybatis-spring-boot–starter,spring-boot-starter-redis

50、什么是嵌入式服务器?为什么要使用嵌入式服务器

  • 节省了下载安装tomcat,应用也不需要再打war包,然后放到webapp目录下再运行
  • 只需要一个安装了Java的虚拟机,就可以直接在上面部署应用程序了
  • springboot已经内置了tomcat.jar,运行main方法时会去启动tomcat,并利用tomcat的spi机制加载springmvc

51、mybatis的优缺点

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

52、MyBatis 与Hibernate 对比

  • MyBatis基于SQL语句
  • Hibernate基于ORM(对象关系映射)
  • 开发速度对比:简单的增删改查,hibernate效率高一些;对于一个大型项目,复杂语句较多,选择mybatis效率高一些。
  • sql优化方面:
    Hibernate的查询会将表中的所有字段查询出来,这一点会有性能消耗。Hibernate也可以自己写SQL来指定需要查询的字段,但这样就破坏了Hibernate开发的简洁性。而Mybatis的SQL是手动编写的,所以可以按需求指定查询的字段。

53、#{}和${的区别是什么

  • #{}是预编译处理、是占位符,${}是字符串替换、是拼接符。
  • Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement来赋值;
  • Mybatis在处理${}时,就是把${}替换成变量的值,调用Statement来赋值;
  • #{}的变量替换是在DBMS中、变量替换后,#{}对应的变量自动加上单引号
  • ${}的变量替换是在DBMS外、变量替换后,${}对应的变量不会加上单引号
  • 使用#{}可以有效的防止SQL注入,提高系统安全性。

54、简述Mybatis的插件运行原理,如何编写一个插件

  • Mybatis只支持针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的 invoke()方法,拦截那些你指定需要拦截的方法。
  • 编写插件︰实现Mybatis的 Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,在配置文件中配置编写的插件。

55、索引的基本原理

索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行 查询时遍历整张表。索引的原理:就是把无序的数据变成有序的查询

  1. 把创建了索引的列的内容进行排序
  2. 对排序结果生成倒排表
  3. 在倒排表内容上拼上数据地址链
  4. 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据

56、mysql聚簇和非聚簇索引的区别

  1. 都是B+树的数据结构
  • 聚簇索引:将数据存储与索引放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的
  • 非聚簇索引:叶子节点不存储数据、存储的是数据行地址,也就是说根据索引查找到数据行的位置再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节,那我们先在这个目录里面找,找到对应的页码后再去对应的页码看文章。
  1. InnoDB中一定有主键,主键一定是聚簇索引,不手动设置、则会使用unique索引,没有unique索引,则会使用数据库内部的一个行的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值
  2. MyISM使用的是非聚簇索引,没有聚簇索引,非聚簇索引的两棵B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。
  3. 如果涉及到大数据量的排序、全表扫描、count之类的操作的话,还是MyISAM占优势些,因为索引所占空间小,这些操作是需要在内存中完成的。

57、mysql索引的数据结构,各自优劣

  • 索引的数据结构和具体存储引擎的实现有关,在MySQL中使用较多的索引有Hash索引,B+树索引等,InnoDB存储引擎的默认索引实现为:B+树索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
  • B+树:
    B+树是一个平衡的多叉树,从根节点到每个叶子节点的高度差值不超过1,而且同层级的节点间有指针相互链接。在B+树上的常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高。因此,B+树索引被广泛应用于数据库、文件系统等场景。
  • 哈希索引:
    哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快
  • 总结:等值查询,哈希索引有绝对的优势;范围查询检索,哈希索引无用。

58、索引设计的原则

查询更快、占用空间更小

  1. 适合索引的列是出现在where子句中的列,或者连接子句中指定的列
  2. 基数较小的表,索引效果较差,没有必要在此列建立索引
  3. 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间,如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。
  4. 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
  5. 定义有外键的数据列一定要建立索引。
  6. 更新频繁字段不适合创建索引
  7. 若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
  8. 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
  9. 对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
  10. 对于定义为text、image和bit的数据类型的列不要建立索引。

59、mysql锁的类型有哪些

  • 基于锁的属性分类:共享锁、排他锁。
  • 基于锁的粒度分类:行级锁(INNODB)、表级锁(INNODB、MYISAM)、页级锁(BDB引擎)、记录锁、间隙锁、临键锁。
  • 基于锁的状态分类:意向共享锁、意向排它锁。

60、mysql执行计划怎么看

执行计划就是sql的执行查询的顺序,以及如何使用索引查询,返回的结果集的行数

61、事务的基本特性和隔离级别

事务基本特性ACID分别是:

  • 原子性指的是一个事务中的操作要么全部成功,要么全部失败。
  • 一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设A只有90块,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证
  • 隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。
  • 持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。

隔离性有4个隔离级别,分别是:

  • read uncommit读未提交,可能会读到其他事务未提交的数据,也叫做脏读。
    用户本来应该读取到id=1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读取结果age=20,这就是脏读。
  • read commit读已提交,两次读取结果不一致,叫做不可重复读。
    不可重复读解决了脏读的问题,他只会读取已经提交的事务。
    用户开启事务读取id=1用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查询读取到不同的结果叫做不可重复读。
  • repeatable read可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
  • serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

62、关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过

在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。

慢查询的优化首先要搞明白慢的原因是什么?是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?

所以优化也是针对这三个方向来的,

  • 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
  • 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
  • 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。

63、ACID靠什么保证的

  • A 原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql
  • C 一致性由其他三大特性保证、程序代码要保证业务上的一致性
  • l 隔离性由MVCC来保证
  • D 持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,宕机的时候可以从redolog恢复

64、什么是MVCC

多版本并发控制:读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务session会看到自己特定版本的数据,版本链

MCC只在READ COMMITTED和REPEATABLE READ两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容,因为READ UNCOMMITTED 总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁。

65、mysql主从同步原理

mysql主从同步的过程:
Mysql的主从复制中主要有三个线程: master (binLog dump thread)、slave(I/O thread 、 SQL thread), Master—条线程和Slave中的两条线程。

由于mysal默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。

  • 全同步复制
    主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。
  • 半同步复制
    和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。

66、简述MyISAM和InnoDB的区别

MylSAM:(主要用于查)

  • 不支持事务,但是每次查询都是原子的;
  • 支持表级锁,即每次操作是对整个表加锁;
  • 存储表的总行数;
  • 一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;
  • 采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性。
    lnnoDB:(主要用于写)
  • 支持ACID的事务,支持事务的四种隔离级别;
  • 支持行级锁及外键约束:因此可以支持写并发;
  • 不存储总行数;
  • 一个InnoDB引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能分布在多个文件里),也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,一般为2G),受操作系统文件大小的限制;
  • 主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使用自增主键,防止插入数据时,为维持B+树结构,文件的大调整。

67、简述mysql中索引类型及对数据库的性能的影响

  • 普通索引:允许被索引的数据列包含重复的值。
  • 唯一索引:可以保证数据记录的唯一性。
  • 主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字PRIMARY KEY 来创建。
  • 联合索引:索引可以覆盖多个数据列,如像INDEX(columnA, columnB)索引。
  • 全文索引:通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column;创建全文索引

  • 索引可以极大的提高数据的查询速度。
  • 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
  • 但是会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件
  • 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大,如果非聚集索引很多,一旦聚集索引改变,那么所有非聚集索引都会跟着变。

六、第六部分(Redis)

68、RDB 和AOF机制

  • RDB: Redis DataBase
    在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

  • AOF: Append Only File
    以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录

  • AOF文件比RDB更新频率高,优先使用AOF还原数据。
    AOF比RDB更安全也更大
    RDB性能比AOF好
    如果两个都配了优先加载AOF

69、Redis的过期键的删除策略

Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的亳秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

Redis中同时使用了惰性过期和定期过期两种过期策略。

70、Redis线程模型,单线程为什么快

Redis基于Reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器file event handler。这个文件事件处理器,它是单线程的,所以Redis 才叫做单线程的模型,它采用IO多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。

单线程快的原因:
1)纯内存操作
2)核心是基于非阻塞的IO多路复用机制
3)单线程反而避免了多线程的频繁上下文切换带来的性能问题

71、缓存雪崩、缓存穿透、缓存击穿

1、缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  • 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。
  • 缓存预热
  • 互斥锁

2、缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成教据库短时间内承受大量请求而崩掉。

解决方案:

  • 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  • 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
  • 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力

3、缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  • 设置热点数据永远不过期。
  • 加互斥锁

72、简述Redis事务实现

  1. 事物开始
  2. 命令入队
  3. 事物执行

73、redis集群方案

  1. 主从模式
  2. 哨兵模式
  3. Redis Cluster是一种服务端Sharding技术,3.0版本开始正式提供。采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行。在redis cluster架构下,每个redis要放开两个端口号,一个是6379客服端访问,另一个是16379端口号是用来进行节点间通信的,也就是cluster bus的通信,用来进行故障检测、配置更新、故障转移授权。
  4. Redis Sharding是Redis Cluster出来之前,业界普遍使用的多Redis实例集群方法。

74、redis主从复制的核心原理

全量复制:
(1)主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘lO的
(2)主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
(3)从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行bgrewriteaof,也会带来额外的消耗
部分复制(增量复制):
1.复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset
⒉.复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出(FIFO)队列作为复制积压缓冲区,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
3.服务器运行ID(runid):每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成,主节点会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。从节点Redis断开重连的时候,就是根据运行ID来判断同步的进度:

在这里插入图片描述

七、第七部分(分布式)

75、CAP理论,BASE理论

CAP理论:consistency (一致性)、Availability (可用性)、Partition Tolerance(分区容错性)

consistency (一致性):
即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致。对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。
Availability (可用性):
即服务一直可用,而且是正常响应时间。系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
Partition Tolerance(分区容错性):
即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对于用户而言并没有什么体验上的影响。

BASE是Basically Available (基本可用) 、Soft state(软状态)和Eventually consistent(最终一致性)

BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
基本可用:
·响应时间上的损失:正常情况下,处理用户请求需要0.5s返回结果,但是由于系统出现故障,处理用户请求的时间变为3 s.
·系统功能上的损失:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用。
软状态:数据同步允许一定的延迟
最终一致性:系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,不要求实时

76、负载均衡算法、类型

1、轮询法
将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。
2、随机法
通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,
其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。3、源地址哈希法
源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。
4、加权轮询法
不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。
5、加权随机法
与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。
6、最小连接数法
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前
积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。

类型:

  • DNS方式实现负载均衡
  • 硬件负载均衡:F5和A10
  • 软件负载均衡:
    Nginx、HAproxy、LvS。

77、分布式架构下,Session共享有什么方案

1、采用无状态服务,抛弃session

2、存入cookie(有安全风险)

3、服务器之间进行Session同步,这样可以保证每个服务器上都有全部的Session信息,不过当服务器数量比较多的时候,同步是会有延迟甚至同步失败;

4、IP绑定策略
使用Nginx(或其他复杂均衡软硬件)中的IP绑定策略,同一个IP只能在指定的同一个机器访问,但是这样做失去了负载均衡的意义,当挂掉一台服务器的时候,会影响一批用户的使用,风险很大;

5、使用Redis存储
把Session放到Redis中存储,虽然架构上变得复杂,并且需要多访问一次Redis,但是这种方案带来的好处也是很大的:

  • 实现了Session共享;
  • 可以水平扩展(增加Redis服务器);
  • 服务器重启Session不丢失(不过也要注意Session在Redis中的刷新/失效机制);
  • 不仅可以跨服务器Session共享,甚至可以跨平台(例如网页端和APP端)。

78、简述你对RPC、RMI的理解

RPC:在本地调用远程的函数,远程过程调用,可以跨语言实现httpClient

RMI:远程方法调用,java中用于实现RPC的一种机制,RPC的java版本,是2EE的网络调用机制,跨JVM调用对象的方法,面向对象的思维方式

79、分布式id生成方案

  • uuid
    1,当前日期和时间时间戳
    2,时钟序列。计数器
    3,全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
    优点:
    代码简单,性能好(本地生成,没有网络消耗),保证唯一(相对而言,重复概率极低可以忽略)
    缺点:

    • 每次生成的ID都是无序的,而且不是全数字,且无法保证趋势递增。
    • UUID生成的是字符串,字符串存储性能差,查询效率慢,写的时候由于不能产生顺序的append操作,需要进行insert操作,导致频繁的页分裂,这种操作在记录占用空间比较大的情况下,性能下降比较大,还会增加读取磁盘次数
    • UUID长度过长,不适用于存储,耗费数据库性能。
    • ID无一定业务含义,可读性差。
    • 有信息安全问题,有可能泄露mac地址
  • 数据库自增序列

  • Leaf-segment
    采用每次获取一个ID区间段的方式来解决,区间段用完之后再去数据库获取新的号段,这样一来可以大大减轻数据库的压力

  • 基于redis、mongodb、zk等中间件生成

  • 雪花算法

80、分布式锁解决方案

需要这个锁独立于每一个服务之外,而不是在服务里面。
数据库:利用主键冲突控制一次只有一个线程能获取锁,非阻塞、不可重入、单点、失效时间
zookeeper分布式锁:
Redis分布式锁:setNX,单线程处理网络请求,不需要考虑并发安全性所有服务节点设置相同的key,返回为0、则锁获取失败
删除锁:判断线程唯一标志,再删除
可重入性及锁续期没有实现,通过redisson解决(类似AQS的实现,看门狗监听机制)

八、

1、前后端分离项目,如何解决跨域问题?

前后端分离项目跨域问题是不可避免的。通常情况下前端由React、Vue等框架编写,通过ajax请求服务端API,传输数据用json格式。

  • nginx代理
  • 使用 filter 添加头信息
  • 使用 @CrossOrigin 注解(springboot里面的注解)
  • 使用 proxy 代理

九、线程面试题

  1. 说说你对线程安全的理解

    不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问

    当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的

    **堆:**是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。

    **栈:**是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放。

  2. Thread、 Runable的区别

    Thread和Runnable的实质是继承关系,一个是继承一个是实现接口。没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。

  3. 说说你对守护线程的理解

    可理解后台线程,不用管它。所有用户线程退出,它也会自动退出。为所有非守护线程提供服务的线程。

    作用:GC垃圾回收线程:就是一个经典的守护线程。

  4. ThreadLocal的原理和使用场景

    原理:

    首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。

    因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个ThreadLocalMap类对应的 get()、set() 方法。

    使用场景:

    1、进行事务操作,用于存储线程事务信息。
    2、数据库连接,Session会话管理。

  5. 为什么用线程池?

    1、降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。
    2、提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
    3、提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。

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

    第一问

    • 一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务
    • 阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
    • 阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时;线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活、不至于一直占用cpu资源

    第二问

    • 在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。
  7. 并发、并行、串行的区别

    串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着

    并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。

    并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行

  8. Java 中 sleep 方法和 wait 方法的区别?

    虽然两者都是用来暂停当前运行的线程,但是 sleep() 实际上只是短暂停顿,因为它不会释放锁,而 wait() 意味着条件等待,这就是为什么该方法要释放锁,因为只有这样,其他等待的线程才能在满足条件时获取到该锁。

  9. Java 中 ++ 操作符是线程安全的吗

    不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。

  10. 用Java写代码来解决生产者—消费者问题

    先弄明白生产者与消费者的关系。生产者、消费者是两个实体对象,生产者生产物品,消费者消费物品。如果在生产者中定义生产的流程,在消费者中定义消费的流程,两个对象就需要彼此引用,这样依赖性太高,而且实际上性能也不高,这个时候就需要一个缓冲器,一个中间对象,我们把它形象地称之为“仓库”。生产的物品放入仓库,消费的物品从仓库中取出,这样生产者和消费者就能够取消两者之间的引用,直接通过仓库引用来同步状态,降低耦合。

    接着我们来看一下生产者、消费者问题的两种类型,一种是使用某种机制来保护生产者和消费者之间的同步,另一种则与Linux中的管道思路相似。相对来说第一种类型的处理方式更为常规,实现方式大致分为三种:经典的wait(),notify()方法、await(),signal()方法以及使用阻塞队列(BlockingQueue)的方法。

  11. springmvc的处理流程

在这里插入图片描述

具体步骤:

1、 首先用户发送请求到前端控制器,前端控制器根据请求信息(如 URL)来决定选择哪一个页面控制器进行处理并把请求委托给它,即以前的控制器的控制逻辑部分;图中的 1、2 步骤;

2、 页面控制器接收到请求后,进行功能处理,首先需要收集和绑定请求参数到一个对象,这个对象在 Spring Web MVC 中叫命令对象,并进行验证,然后将命令对象委托给业务对象进行处理;处理完毕后返回一个 ModelAndView(模型数据和逻辑视图名);图中的 3、4、5 步骤;

3、 前端控制器收回控制权,然后根据返回的逻辑视图名,选择相应的视图进行渲染,并把模型数据传入以便视图渲染;图中的步骤 6、7;

4、 前端控制器再次收回控制权,将响应返回给用户,图中的步骤 8;至此整个结束。


 

posted @ 2021-09-08 14:06  程序那点事  阅读(173)  评论(0编辑  收藏  举报